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) {
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 ouput.
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 existance 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)
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 (settings.buildMode == BuildMode.allAtOnce || settings.platform.compilerBinary != "dmd" || !generate_binary || is_static_library) {
466 			// setup for command line
467 			if (generate_binary) 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 			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 			buildsettings.addDFlags("-c", "-of"~tempobj.toNativeString());
490 			buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(f)).array;
491 			settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine);
492 
493 			settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback);
494 
495 			logInfo("Linking...");
496 			settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()], settings.linkCallback);
497 		}
498 	}
499 
500 	private void runTarget(Path exe_file_path, in BuildSettings buildsettings, string[] run_args, GeneratorSettings settings)
501 	{
502 		if (buildsettings.targetType == TargetType.executable) {
503 			auto cwd = Path(getcwd());
504 			auto runcwd = cwd;
505 			if (buildsettings.workingDirectory.length) {
506 				runcwd = Path(buildsettings.workingDirectory);
507 				if (!runcwd.absolute) runcwd = cwd ~ runcwd;
508 				logDiagnostic("Switching to %s", runcwd.toNativeString());
509 				chdir(runcwd.toNativeString());
510 			}
511 			scope(exit) chdir(cwd.toNativeString());
512 			if (!exe_file_path.absolute) exe_file_path = cwd ~ exe_file_path;
513 			auto exe_path_string = exe_file_path.relativeTo(runcwd).toNativeString();
514 			version (Posix) {
515 				if (!exe_path_string.startsWith(".") && !exe_path_string.startsWith("/"))
516 					exe_path_string = "./" ~ exe_path_string;
517 			}
518 			version (Windows) {
519 				if (!exe_path_string.startsWith(".") && (exe_path_string.length < 2 || exe_path_string[1] != ':'))
520 					exe_path_string = ".\\" ~ exe_path_string;
521 			}
522 			logInfo("Running %s %s", exe_path_string, run_args.join(" "));
523 			if (settings.runCallback) {
524 				auto res = execute(exe_path_string ~ run_args);
525 				settings.runCallback(res.status, res.output);
526 			} else {
527 				auto prg_pid = spawnProcess(exe_path_string ~ run_args);
528 				auto result = prg_pid.wait();
529 				enforce(result == 0, "Program exited with code "~to!string(result));
530 			}
531 		} else
532 			enforce(false, "Target is a library. Skipping execution.");
533 	}
534 
535 	private void cleanupTemporaries()
536 	{
537 		foreach_reverse (f; m_temporaryFiles) {
538 			try {
539 				if (f.endsWithSlash) rmdir(f.toNativeString());
540 				else remove(f.toNativeString());
541 			} catch (Exception e) {
542 				logWarn("Failed to remove temporary file '%s': %s", f.toNativeString(), e.msg);
543 				logDiagnostic("Full error: %s", e.toString().sanitize);
544 			}
545 		}
546 		m_temporaryFiles = null;
547 	}
548 }
549 
550 private Path getMainSourceFile(in Package prj)
551 {
552 	foreach (f; ["source/app.d", "src/app.d", "source/"~prj.name~".d", "src/"~prj.name~".d"])
553 		if (existsFile(prj.path ~ f))
554 			return prj.path ~ f;
555 	return prj.path ~ "source/app.d";
556 }
557 
558 private Path getTargetPath(in ref BuildSettings bs, in ref GeneratorSettings settings)
559 {
560 	return Path(bs.targetPath) ~ settings.compiler.getTargetFileName(bs, settings.platform);
561 }