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