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, 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 = NativePath(getcwd());
  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 ? "" : target_path.parentPath.toNativeString.absolutePath]]);
  237. }
  238.  
  239. return cached;
  240. }
  241.  
  242. private bool performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config,
  243. string build_id, in Package[] packages, in NativePath[] additional_dep_files, out NativePath target_binary_path)
  244. {
  245. auto cwd = NativePath(getcwd());
  246.  
  247. NativePath target_path;
  248. if (settings.tempBuild) {
  249. string packageName = pack.basePackage is null ? pack.name : pack.basePackage.name;
  250. m_tempTargetExecutablePath = target_path = getTempDir() ~ format(".dub/build/%s-%s/%s/", packageName, pack.version_, build_id);
  251. }
  252. else
  253. target_path = packageCache(settings.cache, pack) ~ "build/" ~ build_id;
  254.  
  255. if (!settings.force && isUpToDate(target_path, buildsettings, settings, pack, packages, additional_dep_files)) {
  256. logInfo("Up-to-date", Color.green, "%s %s: target for configuration [%s] is up to date.",
  257. pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  258. logDiagnostic("Using existing build in %s.", target_path.toNativeString());
  259. target_binary_path = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform);
  260. if (!settings.tempBuild)
  261. copyTargetFile(target_path, buildsettings, settings);
  262. return true;
  263. }
  264.  
  265. if (!isWritableDir(target_path, true)) {
  266. if (!settings.tempBuild)
  267. logInfo("Build directory %s is not writable. Falling back to direct build in the system's temp folder.", target_path.relativeTo(cwd).toNativeString());
  268. performDirectBuild(settings, buildsettings, pack, config, target_path);
  269. return false;
  270. }
  271.  
  272. logInfo("Building", Color.light_green, "%s %s: building configuration [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  273.  
  274. if( buildsettings.preBuildCommands.length ){
  275. logInfo("Pre-build", Color.light_green, "Running commands");
  276. runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings);
  277. }
  278.  
  279. // override target path
  280. auto cbuildsettings = buildsettings;
  281. cbuildsettings.targetPath = shrinkPath(target_path, cwd);
  282. buildWithCompiler(settings, cbuildsettings);
  283. target_binary_path = getTargetPath(cbuildsettings, settings);
  284.  
  285. if (!settings.tempBuild)
  286. copyTargetFile(target_path, buildsettings, settings);
  287.  
  288. return false;
  289. }
  290.  
  291. private void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path)
  292. {
  293. auto cwd = NativePath(getcwd());
  294. //Added check for existence of [AppNameInPackagejson].d
  295. //If exists, use that as the starting file.
  296. NativePath mainsrc;
  297. if (buildsettings.mainSourceFile.length) {
  298. mainsrc = NativePath(buildsettings.mainSourceFile);
  299. if (!mainsrc.absolute) mainsrc = pack.path ~ mainsrc;
  300. } else {
  301. mainsrc = getMainSourceFile(pack);
  302. logWarn(`Package has no "mainSourceFile" defined. Using best guess: %s`, mainsrc.relativeTo(pack.path).toNativeString());
  303. }
  304.  
  305. // do not pass all source files to RDMD, only the main source file
  306. buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(s => !s.endsWith(".d"))().array();
  307. settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine);
  308.  
  309. auto generate_binary = !buildsettings.dflags.canFind("-o-");
  310.  
  311. // Create start script, which will be used by the calling bash/cmd script.
  312. // build "rdmd --force %DFLAGS% -I%~dp0..\source -Jviews -Isource @deps.txt %LIBS% source\app.d" ~ application arguments
  313. // or with "/" instead of "\"
  314. bool tmp_target = false;
  315. if (generate_binary) {
  316. if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) {
  317. import std.random;
  318. auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-";
  319. auto tmpdir = getTempDir()~".rdmd/source/";
  320. buildsettings.targetPath = tmpdir.toNativeString();
  321. buildsettings.targetName = rnd ~ buildsettings.targetName;
  322. m_temporaryFiles ~= tmpdir;
  323. tmp_target = true;
  324. }
  325. target_path = getTargetPath(buildsettings, settings);
  326. settings.compiler.setTarget(buildsettings, settings.platform);
  327. }
  328.  
  329. logDiagnostic("Application output name is '%s'", settings.compiler.getTargetFileName(buildsettings, settings.platform));
  330.  
  331. string[] flags = ["--build-only", "--compiler="~settings.platform.compilerBinary];
  332. if (settings.force) flags ~= "--force";
  333. flags ~= buildsettings.dflags;
  334. flags ~= mainsrc.relativeTo(cwd).toNativeString();
  335.  
  336. if (buildsettings.preBuildCommands.length){
  337. logInfo("Pre-build", Color.light_green, "Running commands");
  338. runCommands(buildsettings.preBuildCommands);
  339. }
  340.  
  341. logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  342.  
  343. logInfo("Running rdmd...");
  344. logDiagnostic("rdmd %s", join(flags, " "));
  345. auto rdmd_pid = spawnProcess("rdmd" ~ flags);
  346. auto result = rdmd_pid.wait();
  347. enforce(result == 0, "Build command failed with exit code "~to!string(result));
  348.  
  349. if (tmp_target) {
  350. m_temporaryFiles ~= target_path;
  351. foreach (f; buildsettings.copyFiles)
  352. m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head;
  353. }
  354. }
  355.  
  356. private void performDirectBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path)
  357. {
  358. auto cwd = NativePath(getcwd());
  359. auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly);
  360.  
  361. // make file paths relative to shrink the command line
  362. foreach (ref f; buildsettings.sourceFiles) {
  363. auto fp = NativePath(f);
  364. if( fp.absolute ) fp = fp.relativeTo(cwd);
  365. f = fp.toNativeString();
  366. }
  367.  
  368. logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  369.  
  370. // make all target/import paths relative
  371. string makeRelative(string path) {
  372. auto p = NativePath(path);
  373. // storing in a separate temprary to work around #601
  374. auto prel = p.absolute ? p.relativeTo(cwd) : p;
  375. return prel.toNativeString();
  376. }
  377. buildsettings.targetPath = makeRelative(buildsettings.targetPath);
  378. foreach (ref p; buildsettings.importPaths) p = makeRelative(p);
  379. foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p);
  380.  
  381. bool is_temp_target = false;
  382. if (generate_binary) {
  383. if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) {
  384. import std.random;
  385. auto rnd = to!string(uniform(uint.min, uint.max));
  386. auto tmppath = getTempDir()~("dub/"~rnd~"/");
  387. buildsettings.targetPath = tmppath.toNativeString();
  388. m_temporaryFiles ~= tmppath;
  389. is_temp_target = true;
  390. }
  391. target_path = getTargetPath(buildsettings, settings);
  392. }
  393.  
  394. if( buildsettings.preBuildCommands.length ){
  395. logInfo("Pre-build", Color.light_green, "Running commands");
  396. runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings);
  397. }
  398.  
  399. buildWithCompiler(settings, buildsettings);
  400.  
  401. if (is_temp_target) {
  402. m_temporaryFiles ~= target_path;
  403. foreach (f; buildsettings.copyFiles)
  404. m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head;
  405. }
  406. }
  407.  
  408. private void copyTargetFile(in NativePath build_path, in BuildSettings buildsettings, in GeneratorSettings settings)
  409. {
  410. ensureDirectory(NativePath(buildsettings.targetPath));
  411.  
  412. string[] filenames = [
  413. settings.compiler.getTargetFileName(buildsettings, settings.platform)
  414. ];
  415.  
  416. // Windows: add .pdb (for executables and DLLs) and/or import .lib & .exp (for DLLs) if found
  417. if (settings.platform.isWindows()) {
  418. void addIfFound(string extension) {
  419. import std.path : setExtension;
  420. const candidate = filenames[0].setExtension(extension);
  421. if (existsFile(build_path ~ candidate))
  422. filenames ~= candidate;
  423. }
  424.  
  425. const tt = buildsettings.targetType;
  426. if (tt == TargetType.executable || tt == TargetType.dynamicLibrary)
  427. addIfFound(".pdb");
  428.  
  429. if (tt == TargetType.dynamicLibrary) {
  430. addIfFound(".lib");
  431. addIfFound(".exp");
  432. }
  433. }
  434.  
  435. foreach (filename; filenames)
  436. {
  437. auto src = build_path ~ filename;
  438. logDiagnostic("Copying target from %s to %s", src.toNativeString(), buildsettings.targetPath);
  439. hardLinkFile(src, NativePath(buildsettings.targetPath) ~ filename, true);
  440. }
  441. }
  442.  
  443. private bool isUpToDate(NativePath target_path, BuildSettings buildsettings, GeneratorSettings settings, in Package main_pack, in Package[] packages, in NativePath[] additional_dep_files)
  444. {
  445. import std.datetime;
  446.  
  447. auto targetfile = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform);
  448. if (!existsFile(targetfile)) {
  449. logDiagnostic("Target '%s' doesn't exist, need rebuild.", targetfile.toNativeString());
  450. return false;
  451. }
  452. auto targettime = getFileInfo(targetfile).timeModified;
  453.  
  454. auto allfiles = appender!(string[]);
  455. allfiles ~= buildsettings.sourceFiles;
  456. allfiles ~= buildsettings.importFiles;
  457. allfiles ~= buildsettings.stringImportFiles;
  458. allfiles ~= buildsettings.extraDependencyFiles;
  459. // TODO: add library files
  460. foreach (p; packages)
  461. allfiles ~= (p.recipePath != NativePath.init ? p : p.basePackage).recipePath.toNativeString();
  462. foreach (f; additional_dep_files) allfiles ~= f.toNativeString();
  463. bool checkSelectedVersions = !settings.single;
  464. if (checkSelectedVersions && main_pack is m_project.rootPackage && m_project.rootPackage.getAllDependencies().length > 0)
  465. allfiles ~= (main_pack.path ~ SelectedVersions.defaultFile).toNativeString();
  466.  
  467. foreach (file; allfiles.data) {
  468. if (!existsFile(file)) {
  469. logDiagnostic("File %s doesn't exist, triggering rebuild.", file);
  470. return false;
  471. }
  472. auto ftime = getFileInfo(file).timeModified;
  473. if (ftime > Clock.currTime)
  474. logWarn("File '%s' was modified in the future. Please re-save.", file);
  475. if (ftime > targettime) {
  476. logDiagnostic("File '%s' modified, need rebuild.", file);
  477. return false;
  478. }
  479. }
  480. return true;
  481. }
  482.  
  483. /// Output an unique name to represent the source file.
  484. /// Calls with path that resolve to the same file on the filesystem will return the same,
  485. /// unless they include different symbolic links (which are not resolved).
  486.  
  487. static string pathToObjName(const scope ref BuildPlatform platform, string path)
  488. {
  489. import std.digest.crc : crc32Of;
  490. import std.path : buildNormalizedPath, dirSeparator, relativePath, stripDrive;
  491. if (path.endsWith(".d")) path = path[0 .. $-2];
  492. auto ret = buildNormalizedPath(getcwd(), path).replace(dirSeparator, ".");
  493. auto idx = ret.lastIndexOf('.');
  494. const objSuffix = getObjSuffix(platform);
  495. return idx < 0 ? ret ~ objSuffix : format("%s_%(%02x%)%s", ret[idx+1 .. $], crc32Of(ret[0 .. idx]), objSuffix);
  496. }
  497.  
  498. /// Compile a single source file (srcFile), and write the object to objName.
  499. static string compileUnit(string srcFile, string objName, BuildSettings bs, GeneratorSettings gs) {
  500. NativePath tempobj = NativePath(bs.targetPath)~objName;
  501. string objPath = tempobj.toNativeString();
  502. bs.libs = null;
  503. bs.lflags = null;
  504. bs.sourceFiles = [ srcFile ];
  505. bs.targetType = TargetType.object;
  506. gs.compiler.prepareBuildSettings(bs, gs.platform, BuildSetting.commandLine);
  507. gs.compiler.setTarget(bs, gs.platform, objPath);
  508. gs.compiler.invoke(bs, gs.platform, gs.compileCallback);
  509. return objPath;
  510. }
  511.  
  512. private void buildWithCompiler(GeneratorSettings settings, BuildSettings buildsettings)
  513. {
  514. auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly);
  515. auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library;
  516.  
  517. scope (failure) {
  518. logDiagnostic("FAIL %s %s %s" , buildsettings.targetPath, buildsettings.targetName, buildsettings.targetType);
  519. auto tpath = getTargetPath(buildsettings, settings);
  520. if (generate_binary && existsFile(tpath))
  521. removeFile(tpath);
  522. }
  523. if (settings.buildMode == BuildMode.singleFile && generate_binary) {
  524. import std.parallelism, std.range : walkLength;
  525.  
  526. auto lbuildsettings = buildsettings;
  527. auto srcs = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f));
  528. auto objs = new string[](srcs.walkLength);
  529.  
  530. void compileSource(size_t i, string src) {
  531. logInfo("Compiling", Color.light_green, "%s", src);
  532. const objPath = pathToObjName(settings.platform, src);
  533. objs[i] = compileUnit(src, objPath, buildsettings, settings);
  534. }
  535.  
  536. if (settings.parallelBuild) {
  537. foreach (i, src; srcs.parallel(1)) compileSource(i, src);
  538. } else {
  539. foreach (i, src; srcs.array) compileSource(i, src);
  540. }
  541.  
  542. logInfo("Linking", Color.light_green, "%s", buildsettings.targetName.color(Mode.bold));
  543. lbuildsettings.sourceFiles = is_static_library ? [] : lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array;
  544. settings.compiler.setTarget(lbuildsettings, settings.platform);
  545. settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles);
  546. settings.compiler.invokeLinker(lbuildsettings, settings.platform, objs, settings.linkCallback);
  547.  
  548. // NOTE: separate compile/link is not yet enabled for GDC.
  549. } else if (generate_binary && (settings.buildMode == BuildMode.allAtOnce || settings.compiler.name == "gdc" || is_static_library)) {
  550. // don't include symbols of dependencies (will be included by the top level target)
  551. if (is_static_library) buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)).array;
  552.  
  553. // setup for command line
  554. settings.compiler.setTarget(buildsettings, settings.platform);
  555. settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine);
  556.  
  557. // invoke the compiler
  558. settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback);
  559. } else {
  560. // determine path for the temporary object file
  561. string tempobjname = buildsettings.targetName ~ getObjSuffix(settings.platform);
  562. NativePath tempobj = NativePath(buildsettings.targetPath) ~ tempobjname;
  563.  
  564. // setup linker command line
  565. auto lbuildsettings = buildsettings;
  566. lbuildsettings.sourceFiles = lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array;
  567. if (generate_binary) settings.compiler.setTarget(lbuildsettings, settings.platform);
  568. settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles);
  569.  
  570. // setup compiler command line
  571. buildsettings.libs = null;
  572. buildsettings.lflags = null;
  573. if (generate_binary) buildsettings.addDFlags("-c", "-of"~tempobj.toNativeString());
  574. buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)).array;
  575.  
  576. settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine);
  577.  
  578. settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback);
  579.  
  580. if (generate_binary) {
  581. if (settings.tempBuild) {
  582. logInfo("Linking", Color.light_green, "%s => %s", buildsettings.targetName.color(Mode.bold), buildsettings.getTargetPath(settings));
  583. } else {
  584. logInfo("Linking", Color.light_green, "%s", buildsettings.targetName.color(Mode.bold));
  585. }
  586. settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()], settings.linkCallback);
  587. }
  588. }
  589. }
  590.  
  591. private void runTarget(NativePath exe_file_path, in BuildSettings buildsettings, string[] run_args, GeneratorSettings settings)
  592. {
  593. if (buildsettings.targetType == TargetType.executable) {
  594. auto cwd = NativePath(getcwd());
  595. auto runcwd = cwd;
  596. if (buildsettings.workingDirectory.length) {
  597. runcwd = NativePath(buildsettings.workingDirectory);
  598. if (!runcwd.absolute) runcwd = cwd ~ runcwd;
  599. }
  600. if (!exe_file_path.absolute) exe_file_path = cwd ~ exe_file_path;
  601. runPreRunCommands(m_project.rootPackage, m_project, settings, buildsettings);
  602. logInfo("Running", Color.green, "%s %s", exe_file_path.relativeTo(runcwd), run_args.join(" "));
  603. string[string] env;
  604. foreach (aa; [buildsettings.environments, buildsettings.runEnvironments])
  605. foreach (k, v; aa)
  606. env[k] = v;
  607. if (settings.runCallback) {
  608. auto res = execute([ exe_file_path.toNativeString() ] ~ run_args,
  609. env, Config.none, size_t.max, runcwd.toNativeString());
  610. settings.runCallback(res.status, res.output);
  611. settings.targetExitStatus = res.status;
  612. runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings);
  613. } else {
  614. auto prg_pid = spawnProcess([ exe_file_path.toNativeString() ] ~ run_args,
  615. env, Config.none, runcwd.toNativeString());
  616. auto result = prg_pid.wait();
  617. settings.targetExitStatus = result;
  618. runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings);
  619. enforce(result == 0, "Program exited with code "~to!string(result));
  620. }
  621. } else
  622. enforce(false, "Target is a library. Skipping execution.");
  623. }
  624.  
  625. private void runPreRunCommands(in Package pack, in Project proj, in GeneratorSettings settings,
  626. in BuildSettings buildsettings)
  627. {
  628. if (buildsettings.preRunCommands.length) {
  629. logInfo("Pre-run", Color.light_green, "Running commands...");
  630. runBuildCommands(CommandType.preRun, buildsettings.preRunCommands, pack, proj, settings, buildsettings);
  631. }
  632. }
  633.  
  634. private void runPostRunCommands(in Package pack, in Project proj, in GeneratorSettings settings,
  635. in BuildSettings buildsettings)
  636. {
  637. if (buildsettings.postRunCommands.length) {
  638. logInfo("Post-run", Color.light_green, "Running commands...");
  639. runBuildCommands(CommandType.postRun, buildsettings.postRunCommands, pack, proj, settings, buildsettings);
  640. }
  641. }
  642.  
  643. private void cleanupTemporaries()
  644. {
  645. foreach_reverse (f; m_temporaryFiles) {
  646. try {
  647. if (f.endsWithSlash) rmdir(f.toNativeString());
  648. else remove(f.toNativeString());
  649. } catch (Exception e) {
  650. logWarn("Failed to remove temporary file '%s': %s", f.toNativeString(), e.msg);
  651. logDiagnostic("Full error: %s", e.toString().sanitize);
  652. }
  653. }
  654. m_temporaryFiles = null;
  655. }
  656. }
  657.  
  658. /**
  659. * Provides a unique (per build) identifier
  660. *
  661. * When building a package, it is important to have a unique but stable
  662. * identifier to differentiate builds and allow their caching.
  663. * This function provides such an identifier.
  664. * Example:
  665. * ```
  666. * application-debug-linux.posix-x86_64-dmd_v2.100.2-D80285212AEC1FF9855F18AD52C68B9EEB5C7690609C224575F920096FB1965B
  667. * ```
  668. */
  669. private string computeBuildID(in BuildSettings buildsettings, string config, GeneratorSettings settings)
  670. {
  671. const(string[])[] hashing = [
  672. buildsettings.versions,
  673. buildsettings.debugVersions,
  674. buildsettings.dflags,
  675. buildsettings.lflags,
  676. buildsettings.stringImportPaths,
  677. buildsettings.importPaths,
  678. settings.platform.architecture,
  679. [
  680. (cast(uint)(buildsettings.options & ~BuildOption.color)).to!string, // exclude color option from id
  681. settings.platform.compilerBinary,
  682. settings.platform.compiler,
  683. settings.platform.compilerVersion,
  684. ],
  685. ];
  686.  
  687. return computeBuildName(config, settings, hashing);
  688. }
  689.  
  690. private NativePath getMainSourceFile(in Package prj)
  691. {
  692. foreach (f; ["source/app.d", "src/app.d", "source/"~prj.name~".d", "src/"~prj.name~".d"])
  693. if (existsFile(prj.path ~ f))
  694. return prj.path ~ f;
  695. return prj.path ~ "source/app.d";
  696. }
  697.  
  698. private NativePath getTargetPath(const scope ref BuildSettings bs, const scope ref GeneratorSettings settings)
  699. {
  700. return NativePath(bs.targetPath) ~ settings.compiler.getTargetFileName(bs, settings.platform);
  701. }
  702.  
  703. private string shrinkPath(NativePath path, NativePath base)
  704. {
  705. auto orig = path.toNativeString();
  706. if (!path.absolute) return orig;
  707. version (Windows)
  708. {
  709. // avoid relative paths starting with `..\`: https://github.com/dlang/dub/issues/2143
  710. if (!path.startsWith(base)) return orig;
  711. }
  712. auto rel = path.relativeTo(base).toNativeString();
  713. return rel.length < orig.length ? rel : orig;
  714. }
  715.  
  716. unittest {
  717. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo")) == NativePath("bar/baz").toNativeString());
  718. version (Windows)
  719. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("/foo/bar/baz").toNativeString());
  720. else
  721. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("../bar/baz").toNativeString());
  722. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/")) == NativePath("/foo/bar/baz").toNativeString());
  723. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/baz")) == NativePath("/foo/bar/baz").toNativeString());
  724. }
  725.  
  726. unittest { // issue #1235 - pass no library files to compiler command line when building a static lib
  727. import dub.internal.vibecompat.data.json : parseJsonString;
  728. import dub.compilers.gdc : GDCCompiler;
  729. import dub.platform : determinePlatform;
  730.  
  731. version (Windows) auto libfile = "bar.lib";
  732. else auto libfile = "bar.a";
  733.  
  734. auto desc = parseJsonString(`{"name": "test", "targetType": "library", "sourceFiles": ["foo.d", "`~libfile~`"]}`);
  735. auto pack = new Package(desc, NativePath("/tmp/fooproject"));
  736. auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false);
  737. auto prj = new Project(pman, pack);
  738.  
  739. final static class TestCompiler : GDCCompiler {
  740. override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) {
  741. assert(!settings.dflags[].any!(f => f.canFind("bar")));
  742. }
  743. override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) {
  744. assert(false);
  745. }
  746. }
  747.  
  748. GeneratorSettings settings;
  749. settings.platform = BuildPlatform(determinePlatform(), ["x86"], "gdc", "test", 2075);
  750. settings.compiler = new TestCompiler;
  751. settings.config = "library";
  752. settings.buildType = "debug";
  753. settings.tempBuild = true;
  754.  
  755. auto gen = new BuildGenerator(prj);
  756. gen.generate(settings);
  757. }