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