Newer
Older
dub_jkp / source / dub / generators / build.d
@Martin Kinkelin Martin Kinkelin on 17 Sep 2022 29 KB Windows: Copy .lib files from DLL deps only
  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.  
  81. enforce(!(settings.rdmd && root_ti.buildSettings.targetType == TargetType.none),
  82. "Building package with target type \"none\" with rdmd is not supported yet.");
  83.  
  84. logInfo("Starting", Color.light_green,
  85. "Performing \"%s\" build using %s for %-(%s, %).",
  86. settings.buildType.color(Color.magenta), settings.platform.compilerBinary,
  87. settings.platform.architecture);
  88.  
  89. bool any_cached = false;
  90.  
  91. NativePath[string] target_paths;
  92.  
  93. bool[string] visited;
  94. void buildTargetRec(string target)
  95. {
  96. if (target in visited) return;
  97. visited[target] = true;
  98.  
  99. auto ti = targets[target];
  100.  
  101. foreach (dep; ti.dependencies)
  102. buildTargetRec(dep);
  103.  
  104. NativePath[] additional_dep_files, dependencyBinariesToCopy;
  105. auto bs = ti.buildSettings.dup;
  106. const dependeeTT = bs.targetType;
  107. foreach (ldep; ti.linkDependencies) {
  108. const ldepPath = target_paths[ldep].toNativeString();
  109. const doLink = dependeeTT != TargetType.staticLibrary && !(bs.options & BuildOption.syntaxOnly);
  110.  
  111. if (doLink && isLinkerFile(settings.platform, ldepPath))
  112. bs.addSourceFiles(ldepPath);
  113. else
  114. additional_dep_files ~= target_paths[ldep];
  115.  
  116. if (targets[ldep].buildSettings.targetType == TargetType.dynamicLibrary) {
  117. // copy the .{dll,so,dylib} if the dependee is an executable or dynamic lib
  118. if (dependeeTT == TargetType.executable || dependeeTT == TargetType.dynamicLibrary)
  119. dependencyBinariesToCopy ~= NativePath(ldepPath);
  120.  
  121. if (settings.platform.isWindows()) {
  122. if (dependeeTT == TargetType.executable || dependeeTT == TargetType.dynamicLibrary) {
  123. // copy the accompanying .pdb if found
  124. const pdb = ldepPath.setExtension(".pdb");
  125. if (existsFile(pdb))
  126. dependencyBinariesToCopy ~= NativePath(pdb);
  127. }
  128.  
  129. const importLib = ldepPath.setExtension(".lib");
  130. if (existsFile(importLib)) {
  131. // link dependee against the import lib
  132. if (doLink)
  133. bs.addSourceFiles(importLib);
  134. // and copy if the dependee is a DLL too
  135. if (dependeeTT == TargetType.dynamicLibrary)
  136. dependencyBinariesToCopy ~= NativePath(importLib);
  137. }
  138.  
  139. // copy the .exp file if the dependee is a DLL (just like the import lib)
  140. const exp = ldepPath.setExtension(".exp");
  141. if (dependeeTT == TargetType.dynamicLibrary && existsFile(exp))
  142. dependencyBinariesToCopy ~= NativePath(exp);
  143. }
  144. }
  145. }
  146. NativePath tpath;
  147. if (dependeeTT != TargetType.none)
  148. if (buildTarget(settings, bs, ti.pack, ti.config, ti.packages, additional_dep_files, dependencyBinariesToCopy, tpath))
  149. any_cached = true;
  150. target_paths[target] = tpath;
  151. }
  152.  
  153. // build all targets
  154. if (settings.rdmd || root_ti.buildSettings.targetType == TargetType.staticLibrary) {
  155. // RDMD always builds everything at once and static libraries don't need their
  156. // dependencies to be built
  157. NativePath tpath;
  158. buildTarget(settings, root_ti.buildSettings.dup, m_project.rootPackage, root_ti.config, root_ti.packages, null, null, tpath);
  159. } else {
  160. buildTargetRec(m_project.rootPackage.name);
  161.  
  162. if (any_cached) {
  163. logInfo("Finished", Color.green,
  164. "To force a rebuild of up-to-date targets, run again with --force"
  165. );
  166. }
  167. }
  168. }
  169.  
  170. override void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets)
  171. {
  172. // run the generated executable
  173. auto buildsettings = targets[m_project.rootPackage.name].buildSettings.dup;
  174. if (settings.run && !(buildsettings.options & BuildOption.syntaxOnly)) {
  175. NativePath exe_file_path;
  176. if (m_tempTargetExecutablePath.empty)
  177. exe_file_path = getTargetPath(buildsettings, settings);
  178. else
  179. exe_file_path = m_tempTargetExecutablePath ~ settings.compiler.getTargetFileName(buildsettings, settings.platform);
  180. runTarget(exe_file_path, buildsettings, settings.runArgs, settings);
  181. }
  182. }
  183.  
  184. 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)
  185. {
  186. import std.path : absolutePath;
  187.  
  188. auto cwd = NativePath(getcwd());
  189. bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly);
  190.  
  191. auto build_id = computeBuildID(config, buildsettings, settings);
  192.  
  193. // make all paths relative to shrink the command line
  194. string makeRelative(string path) { return shrinkPath(NativePath(path), cwd); }
  195. foreach (ref f; buildsettings.sourceFiles) f = makeRelative(f);
  196. foreach (ref p; buildsettings.importPaths) p = makeRelative(p);
  197. foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p);
  198.  
  199. // perform the actual build
  200. bool cached = false;
  201. if (settings.rdmd) performRDMDBuild(settings, buildsettings, pack, config, dependencyBinariesToCopy, target_path);
  202. else if (settings.direct || !generate_binary) performDirectBuild(settings, buildsettings, pack, config, dependencyBinariesToCopy, target_path);
  203. else cached = performCachedBuild(settings, buildsettings, pack, config, build_id, packages, additional_dep_files, dependencyBinariesToCopy, target_path);
  204.  
  205. // HACK: cleanup dummy doc files, we shouldn't specialize on buildType
  206. // here and the compiler shouldn't need dummy doc output.
  207. if (settings.buildType == "ddox") {
  208. if ("__dummy.html".exists)
  209. removeFile("__dummy.html");
  210. if ("__dummy_docs".exists)
  211. rmdirRecurse("__dummy_docs");
  212. }
  213.  
  214. // run post-build commands
  215. if (!cached && buildsettings.postBuildCommands.length) {
  216. logInfo("Post-build", Color.light_green, "Running commands");
  217. runBuildCommands(CommandType.postBuild, buildsettings.postBuildCommands, pack, m_project, settings, buildsettings,
  218. [["DUB_BUILD_PATH" : target_path is NativePath.init ? "" : target_path.parentPath.toNativeString.absolutePath]]);
  219. }
  220.  
  221. return cached;
  222. }
  223.  
  224. private bool performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config,
  225. string build_id, in Package[] packages, in NativePath[] additional_dep_files, in NativePath[] dependencyBinariesToCopy, out NativePath target_binary_path)
  226. {
  227. auto cwd = NativePath(getcwd());
  228.  
  229. NativePath target_path;
  230. if (settings.tempBuild) {
  231. string packageName = pack.basePackage is null ? pack.name : pack.basePackage.name;
  232. m_tempTargetExecutablePath = target_path = getTempDir() ~ format(".dub/build/%s-%s/%s/", packageName, pack.version_, build_id);
  233. }
  234. else target_path = pack.path ~ format(".dub/build/%s/", build_id);
  235.  
  236. if (!settings.force && isUpToDate(target_path, buildsettings, settings, pack, packages, additional_dep_files)) {
  237. logInfo("Up-to-date", Color.green, "%s %s: target for configuration [%s] is up to date.",
  238. pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  239. logDiagnostic("Using existing build in %s.", target_path.toNativeString());
  240. target_binary_path = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform);
  241. if (!settings.tempBuild)
  242. copyTargetFile(target_path, buildsettings, settings, dependencyBinariesToCopy);
  243. return true;
  244. }
  245.  
  246. if (!isWritableDir(target_path, true)) {
  247. if (!settings.tempBuild)
  248. logInfo("Build directory %s is not writable. Falling back to direct build in the system's temp folder.", target_path.relativeTo(cwd).toNativeString());
  249. performDirectBuild(settings, buildsettings, pack, config, dependencyBinariesToCopy, target_path);
  250. return false;
  251. }
  252.  
  253. logInfo("Building", Color.light_green, "%s %s: building configuration [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  254.  
  255. if( buildsettings.preBuildCommands.length ){
  256. logInfo("Pre-build", Color.light_green, "Running commands");
  257. runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings);
  258. }
  259.  
  260. // override target path
  261. auto cbuildsettings = buildsettings;
  262. cbuildsettings.targetPath = shrinkPath(target_path, cwd);
  263. buildWithCompiler(settings, cbuildsettings);
  264. target_binary_path = getTargetPath(cbuildsettings, settings);
  265.  
  266. if (!settings.tempBuild)
  267. copyTargetFile(target_path, buildsettings, settings, dependencyBinariesToCopy);
  268.  
  269. return false;
  270. }
  271.  
  272. private void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, in NativePath[] dependencyBinariesToCopy, out NativePath target_path)
  273. {
  274. auto cwd = NativePath(getcwd());
  275. //Added check for existence of [AppNameInPackagejson].d
  276. //If exists, use that as the starting file.
  277. NativePath mainsrc;
  278. if (buildsettings.mainSourceFile.length) {
  279. mainsrc = NativePath(buildsettings.mainSourceFile);
  280. if (!mainsrc.absolute) mainsrc = pack.path ~ mainsrc;
  281. } else {
  282. mainsrc = getMainSourceFile(pack);
  283. logWarn(`Package has no "mainSourceFile" defined. Using best guess: %s`, mainsrc.relativeTo(pack.path).toNativeString());
  284. }
  285.  
  286. // do not pass all source files to RDMD, only the main source file
  287. buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(s => !s.endsWith(".d"))().array();
  288. settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine);
  289.  
  290. auto generate_binary = !buildsettings.dflags.canFind("-o-");
  291.  
  292. // Create start script, which will be used by the calling bash/cmd script.
  293. // build "rdmd --force %DFLAGS% -I%~dp0..\source -Jviews -Isource @deps.txt %LIBS% source\app.d" ~ application arguments
  294. // or with "/" instead of "\"
  295. bool tmp_target = false;
  296. if (generate_binary) {
  297. if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) {
  298. import std.random;
  299. auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-";
  300. auto tmpdir = getTempDir()~".rdmd/source/";
  301. buildsettings.targetPath = tmpdir.toNativeString();
  302. buildsettings.targetName = rnd ~ buildsettings.targetName;
  303. m_temporaryFiles ~= tmpdir;
  304. tmp_target = true;
  305. }
  306. target_path = getTargetPath(buildsettings, settings);
  307. settings.compiler.setTarget(buildsettings, settings.platform);
  308. }
  309.  
  310. logDiagnostic("Application output name is '%s'", settings.compiler.getTargetFileName(buildsettings, settings.platform));
  311.  
  312. string[] flags = ["--build-only", "--compiler="~settings.platform.compilerBinary];
  313. if (settings.force) flags ~= "--force";
  314. flags ~= buildsettings.dflags;
  315. flags ~= mainsrc.relativeTo(cwd).toNativeString();
  316.  
  317. if (buildsettings.preBuildCommands.length){
  318. logInfo("Pre-build", Color.light_green, "Running commands");
  319. runCommands(buildsettings.preBuildCommands);
  320. }
  321.  
  322. logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  323.  
  324. logInfo("Running rdmd...");
  325. logDiagnostic("rdmd %s", join(flags, " "));
  326. auto rdmd_pid = spawnProcess("rdmd" ~ flags);
  327. auto result = rdmd_pid.wait();
  328. enforce(result == 0, "Build command failed with exit code "~to!string(result));
  329.  
  330. if (tmp_target) {
  331. m_temporaryFiles ~= target_path;
  332. foreach (f; buildsettings.copyFiles)
  333. m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head;
  334. }
  335. }
  336.  
  337. private void performDirectBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, in NativePath[] dependencyBinariesToCopy, out NativePath target_path)
  338. {
  339. auto cwd = NativePath(getcwd());
  340. auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly);
  341.  
  342. // make file paths relative to shrink the command line
  343. foreach (ref f; buildsettings.sourceFiles) {
  344. auto fp = NativePath(f);
  345. if( fp.absolute ) fp = fp.relativeTo(cwd);
  346. f = fp.toNativeString();
  347. }
  348.  
  349. logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue));
  350.  
  351. // make all target/import paths relative
  352. string makeRelative(string path) {
  353. auto p = NativePath(path);
  354. // storing in a separate temprary to work around #601
  355. auto prel = p.absolute ? p.relativeTo(cwd) : p;
  356. return prel.toNativeString();
  357. }
  358. buildsettings.targetPath = makeRelative(buildsettings.targetPath);
  359. foreach (ref p; buildsettings.importPaths) p = makeRelative(p);
  360. foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p);
  361.  
  362. bool is_temp_target = false;
  363. if (generate_binary) {
  364. if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) {
  365. import std.random;
  366. auto rnd = to!string(uniform(uint.min, uint.max));
  367. auto tmppath = getTempDir()~("dub/"~rnd~"/");
  368. buildsettings.targetPath = tmppath.toNativeString();
  369. m_temporaryFiles ~= tmppath;
  370. is_temp_target = true;
  371. }
  372. target_path = getTargetPath(buildsettings, settings);
  373. }
  374.  
  375. if( buildsettings.preBuildCommands.length ){
  376. logInfo("Pre-build", Color.light_green, "Running commands");
  377. runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings);
  378. }
  379.  
  380. buildWithCompiler(settings, buildsettings);
  381.  
  382. if (is_temp_target) {
  383. m_temporaryFiles ~= target_path;
  384. foreach (f; buildsettings.copyFiles)
  385. m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head;
  386. }
  387. }
  388.  
  389. private string computeBuildID(string config, in BuildSettings buildsettings, GeneratorSettings settings)
  390. {
  391. const(string[])[] hashing = [
  392. buildsettings.versions,
  393. buildsettings.debugVersions,
  394. buildsettings.dflags,
  395. buildsettings.lflags,
  396. buildsettings.stringImportPaths,
  397. buildsettings.importPaths,
  398. settings.platform.architecture,
  399. [
  400. (cast(uint)(buildsettings.options & ~BuildOption.color)).to!string, // exclude color option from id
  401. settings.platform.compilerBinary,
  402. settings.platform.compiler,
  403. settings.platform.compilerVersion,
  404. ],
  405. ];
  406.  
  407. return computeBuildName(config, settings, hashing);
  408. }
  409.  
  410. private void copyTargetFile(in NativePath build_path, in BuildSettings buildsettings, in GeneratorSettings settings, in NativePath[] dependencyBinariesToCopy)
  411. {
  412. if (!existsFile(NativePath(buildsettings.targetPath)))
  413. mkdirRecurse(buildsettings.targetPath);
  414.  
  415. string[] filenames = [
  416. settings.compiler.getTargetFileName(buildsettings, settings.platform)
  417. ];
  418.  
  419. // Windows: add .pdb (for executables and DLLs) and/or import .lib & .exp (for DLLs) if found
  420. if (settings.platform.isWindows()) {
  421. void addIfFound(string extension) {
  422. import std.path : setExtension;
  423. const candidate = filenames[0].setExtension(extension);
  424. if (existsFile(build_path ~ candidate))
  425. filenames ~= candidate;
  426. }
  427.  
  428. const tt = buildsettings.targetType;
  429. if (tt == TargetType.executable || tt == TargetType.dynamicLibrary)
  430. addIfFound(".pdb");
  431.  
  432. if (tt == TargetType.dynamicLibrary) {
  433. addIfFound(".lib");
  434. addIfFound(".exp");
  435. }
  436. }
  437.  
  438. foreach (filename; filenames)
  439. {
  440. auto src = build_path ~ filename;
  441. logDiagnostic("Copying target from %s to %s", src.toNativeString(), buildsettings.targetPath);
  442. hardLinkFile(src, NativePath(buildsettings.targetPath) ~ filename, true);
  443. }
  444.  
  445. foreach(src; dependencyBinariesToCopy) {
  446. logDiagnostic("Copying target from %s to %s", src.toNativeString(), buildsettings.targetPath);
  447. hardLinkFile(src, NativePath(buildsettings.targetPath) ~ src.head, true);
  448. }
  449. }
  450.  
  451. private bool isUpToDate(NativePath target_path, BuildSettings buildsettings, GeneratorSettings settings, in Package main_pack, in Package[] packages, in NativePath[] additional_dep_files)
  452. {
  453. import std.datetime;
  454.  
  455. auto targetfile = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform);
  456. if (!existsFile(targetfile)) {
  457. logDiagnostic("Target '%s' doesn't exist, need rebuild.", targetfile.toNativeString());
  458. return false;
  459. }
  460. auto targettime = getFileInfo(targetfile).timeModified;
  461.  
  462. auto allfiles = appender!(string[]);
  463. allfiles ~= buildsettings.sourceFiles;
  464. allfiles ~= buildsettings.importFiles;
  465. allfiles ~= buildsettings.stringImportFiles;
  466. allfiles ~= buildsettings.extraDependencyFiles;
  467. // TODO: add library files
  468. foreach (p; packages)
  469. allfiles ~= (p.recipePath != NativePath.init ? p : p.basePackage).recipePath.toNativeString();
  470. foreach (f; additional_dep_files) allfiles ~= f.toNativeString();
  471. bool checkSelectedVersions = !settings.single;
  472. if (checkSelectedVersions && main_pack is m_project.rootPackage && m_project.rootPackage.getAllDependencies().length > 0)
  473. allfiles ~= (main_pack.path ~ SelectedVersions.defaultFile).toNativeString();
  474.  
  475. foreach (file; allfiles.data) {
  476. if (!existsFile(file)) {
  477. logDiagnostic("File %s doesn't exist, triggering rebuild.", file);
  478. return false;
  479. }
  480. auto ftime = getFileInfo(file).timeModified;
  481. if (ftime > Clock.currTime)
  482. logWarn("File '%s' was modified in the future. Please re-save.", file);
  483. if (ftime > targettime) {
  484. logDiagnostic("File '%s' modified, need rebuild.", file);
  485. return false;
  486. }
  487. }
  488. return true;
  489. }
  490.  
  491. /// Output an unique name to represent the source file.
  492. /// Calls with path that resolve to the same file on the filesystem will return the same,
  493. /// unless they include different symbolic links (which are not resolved).
  494.  
  495. static string pathToObjName(const scope ref BuildPlatform platform, string path)
  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(getcwd(), 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);
  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);
  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);
  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);
  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);
  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);
  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 = NativePath(getcwd());
  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. private NativePath getMainSourceFile(in Package prj)
  667. {
  668. foreach (f; ["source/app.d", "src/app.d", "source/"~prj.name~".d", "src/"~prj.name~".d"])
  669. if (existsFile(prj.path ~ f))
  670. return prj.path ~ f;
  671. return prj.path ~ "source/app.d";
  672. }
  673.  
  674. private NativePath getTargetPath(const scope ref BuildSettings bs, const scope ref GeneratorSettings settings)
  675. {
  676. return NativePath(bs.targetPath) ~ settings.compiler.getTargetFileName(bs, settings.platform);
  677. }
  678.  
  679. private string shrinkPath(NativePath path, NativePath base)
  680. {
  681. auto orig = path.toNativeString();
  682. if (!path.absolute) return orig;
  683. version (Windows)
  684. {
  685. // avoid relative paths starting with `..\`: https://github.com/dlang/dub/issues/2143
  686. if (!path.startsWith(base)) return orig;
  687. }
  688. auto rel = path.relativeTo(base).toNativeString();
  689. return rel.length < orig.length ? rel : orig;
  690. }
  691.  
  692. unittest {
  693. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo")) == NativePath("bar/baz").toNativeString());
  694. version (Windows)
  695. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("/foo/bar/baz").toNativeString());
  696. else
  697. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("../bar/baz").toNativeString());
  698. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/")) == NativePath("/foo/bar/baz").toNativeString());
  699. assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/baz")) == NativePath("/foo/bar/baz").toNativeString());
  700. }
  701.  
  702. unittest { // issue #1235 - pass no library files to compiler command line when building a static lib
  703. import dub.internal.vibecompat.data.json : parseJsonString;
  704. import dub.compilers.gdc : GDCCompiler;
  705. import dub.platform : determinePlatform;
  706.  
  707. version (Windows) auto libfile = "bar.lib";
  708. else auto libfile = "bar.a";
  709.  
  710. auto desc = parseJsonString(`{"name": "test", "targetType": "library", "sourceFiles": ["foo.d", "`~libfile~`"]}`);
  711. auto pack = new Package(desc, NativePath("/tmp/fooproject"));
  712. auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false);
  713. auto prj = new Project(pman, pack);
  714.  
  715. final static class TestCompiler : GDCCompiler {
  716. override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) {
  717. assert(!settings.dflags[].any!(f => f.canFind("bar")));
  718. }
  719. override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) {
  720. assert(false);
  721. }
  722. }
  723.  
  724. GeneratorSettings settings;
  725. settings.platform = BuildPlatform(determinePlatform(), ["x86"], "gdc", "test", 2075);
  726. settings.compiler = new TestCompiler;
  727. settings.config = "library";
  728. settings.buildType = "debug";
  729. settings.tempBuild = true;
  730.  
  731. auto gen = new BuildGenerator(prj);
  732. gen.generate(settings);
  733. }