Newer
Older
dub_jkp / source / dub / generators / build.d
  1. /**
  2. Generator for direct compiler builds.
  3.  
  4. Copyright: © 2013-2013 rejectedsoftware e.K.
  5. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  6. Authors: Sönke Ludwig
  7. */
  8. module dub.generators.build;
  9.  
  10. import dub.compilers.compiler;
  11. import dub.compilers.utils;
  12. import dub.generators.generator;
  13. import dub.internal.utils;
  14. import dub.internal.vibecompat.core.file;
  15. import dub.internal.vibecompat.inet.path;
  16. import dub.internal.logging;
  17. import dub.package_;
  18. import dub.packagemanager;
  19. import dub.project;
  20.  
  21. import std.algorithm;
  22. import std.array;
  23. import std.conv;
  24. import std.exception;
  25. import std.file;
  26. import std.process;
  27. import std.string;
  28. import std.encoding : sanitize;
  29.  
  30. string getObjSuffix(const scope ref BuildPlatform platform)
  31. {
  32. return platform.isWindows() ? ".obj" : ".o";
  33. }
  34.  
  35. string computeBuildName(string config, in GeneratorSettings settings, const string[][] hashing...)
  36. {
  37. import std.digest.sha : SHA256;
  38. import std.base64 : Base64URL;
  39.  
  40. SHA256 hash;
  41. hash.start();
  42. void addHash(in string[] strings...) { foreach (s; strings) { hash.put(cast(ubyte[])s); hash.put(0); } hash.put(0); }
  43. foreach(strings; hashing)
  44. addHash(strings);
  45. addHash(settings.platform.platform);
  46. addHash(settings.platform.architecture);
  47. addHash(settings.platform.compiler);
  48. addHash(settings.platform.compilerVersion);
  49. const hashstr = Base64URL.encode(hash.finish()[0 .. $ / 2]).stripRight("=");
  50.  
  51. return format("%s-%s-%s", config, settings.buildType, hashstr);
  52. }
  53.  
  54. class BuildGenerator : ProjectGenerator {
  55. private {
  56. PackageManager m_packageMan;
  57. NativePath[] m_temporaryFiles;
  58. }
  59.  
  60. this(Project project)
  61. {
  62. super(project);
  63. m_packageMan = project.packageManager;
  64. }
  65.  
  66. override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets)
  67. {
  68. import std.path : setExtension;
  69. scope (exit) cleanupTemporaries();
  70.  
  71. void checkPkgRequirements(const(Package) pkg)
  72. {
  73. const tr = pkg.recipe.toolchainRequirements;
  74. tr.checkPlatform(settings.platform, pkg.name);
  75. }
  76.  
  77. checkPkgRequirements(m_project.rootPackage);
  78. foreach (pkg; m_project.dependencies)
  79. checkPkgRequirements(pkg);
  80.  
  81. auto root_ti = targets[m_project.rootPackage.name];
  82. const rootTT = root_ti.buildSettings.targetType;
  83.  
  84. enforce(!(settings.rdmd && rootTT == TargetType.none),
  85. "Building package with target type \"none\" with rdmd is not supported yet.");
  86.  
  87. logInfo("Starting", Color.light_green,
  88. "Performing \"%s\" build using %s for %-(%s, %).",
  89. settings.buildType.color(Color.magenta), settings.platform.compilerBinary,
  90. settings.platform.architecture);
  91.  
  92. bool any_cached = false;
  93.  
  94. NativePath[string] target_paths;
  95.  
  96. NativePath[] dynamicLibDepsFilesToCopy; // to the root package output dir
  97. const copyDynamicLibDepsLinkerFiles = rootTT == TargetType.dynamicLibrary || rootTT == TargetType.none;
  98. const copyDynamicLibDepsRuntimeFiles = copyDynamicLibDepsLinkerFiles || rootTT == TargetType.executable;
  99.  
  100. bool[string] visited;
  101. void buildTargetRec(string target)
  102. {
  103. if (target in visited) return;
  104. visited[target] = true;
  105.  
  106. auto ti = targets[target];
  107.  
  108. foreach (dep; ti.dependencies)
  109. buildTargetRec(dep);
  110.  
  111. NativePath[] additional_dep_files;
  112. auto bs = ti.buildSettings.dup;
  113. const tt = bs.targetType;
  114. foreach (ldep; ti.linkDependencies) {
  115. const ldepPath = target_paths[ldep].toNativeString();
  116. const doLink = tt != TargetType.staticLibrary && !(bs.options & BuildOption.syntaxOnly);
  117.  
  118. if (doLink && isLinkerFile(settings.platform, ldepPath))
  119. bs.addSourceFiles(ldepPath);
  120. else
  121. additional_dep_files ~= target_paths[ldep];
  122.  
  123. if (targets[ldep].buildSettings.targetType == TargetType.dynamicLibrary) {
  124. // copy the .{dll,so,dylib}
  125. if (copyDynamicLibDepsRuntimeFiles)
  126. dynamicLibDepsFilesToCopy ~= NativePath(ldepPath);
  127.  
  128. if (settings.platform.isWindows()) {
  129. // copy the accompanying .pdb if found
  130. if (copyDynamicLibDepsRuntimeFiles) {
  131. const pdb = ldepPath.setExtension(".pdb");
  132. if (existsFile(pdb))
  133. dynamicLibDepsFilesToCopy ~= NativePath(pdb);
  134. }
  135.  
  136. const importLib = ldepPath.setExtension(".lib");
  137. if (existsFile(importLib)) {
  138. // link dependee against the import lib
  139. if (doLink)
  140. bs.addSourceFiles(importLib);
  141. // and copy
  142. if (copyDynamicLibDepsLinkerFiles)
  143. dynamicLibDepsFilesToCopy ~= NativePath(importLib);
  144. }
  145.  
  146. // copy the .exp file if found
  147. const exp = ldepPath.setExtension(".exp");
  148. if (copyDynamicLibDepsLinkerFiles && existsFile(exp))
  149. dynamicLibDepsFilesToCopy ~= NativePath(exp);
  150. }
  151. }
  152. }
  153. NativePath tpath;
  154. if (tt != TargetType.none) {
  155. if (buildTarget(settings, bs, ti.pack, ti.config, ti.packages, additional_dep_files, tpath))
  156. any_cached = true;
  157. }
  158. target_paths[target] = tpath;
  159. }
  160.  
  161. // build all targets
  162. if (settings.rdmd || rootTT == TargetType.staticLibrary) {
  163. // RDMD always builds everything at once and static libraries don't need their
  164. // dependencies to be built
  165. NativePath tpath;
  166. buildTarget(settings, root_ti.buildSettings.dup, m_project.rootPackage, root_ti.config, root_ti.packages, null, tpath);
  167. return;
  168. }
  169.  
  170. buildTargetRec(m_project.rootPackage.name);
  171.  
  172. if (dynamicLibDepsFilesToCopy.length) {
  173. const rootTargetPath = NativePath(root_ti.buildSettings.targetPath);
  174.  
  175. ensureDirectory(rootTargetPath);
  176. foreach (src; dynamicLibDepsFilesToCopy) {
  177. logDiagnostic("Copying target from %s to %s",
  178. src.toNativeString(), rootTargetPath.toNativeString());
  179. hardLinkFile(src, rootTargetPath ~ src.head, true);
  180. }
  181. }
  182.  
  183. if (any_cached) {
  184. logInfo("Finished", Color.green,
  185. "To force a rebuild of up-to-date targets, run again with --force"
  186. );
  187. }
  188. }
  189.  
  190. override void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets)
  191. {
  192. // run the generated executable
  193. auto buildsettings = targets[m_project.rootPackage.name].buildSettings.dup;
  194. if (settings.run && !(buildsettings.options & BuildOption.syntaxOnly)) {
  195. NativePath exe_file_path;
  196. if (m_tempTargetExecutablePath.empty)
  197. exe_file_path = getTargetPath(buildsettings, settings);
  198. else
  199. exe_file_path = m_tempTargetExecutablePath ~ settings.compiler.getTargetFileName(buildsettings, settings.platform);
  200. runTarget(exe_file_path, buildsettings, settings.runArgs, settings);
  201. }
  202. }
  203.  
  204. private bool buildTarget(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, in Package[] packages, in NativePath[] additional_dep_files, out NativePath target_path)
  205. {
  206. import std.path : absolutePath;
  207.  
  208. auto cwd = settings.toolWorkingDirectory;
  209. bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly);
  210.  
  211. auto build_id = buildsettings.computeBuildID(config, settings);
  212.  
  213. // make all paths relative to shrink the command line
  214. string makeRelative(string path) { return shrinkPath(NativePath(path), cwd); }
  215. foreach (ref f; buildsettings.sourceFiles) f = makeRelative(f);
  216. foreach (ref p; buildsettings.importPaths) p = makeRelative(p);
  217. foreach (ref p; buildsettings.cImportPaths) p = makeRelative(p);
  218. foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p);
  219.  
  220. // perform the actual build
  221. bool cached = false;
  222. if (settings.rdmd) performRDMDBuild(settings, buildsettings, pack, config, target_path);
  223. else if (!generate_binary) performDirectBuild(settings, buildsettings, pack, config, target_path);
  224. else cached = performCachedBuild(settings, buildsettings, pack, config, build_id, packages, additional_dep_files, target_path);
  225.  
  226. // HACK: cleanup dummy doc files, we shouldn't specialize on buildType
  227. // here and the compiler shouldn't need dummy doc output.
  228. if (settings.buildType == "ddox") {
  229. if ("__dummy.html".exists)
  230. removeFile("__dummy.html");
  231. if ("__dummy_docs".exists)
  232. rmdirRecurse("__dummy_docs");
  233. }
  234.  
  235. // run post-build commands
  236. if (!cached && buildsettings.postBuildCommands.length) {
  237. logInfo("Post-build", Color.light_green, "Running commands");
  238. runBuildCommands(CommandType.postBuild, buildsettings.postBuildCommands, pack, m_project, settings, buildsettings,
  239. [["DUB_BUILD_PATH" : target_path is NativePath.init
  240. ? ""
  241. : target_path.parentPath.toNativeString.absolutePath(settings.toolWorkingDirectory.toNativeString)]]);
  242. }
  243.  
  244. return cached;
  245. }
  246.  
  247. private bool performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config,
  248. string build_id, in Package[] packages, in NativePath[] additional_dep_files, out NativePath target_binary_path)
  249. {
  250. NativePath target_path;
  251. if (settings.tempBuild) {
  252. string packageName = pack.basePackage is null ? pack.name : pack.basePackage.name;
  253. m_tempTargetExecutablePath = target_path = getTempDir() ~ format(".dub/build/%s-%s/%s/", packageName, pack.version_, build_id);
  254. }
  255. else
  256. target_path = packageCache(settings.cache, pack) ~ "build/" ~ build_id;
  257.  
  258. if (!settings.force && isUpToDate(target_path, buildsettings, settings, pack, packages, additional_dep_files)) {
  259. logInfo("Up-to-date", Color.green, "%s %s: target for configuration [%s] is up to date.",
  260. pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  261. logDiagnostic("Using existing build in %s.", target_path.toNativeString());
  262. target_binary_path = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform);
  263. if (!settings.tempBuild)
  264. copyTargetFile(target_path, buildsettings, settings);
  265. return true;
  266. }
  267.  
  268. if (!isWritableDir(target_path, true)) {
  269. if (!settings.tempBuild)
  270. logInfo("Build directory %s is not writable. Falling back to direct build in the system's temp folder.", target_path);
  271. performDirectBuild(settings, buildsettings, pack, config, target_path);
  272. return false;
  273. }
  274.  
  275. logInfo("Building", Color.light_green, "%s %s: building configuration [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  276.  
  277. if( buildsettings.preBuildCommands.length ){
  278. logInfo("Pre-build", Color.light_green, "Running commands");
  279. runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings);
  280. }
  281.  
  282. // override target path
  283. auto cbuildsettings = buildsettings;
  284. cbuildsettings.targetPath = target_path.toNativeString();
  285. buildWithCompiler(settings, cbuildsettings);
  286. target_binary_path = getTargetPath(cbuildsettings, settings);
  287.  
  288. if (!settings.tempBuild)
  289. copyTargetFile(target_path, buildsettings, settings);
  290.  
  291. return false;
  292. }
  293.  
  294. private void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path)
  295. {
  296. auto cwd = settings.toolWorkingDirectory;
  297. //Added check for existence of [AppNameInPackagejson].d
  298. //If exists, use that as the starting file.
  299. NativePath mainsrc;
  300. if (buildsettings.mainSourceFile.length) {
  301. mainsrc = NativePath(buildsettings.mainSourceFile);
  302. if (!mainsrc.absolute) mainsrc = pack.path ~ mainsrc;
  303. } else {
  304. mainsrc = getMainSourceFile(pack);
  305. logWarn(`Package has no "mainSourceFile" defined. Using best guess: %s`, mainsrc.relativeTo(pack.path).toNativeString());
  306. }
  307.  
  308. // do not pass all source files to RDMD, only the main source file
  309. buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(s => !s.endsWith(".d"))().array();
  310. settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine);
  311.  
  312. auto generate_binary = !buildsettings.dflags.canFind("-o-");
  313.  
  314. // Create start script, which will be used by the calling bash/cmd script.
  315. // build "rdmd --force %DFLAGS% -I%~dp0..\source -Jviews -Isource @deps.txt %LIBS% source\app.d" ~ application arguments
  316. // or with "/" instead of "\"
  317. bool tmp_target = false;
  318. if (generate_binary) {
  319. if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) {
  320. import std.random;
  321. auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-";
  322. auto tmpdir = getTempDir()~".rdmd/source/";
  323. buildsettings.targetPath = tmpdir.toNativeString();
  324. buildsettings.targetName = rnd ~ buildsettings.targetName;
  325. m_temporaryFiles ~= tmpdir;
  326. tmp_target = true;
  327. }
  328. target_path = getTargetPath(buildsettings, settings);
  329. settings.compiler.setTarget(buildsettings, settings.platform);
  330. }
  331.  
  332. logDiagnostic("Application output name is '%s'", settings.compiler.getTargetFileName(buildsettings, settings.platform));
  333.  
  334. string[] flags = ["--build-only", "--compiler="~settings.platform.compilerBinary];
  335. if (settings.force) flags ~= "--force";
  336. flags ~= buildsettings.dflags;
  337. flags ~= mainsrc.relativeTo(cwd).toNativeString();
  338.  
  339. if (buildsettings.preBuildCommands.length){
  340. logInfo("Pre-build", Color.light_green, "Running commands");
  341. runCommands(buildsettings.preBuildCommands, null, cwd.toNativeString());
  342. }
  343.  
  344. logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  345.  
  346. logInfo("Running rdmd...");
  347. logDiagnostic("rdmd %s", join(flags, " "));
  348. auto rdmd_pid = spawnProcess("rdmd" ~ flags, null, Config.none, cwd.toNativeString());
  349. auto result = rdmd_pid.wait();
  350. enforce(result == 0, "Build command failed with exit code "~to!string(result));
  351.  
  352. if (tmp_target) {
  353. m_temporaryFiles ~= target_path;
  354. foreach (f; buildsettings.copyFiles)
  355. m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head;
  356. }
  357. }
  358.  
  359. private void performDirectBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path)
  360. {
  361. auto cwd = settings.toolWorkingDirectory;
  362. auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly);
  363.  
  364. // make file paths relative to shrink the command line
  365. foreach (ref f; buildsettings.sourceFiles) {
  366. auto fp = NativePath(f);
  367. if( fp.absolute ) fp = fp.relativeTo(cwd);
  368. f = fp.toNativeString();
  369. }
  370.  
  371. logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  372.  
  373. // make all target/import paths relative
  374. string makeRelative(string path) {
  375. auto p = NativePath(path);
  376. // storing in a separate temprary to work around #601
  377. auto prel = p.absolute ? p.relativeTo(cwd) : p;
  378. return prel.toNativeString();
  379. }
  380. buildsettings.targetPath = makeRelative(buildsettings.targetPath);
  381. foreach (ref p; buildsettings.importPaths) p = makeRelative(p);
  382. foreach (ref p; buildsettings.cImportPaths) p = makeRelative(p);
  383. foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p);
  384.  
  385. bool is_temp_target = false;
  386. if (generate_binary) {
  387. if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) {
  388. import std.random;
  389. auto rnd = to!string(uniform(uint.min, uint.max));
  390. auto tmppath = getTempDir()~("dub/"~rnd~"/");
  391. buildsettings.targetPath = tmppath.toNativeString();
  392. m_temporaryFiles ~= tmppath;
  393. is_temp_target = true;
  394. }
  395. target_path = getTargetPath(buildsettings, settings);
  396. }
  397.  
  398. if( buildsettings.preBuildCommands.length ){
  399. logInfo("Pre-build", Color.light_green, "Running commands");
  400. runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings);
  401. }
  402.  
  403. buildWithCompiler(settings, buildsettings);
  404.  
  405. if (is_temp_target) {
  406. m_temporaryFiles ~= target_path;
  407. foreach (f; buildsettings.copyFiles)
  408. m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head;
  409. }
  410. }
  411.  
  412. private void copyTargetFile(in NativePath build_path, in BuildSettings buildsettings, in GeneratorSettings settings)
  413. {
  414. ensureDirectory(NativePath(buildsettings.targetPath));
  415.  
  416. string[] filenames = [
  417. settings.compiler.getTargetFileName(buildsettings, settings.platform)
  418. ];
  419.  
  420. // Windows: add .pdb (for executables and DLLs) and/or import .lib & .exp (for DLLs) if found
  421. if (settings.platform.isWindows()) {
  422. void addIfFound(string extension) {
  423. import std.path : setExtension;
  424. const candidate = filenames[0].setExtension(extension);
  425. if (existsFile(build_path ~ candidate))
  426. filenames ~= candidate;
  427. }
  428.  
  429. const tt = buildsettings.targetType;
  430. if (tt == TargetType.executable || tt == TargetType.dynamicLibrary)
  431. addIfFound(".pdb");
  432.  
  433. if (tt == TargetType.dynamicLibrary) {
  434. addIfFound(".lib");
  435. addIfFound(".exp");
  436. }
  437. }
  438.  
  439. foreach (filename; filenames)
  440. {
  441. auto src = build_path ~ filename;
  442. logDiagnostic("Copying target from %s to %s", src.toNativeString(), buildsettings.targetPath);
  443. hardLinkFile(src, NativePath(buildsettings.targetPath) ~ filename, true);
  444. }
  445. }
  446.  
  447. private bool isUpToDate(NativePath target_path, BuildSettings buildsettings, GeneratorSettings settings, in Package main_pack, in Package[] packages, in NativePath[] additional_dep_files)
  448. {
  449. import std.datetime;
  450.  
  451. auto targetfile = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform);
  452. if (!existsFile(targetfile)) {
  453. logDiagnostic("Target '%s' doesn't exist, need rebuild.", targetfile.toNativeString());
  454. return false;
  455. }
  456. auto targettime = getFileInfo(targetfile).timeModified;
  457.  
  458. auto allfiles = appender!(string[]);
  459. allfiles ~= buildsettings.sourceFiles;
  460. allfiles ~= buildsettings.importFiles;
  461. allfiles ~= buildsettings.stringImportFiles;
  462. allfiles ~= buildsettings.extraDependencyFiles;
  463. // TODO: add library files
  464. foreach (p; packages)
  465. allfiles ~= (p.recipePath != NativePath.init ? p : p.basePackage).recipePath.toNativeString();
  466. foreach (f; additional_dep_files) allfiles ~= f.toNativeString();
  467. bool checkSelectedVersions = !settings.single;
  468. if (checkSelectedVersions && main_pack is m_project.rootPackage && m_project.rootPackage.getAllDependencies().length > 0)
  469. allfiles ~= (main_pack.path ~ SelectedVersions.defaultFile).toNativeString();
  470.  
  471. foreach (file; allfiles.data) {
  472. if (!existsFile(file)) {
  473. logDiagnostic("File %s doesn't exist, triggering rebuild.", file);
  474. return false;
  475. }
  476. auto ftime = getFileInfo(file).timeModified;
  477. if (ftime > Clock.currTime)
  478. logWarn("File '%s' was modified in the future. Please re-save.", file);
  479. if (ftime > targettime) {
  480. logDiagnostic("File '%s' modified, need rebuild.", file);
  481. return false;
  482. }
  483. }
  484. return true;
  485. }
  486.  
  487. /// Output an unique name to represent the source file.
  488. /// Calls with path that resolve to the same file on the filesystem will return the same,
  489. /// unless they include different symbolic links (which are not resolved).
  490. deprecated("Use the overload taking in the current working directory")
  491. static string pathToObjName(const scope ref BuildPlatform platform, string path)
  492. {
  493. return pathToObjName(platform, path, getWorkingDirectory);
  494. }
  495.  
  496. /// ditto
  497. static string pathToObjName(const scope ref BuildPlatform platform, string path, NativePath cwd)
  498. {
  499. import std.digest.crc : crc32Of;
  500. import std.path : buildNormalizedPath, dirSeparator, relativePath, stripDrive;
  501. if (path.endsWith(".d")) path = path[0 .. $-2];
  502. auto ret = buildNormalizedPath(cwd.toNativeString(), path).replace(dirSeparator, ".");
  503. auto idx = ret.lastIndexOf('.');
  504. const objSuffix = getObjSuffix(platform);
  505. return idx < 0 ? ret ~ objSuffix : format("%s_%(%02x%)%s", ret[idx+1 .. $], crc32Of(ret[0 .. idx]), objSuffix);
  506. }
  507.  
  508. /// Compile a single source file (srcFile), and write the object to objName.
  509. static string compileUnit(string srcFile, string objName, BuildSettings bs, GeneratorSettings gs) {
  510. NativePath tempobj = NativePath(bs.targetPath)~objName;
  511. string objPath = tempobj.toNativeString();
  512. bs.libs = null;
  513. bs.lflags = null;
  514. bs.sourceFiles = [ srcFile ];
  515. bs.targetType = TargetType.object;
  516. gs.compiler.prepareBuildSettings(bs, gs.platform, BuildSetting.commandLine);
  517. gs.compiler.setTarget(bs, gs.platform, objPath);
  518. gs.compiler.invoke(bs, gs.platform, gs.compileCallback, gs.toolWorkingDirectory);
  519. return objPath;
  520. }
  521.  
  522. private void buildWithCompiler(GeneratorSettings settings, BuildSettings buildsettings)
  523. {
  524. auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly);
  525. auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library;
  526.  
  527. scope (failure) {
  528. logDiagnostic("FAIL %s %s %s" , buildsettings.targetPath, buildsettings.targetName, buildsettings.targetType);
  529. auto tpath = getTargetPath(buildsettings, settings);
  530. if (generate_binary && existsFile(tpath))
  531. removeFile(tpath);
  532. }
  533. if (settings.buildMode == BuildMode.singleFile && generate_binary) {
  534. import std.parallelism, std.range : walkLength;
  535.  
  536. auto lbuildsettings = buildsettings;
  537. auto srcs = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f));
  538. auto objs = new string[](srcs.walkLength);
  539.  
  540. void compileSource(size_t i, string src) {
  541. logInfo("Compiling", Color.light_green, "%s", src);
  542. const objPath = pathToObjName(settings.platform, src, settings.toolWorkingDirectory);
  543. objs[i] = compileUnit(src, objPath, buildsettings, settings);
  544. }
  545.  
  546. if (settings.parallelBuild) {
  547. foreach (i, src; srcs.parallel(1)) compileSource(i, src);
  548. } else {
  549. foreach (i, src; srcs.array) compileSource(i, src);
  550. }
  551.  
  552. logInfo("Linking", Color.light_green, "%s", buildsettings.targetName.color(Mode.bold));
  553. lbuildsettings.sourceFiles = is_static_library ? [] : lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array;
  554. settings.compiler.setTarget(lbuildsettings, settings.platform);
  555. settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles);
  556. settings.compiler.invokeLinker(lbuildsettings, settings.platform, objs, settings.linkCallback, settings.toolWorkingDirectory);
  557.  
  558. // NOTE: separate compile/link is not yet enabled for GDC.
  559. } else if (generate_binary && (settings.buildMode == BuildMode.allAtOnce || settings.compiler.name == "gdc" || is_static_library)) {
  560. // don't include symbols of dependencies (will be included by the top level target)
  561. if (is_static_library) buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)).array;
  562.  
  563. // setup for command line
  564. settings.compiler.setTarget(buildsettings, settings.platform);
  565. settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine);
  566.  
  567. // invoke the compiler
  568. settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback, settings.toolWorkingDirectory);
  569. } else {
  570. // determine path for the temporary object file
  571. string tempobjname = buildsettings.targetName ~ getObjSuffix(settings.platform);
  572. NativePath tempobj = NativePath(buildsettings.targetPath) ~ tempobjname;
  573.  
  574. // setup linker command line
  575. auto lbuildsettings = buildsettings;
  576. lbuildsettings.sourceFiles = lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array;
  577. if (generate_binary) settings.compiler.setTarget(lbuildsettings, settings.platform);
  578. settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles);
  579.  
  580. // setup compiler command line
  581. buildsettings.libs = null;
  582. buildsettings.lflags = null;
  583. if (generate_binary) buildsettings.addDFlags("-c", "-of"~tempobj.toNativeString());
  584. buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)).array;
  585.  
  586. settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine);
  587.  
  588. settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback, settings.toolWorkingDirectory);
  589.  
  590. if (generate_binary) {
  591. if (settings.tempBuild) {
  592. logInfo("Linking", Color.light_green, "%s => %s", buildsettings.targetName.color(Mode.bold), buildsettings.getTargetPath(settings));
  593. } else {
  594. logInfo("Linking", Color.light_green, "%s", buildsettings.targetName.color(Mode.bold));
  595. }
  596. settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()], settings.linkCallback, settings.toolWorkingDirectory);
  597. }
  598. }
  599. }
  600.  
  601. private void runTarget(NativePath exe_file_path, in BuildSettings buildsettings, string[] run_args, GeneratorSettings settings)
  602. {
  603. if (buildsettings.targetType == TargetType.executable) {
  604. auto cwd = settings.toolWorkingDirectory;
  605. auto runcwd = cwd;
  606. if (buildsettings.workingDirectory.length) {
  607. runcwd = NativePath(buildsettings.workingDirectory);
  608. if (!runcwd.absolute) runcwd = cwd ~ runcwd;
  609. }
  610. if (!exe_file_path.absolute) exe_file_path = cwd ~ exe_file_path;
  611. runPreRunCommands(m_project.rootPackage, m_project, settings, buildsettings);
  612. logInfo("Running", Color.green, "%s %s", exe_file_path.relativeTo(runcwd), run_args.join(" "));
  613. string[string] env;
  614. foreach (aa; [buildsettings.environments, buildsettings.runEnvironments])
  615. foreach (k, v; aa)
  616. env[k] = v;
  617. if (settings.runCallback) {
  618. auto res = execute([ exe_file_path.toNativeString() ] ~ run_args,
  619. env, Config.none, size_t.max, runcwd.toNativeString());
  620. settings.runCallback(res.status, res.output);
  621. settings.targetExitStatus = res.status;
  622. runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings);
  623. } else {
  624. auto prg_pid = spawnProcess([ exe_file_path.toNativeString() ] ~ run_args,
  625. env, Config.none, runcwd.toNativeString());
  626. auto result = prg_pid.wait();
  627. settings.targetExitStatus = result;
  628. runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings);
  629. enforce(result == 0, "Program exited with code "~to!string(result));
  630. }
  631. } else
  632. enforce(false, "Target is a library. Skipping execution.");
  633. }
  634.  
  635. private void runPreRunCommands(in Package pack, in Project proj, in GeneratorSettings settings,
  636. in BuildSettings buildsettings)
  637. {
  638. if (buildsettings.preRunCommands.length) {
  639. logInfo("Pre-run", Color.light_green, "Running commands...");
  640. runBuildCommands(CommandType.preRun, buildsettings.preRunCommands, pack, proj, settings, buildsettings);
  641. }
  642. }
  643.  
  644. private void runPostRunCommands(in Package pack, in Project proj, in GeneratorSettings settings,
  645. in BuildSettings buildsettings)
  646. {
  647. if (buildsettings.postRunCommands.length) {
  648. logInfo("Post-run", Color.light_green, "Running commands...");
  649. runBuildCommands(CommandType.postRun, buildsettings.postRunCommands, pack, proj, settings, buildsettings);
  650. }
  651. }
  652.  
  653. private void cleanupTemporaries()
  654. {
  655. foreach_reverse (f; m_temporaryFiles) {
  656. try {
  657. if (f.endsWithSlash) rmdir(f.toNativeString());
  658. else remove(f.toNativeString());
  659. } catch (Exception e) {
  660. logWarn("Failed to remove temporary file '%s': %s", f.toNativeString(), e.msg);
  661. logDiagnostic("Full error: %s", e.toString().sanitize);
  662. }
  663. }
  664. m_temporaryFiles = null;
  665. }
  666. }
  667.  
  668. /**
  669. * Provides a unique (per build) identifier
  670. *
  671. * When building a package, it is important to have a unique but stable
  672. * identifier to differentiate builds and allow their caching.
  673. * This function provides such an identifier.
  674. * Example:
  675. * ```
  676. * application-debug-linux.posix-x86_64-dmd_v2.100.2-D80285212AEC1FF9855F18AD52C68B9EEB5C7690609C224575F920096FB1965B
  677. * ```
  678. */
  679. private string computeBuildID(in BuildSettings buildsettings, string config, GeneratorSettings settings)
  680. {
  681. const(string[])[] hashing = [
  682. buildsettings.versions,
  683. buildsettings.debugVersions,
  684. buildsettings.dflags,
  685. buildsettings.lflags,
  686. buildsettings.stringImportPaths,
  687. buildsettings.importPaths,
  688. buildsettings.cImportPaths,
  689. settings.platform.architecture,
  690. [
  691. (cast(uint)(buildsettings.options & ~BuildOption.color)).to!string, // exclude color option from id
  692. settings.platform.compilerBinary,
  693. settings.platform.compiler,
  694. settings.platform.compilerVersion,
  695. ],
  696. ];
  697.  
  698. return computeBuildName(config, settings, hashing);
  699. }
  700.  
  701. private NativePath getMainSourceFile(in Package prj)
  702. {
  703. foreach (f; ["source/app.d", "src/app.d", "source/"~prj.name~".d", "src/"~prj.name~".d"])
  704. if (existsFile(prj.path ~ f))
  705. return prj.path ~ f;
  706. return prj.path ~ "source/app.d";
  707. }
  708.  
  709. private NativePath getTargetPath(const scope ref BuildSettings bs, const scope ref GeneratorSettings settings)
  710. {
  711. return NativePath(bs.targetPath) ~ settings.compiler.getTargetFileName(bs, settings.platform);
  712. }
  713.  
  714. private string shrinkPath(NativePath path, NativePath base)
  715. {
  716. auto orig = path.toNativeString();
  717. if (!path.absolute) return orig;
  718. version (Windows)
  719. {
  720. // avoid relative paths starting with `..\`: https://github.com/dlang/dub/issues/2143
  721. if (!path.startsWith(base)) return orig;
  722. }
  723. auto rel = path.relativeTo(base).toNativeString();
  724. return rel.length < orig.length ? rel : orig;
  725. }
  726.  
  727. unittest {
  728. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo")) == NativePath("bar/baz").toNativeString());
  729. version (Windows)
  730. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("/foo/bar/baz").toNativeString());
  731. else
  732. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("../bar/baz").toNativeString());
  733. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/")) == NativePath("/foo/bar/baz").toNativeString());
  734. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/baz")) == NativePath("/foo/bar/baz").toNativeString());
  735. }
  736.  
  737. unittest { // issue #1235 - pass no library files to compiler command line when building a static lib
  738. import dub.internal.vibecompat.data.json : parseJsonString;
  739. import dub.compilers.gdc : GDCCompiler;
  740. import dub.platform : determinePlatform;
  741.  
  742. version (Windows) auto libfile = "bar.lib";
  743. else auto libfile = "bar.a";
  744.  
  745. auto desc = parseJsonString(`{"name": "test", "targetType": "library", "sourceFiles": ["foo.d", "`~libfile~`"]}`);
  746. auto pack = new Package(desc, NativePath("/tmp/fooproject"));
  747. auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false);
  748. auto prj = new Project(pman, pack);
  749.  
  750. final static class TestCompiler : GDCCompiler {
  751. override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd) {
  752. assert(!settings.dflags[].any!(f => f.canFind("bar")));
  753. }
  754. override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd) {
  755. assert(false);
  756. }
  757. }
  758.  
  759. GeneratorSettings settings;
  760. settings.platform = BuildPlatform(determinePlatform(), ["x86"], "gdc", "test", 2075);
  761. settings.compiler = new TestCompiler;
  762. settings.config = "library";
  763. settings.buildType = "debug";
  764. settings.tempBuild = true;
  765.  
  766. auto gen = new BuildGenerator(prj);
  767. gen.generate(settings);
  768. }