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