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