1 /**
2 	Generator for project files
3 
4 	Copyright: © 2012-2013 Matthias Dondorff, © 2013-2016 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Matthias Dondorff
7 */
8 module dub.generators.generator;
9 
10 import dub.compilers.compiler;
11 import dub.generators.cmake;
12 import dub.generators.build;
13 import dub.generators.sublimetext;
14 import dub.generators.visuald;
15 import dub.internal.utils;
16 import dub.internal.vibecompat.core.file;
17 import dub.internal.vibecompat.data.json;
18 import dub.internal.vibecompat.inet.path;
19 import dub.internal.logging;
20 import dub.package_;
21 import dub.packagemanager;
22 import dub.project;
23 
24 import std.algorithm : map, filter, canFind, balancedParens;
25 import std.array : array, appender, join;
26 import std.exception;
27 import std.file;
28 import std.string;
29 
30 
31 /**
32 	Common interface for project generators/builders.
33 */
34 class ProjectGenerator
35 {
36 	/** Information about a single binary target.
37 
38 		A binary target can either be an executable or a static/dynamic library.
39 		It consists of one or more packages.
40 	*/
41 	struct TargetInfo {
42 		/// The root package of this target
43 		Package pack;
44 
45 		/// All packages compiled into this target
46 		Package[] packages;
47 
48 		/// The configuration used for building the root package
49 		string config;
50 
51 		/** Build settings used to build the target.
52 
53 			The build settings include all sources of all contained packages.
54 
55 			Depending on the specific generator implementation, it may be
56 			necessary to add any static or dynamic libraries generated for
57 			child targets ($(D linkDependencies)).
58 		*/
59 		BuildSettings buildSettings;
60 
61 		/** List of all dependencies.
62 
63 			This list includes dependencies that are not the root of a binary
64 			target.
65 		*/
66 		string[] dependencies;
67 
68 		/** List of all binary dependencies.
69 
70 			This list includes all dependencies that are the root of a binary
71 			target.
72 		*/
73 		string[] linkDependencies;
74 	}
75 
76 	private struct EnvironmentVariables
77 	{
78 		string[string] environments;
79 		string[string] buildEnvironments;
80 		string[string] runEnvironments;
81 		string[string] preGenerateEnvironments;
82 		string[string] postGenerateEnvironments;
83 		string[string] preBuildEnvironments;
84 		string[string] postBuildEnvironments;
85 		string[string] preRunEnvironments;
86 		string[string] postRunEnvironments;
87 
88 		this(const scope ref BuildSettings bs)
89 		{
90 			update(bs);
91 		}
92 
93 		void update(Envs)(const scope auto ref Envs envs)
94 		{
95 			import std.algorithm: each;
96 			envs.environments.byKeyValue.each!(pair => environments[pair.key] = pair.value);
97 			envs.buildEnvironments.byKeyValue.each!(pair => buildEnvironments[pair.key] = pair.value);
98 			envs.runEnvironments.byKeyValue.each!(pair => runEnvironments[pair.key] = pair.value);
99 			envs.preGenerateEnvironments.byKeyValue.each!(pair => preGenerateEnvironments[pair.key] = pair.value);
100 			envs.postGenerateEnvironments.byKeyValue.each!(pair => postGenerateEnvironments[pair.key] = pair.value);
101 			envs.preBuildEnvironments.byKeyValue.each!(pair => preBuildEnvironments[pair.key] = pair.value);
102 			envs.postBuildEnvironments.byKeyValue.each!(pair => postBuildEnvironments[pair.key] = pair.value);
103 			envs.preRunEnvironments.byKeyValue.each!(pair => preRunEnvironments[pair.key] = pair.value);
104 			envs.postRunEnvironments.byKeyValue.each!(pair => postRunEnvironments[pair.key] = pair.value);
105 		}
106 
107 		void updateBuildSettings(ref BuildSettings bs)
108 		{
109 			bs.updateEnvironments(environments);
110 			bs.updateBuildEnvironments(buildEnvironments);
111 			bs.updateRunEnvironments(runEnvironments);
112 			bs.updatePreGenerateEnvironments(preGenerateEnvironments);
113 			bs.updatePostGenerateEnvironments(postGenerateEnvironments);
114 			bs.updatePreBuildEnvironments(preBuildEnvironments);
115 			bs.updatePostBuildEnvironments(postBuildEnvironments);
116 			bs.updatePreRunEnvironments(preRunEnvironments);
117 			bs.updatePostRunEnvironments(postRunEnvironments);
118 		}
119 	}
120 
121 	protected {
122 		Project m_project;
123 		NativePath m_tempTargetExecutablePath;
124 	}
125 
126 	this(Project project)
127 	{
128 		m_project = project;
129 	}
130 
131 	/** Performs the full generator process.
132 	*/
133 	final void generate(GeneratorSettings settings)
134 	{
135 		import dub.compilers.utils : enforceBuildRequirements;
136 
137 		if (!settings.config.length) settings.config = m_project.getDefaultConfiguration(settings.platform);
138 
139 		string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config);
140 		TargetInfo[string] targets;
141 		EnvironmentVariables[string] envs;
142 
143 		foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) {
144 			auto config = configs[pack.name];
145 			auto bs = pack.getBuildSettings(settings.platform, config);
146 			targets[pack.name] = TargetInfo(pack, [pack], config, bs);
147 			envs[pack.name] = EnvironmentVariables(bs);
148 		}
149 		foreach (pack; m_project.getTopologicalPackageList(false, null, configs)) {
150 			auto ti = pack.name in targets;
151 			auto parentEnvs = ti.pack.name in envs;
152 			foreach (deppkgName, depInfo; pack.getDependencies(ti.config)) {
153 				if (auto childEnvs = deppkgName in envs) {
154 					childEnvs.update(ti.buildSettings);
155 					parentEnvs.update(childEnvs);
156 				}
157 			}
158 		}
159 		BuildSettings makeBuildSettings(in Package pack, ref BuildSettings src)
160 		{
161 			BuildSettings bs;
162 			if (settings.buildSettings.options & BuildOption.lowmem) bs.options |= BuildOption.lowmem;
163 			BuildSettings srcbs = src.dup;
164 			envs[pack.name].updateBuildSettings(srcbs);
165 			bs.processVars(m_project, pack, srcbs, settings, true);
166 			return bs;
167 		}
168 
169 		foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) {
170 			BuildSettings bs = makeBuildSettings(pack, targets[pack.name].buildSettings);
171 			prepareGeneration(pack, m_project, settings, bs);
172 
173 			// Regenerate buildSettings.sourceFiles
174 			if (bs.preGenerateCommands.length) {
175 				auto newSettings = pack.getBuildSettings(settings.platform, configs[pack.name]);
176 				bs = makeBuildSettings(pack, newSettings);
177 			}
178 			targets[pack.name].buildSettings = bs;
179 		}
180 		configurePackages(m_project.rootPackage, targets, settings);
181 
182 		addBuildTypeSettings(targets, settings);
183 		foreach (ref t; targets.byValue) enforceBuildRequirements(t.buildSettings);
184 
185 		generateTargets(settings, targets);
186 
187 		foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) {
188 			auto config = configs[pack.name];
189 			auto pkgbs  = pack.getBuildSettings(settings.platform, config);
190 			BuildSettings buildsettings = makeBuildSettings(pack, pkgbs);
191 			bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly);
192 			auto bs = &targets[m_project.rootPackage.name].buildSettings;
193 			auto targetPath = !m_tempTargetExecutablePath.empty ? m_tempTargetExecutablePath :
194 							  !bs.targetPath.empty ? NativePath(bs.targetPath) :
195 							  NativePath(buildsettings.targetPath);
196 
197 			finalizeGeneration(pack, m_project, settings, buildsettings, targetPath, generate_binary);
198 		}
199 
200 		performPostGenerateActions(settings, targets);
201 	}
202 
203 	/** Overridden in derived classes to implement the actual generator functionality.
204 
205 		The function should go through all targets recursively. The first target
206 		(which is guaranteed to be there) is
207 		$(D targets[m_project.rootPackage.name]). The recursive descent is then
208 		done using the $(D TargetInfo.linkDependencies) list.
209 
210 		This method is also potentially responsible for running the pre and post
211 		build commands, while pre and post generate commands are already taken
212 		care of by the $(D generate) method.
213 
214 		Params:
215 			settings = The generator settings used for this run
216 			targets = A map from package name to TargetInfo that contains all
217 				binary targets to be built.
218 	*/
219 	protected abstract void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets);
220 
221 	/** Overridable method to be invoked after the generator process has finished.
222 
223 		An examples of functionality placed here is to run the application that
224 		has just been built.
225 	*/
226 	protected void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets) {}
227 
228 	/** Configure `rootPackage` and all of it's dependencies.
229 
230 		1. Merge versions, debugVersions, and inheritable build
231 		settings from dependents to their dependencies.
232 
233 		2. Define version identifiers Have_dependency_xyz for all
234 		direct dependencies of all packages.
235 
236 		3. Merge versions, debugVersions, and inheritable build settings from
237 		dependencies to their dependents, so that importer and importee are ABI
238 		compatible. This also transports all Have_dependency_xyz version
239 		identifiers to `rootPackage`.
240 
241 		4. Merge injectSourceFiles from dependencies into their dependents.
242 		This is based upon binary images and will transcend direct relationships
243 		including shared libraries.
244 
245 		5. Filter unused versions and debugVersions from all targets. The
246 		filters have previously been upwards inherited (3. and 4.) so that versions
247 		used in a dependency are also applied to all dependents.
248 
249 		Note: The upwards inheritance is done at last so that siblings do not
250 		influence each other, also see https://github.com/dlang/dub/pull/1128.
251 
252 		Note: Targets without output are integrated into their
253 		dependents and removed from `targets`.
254 	 */
255 	private void configurePackages(Package rootPackage, TargetInfo[string] targets, GeneratorSettings genSettings)
256 	{
257 		import std.algorithm : remove, sort;
258 		import std.range : repeat;
259 
260 		auto roottarget = &targets[rootPackage.name];
261 
262 		// 0. do shallow configuration (not including dependencies) of all packages
263 		TargetType determineTargetType(const ref TargetInfo ti, const ref GeneratorSettings genSettings)
264 		{
265 			TargetType tt = ti.buildSettings.targetType;
266 			if (ti.pack is rootPackage) {
267 				if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary;
268 			} else {
269 				if (tt == TargetType.autodetect || tt == TargetType.library) tt = genSettings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary;
270 				else if (genSettings.platform.architecture.canFind("x86_omf") && tt == TargetType.dynamicLibrary) {
271 					// Unfortunately we cannot remove this check for OMF targets,
272 					// due to Optlink not producing shared libraries without a lot of user intervention.
273 					// For other targets, say MSVC it'll do the right thing for the most part,
274 					// export is still a problem as of this writing, which means static libraries cannot have linking to them removed.
275 					// But that is for the most part up to the developer, to get it working correctly.
276 
277 					logWarn("Dynamic libraries are not yet supported as dependencies for Windows target OMF - building as static library.");
278 					tt = TargetType.staticLibrary;
279 				}
280 			}
281 			if (tt != TargetType.none && tt != TargetType.sourceLibrary && ti.buildSettings.sourceFiles.empty) {
282 				logWarn(`Configuration [%s] of package %s contains no source files. Please add %s to its package description to avoid building it.`,
283 						ti.config.color(Color.blue), ti.pack.name.color(Mode.bold), `{"targetType": "none"}`.color(Mode.bold));
284 				tt = TargetType.none;
285 			}
286 			return tt;
287 		}
288 
289 		string[] mainSourceFiles;
290 		bool[string] hasOutput;
291 
292 		foreach (ref ti; targets.byValue)
293 		{
294 			auto bs = &ti.buildSettings;
295 			// determine the actual target type
296 			bs.targetType = determineTargetType(ti, genSettings);
297 
298 			switch (bs.targetType)
299 			{
300 			case TargetType.none:
301 				// ignore any build settings for targetType none (only dependencies will be processed)
302 				*bs = BuildSettings.init;
303 				bs.targetType = TargetType.none;
304 				break;
305 
306 			case TargetType.executable:
307 				break;
308 
309 			case TargetType.dynamicLibrary:
310 				// set -fPIC for dynamic library builds
311 				ti.buildSettings.addOptions(BuildOption.pic);
312 				goto default;
313 
314 			default:
315 				// remove any mainSourceFile from non-executable builds
316 				if (bs.mainSourceFile.length) {
317 					bs.sourceFiles = bs.sourceFiles.remove!(f => f == bs.mainSourceFile);
318 					mainSourceFiles ~= bs.mainSourceFile;
319 				}
320 				break;
321 			}
322 			bool generatesBinary = bs.targetType != TargetType.sourceLibrary && bs.targetType != TargetType.none;
323 			hasOutput[ti.pack.name] = generatesBinary || ti.pack is rootPackage;
324 		}
325 
326 		// add main source files to root executable
327 		{
328 			auto bs = &roottarget.buildSettings;
329 			if (bs.targetType == TargetType.executable || genSettings.single) bs.addSourceFiles(mainSourceFiles);
330 		}
331 
332 		if (genSettings.filterVersions)
333 			foreach (ref ti; targets.byValue)
334 				inferVersionFilters(ti);
335 
336 		// mark packages as visited (only used during upwards propagation)
337 		void[0][Package] visited;
338 
339 		// collect all dependencies
340 		void collectDependencies(Package pack, ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0)
341 		{
342 			// use `visited` here as pkgs cannot depend on themselves
343 			if (pack in visited)
344 				return;
345 			// transitive dependencies must be visited multiple times, see #1350
346 			immutable transitive = !hasOutput[pack.name];
347 			if (!transitive)
348 				visited[pack] = typeof(visited[pack]).init;
349 
350 			auto bs = &ti.buildSettings;
351 			if (hasOutput[pack.name])
352 				logDebug("%sConfiguring target %s (%s %s %s)", ' '.repeat(2 * level), pack.name, bs.targetType, bs.targetPath, bs.targetName);
353 			else
354 				logDebug("%sConfiguring target without output %s", ' '.repeat(2 * level), pack.name);
355 
356 			// get specified dependencies, e.g. vibe-d ~0.8.1
357 			auto deps = pack.getDependencies(targets[pack.name].config);
358 			logDebug("deps: %s -> %(%s, %)", pack.name, deps.byKey);
359 			foreach (depname; deps.keys.sort())
360 			{
361 				auto depspec = deps[depname];
362 				// get selected package for that dependency, e.g. vibe-d 0.8.2-beta.2
363 				auto deppack = m_project.getDependency(depname, depspec.optional);
364 				if (deppack is null) continue; // optional and not selected
365 
366 				// if dependency has no output
367 				if (!hasOutput[depname]) {
368 					// add itself
369 					ti.packages ~= deppack;
370 					// and it's transitive dependencies to current target
371 					collectDependencies(deppack, ti, targets, level + 1);
372 					continue;
373 				}
374 				auto depti = &targets[depname];
375 				const depbs = &depti.buildSettings;
376 				if (depbs.targetType == TargetType.executable && ti.buildSettings.targetType != TargetType.none)
377 					continue;
378 
379 				// add to (link) dependencies
380 				ti.dependencies ~= depname;
381 				ti.linkDependencies ~= depname;
382 
383 				// recurse
384 				collectDependencies(deppack, *depti, targets, level + 1);
385 
386 				// also recursively add all link dependencies of static *and* dynamic libraries
387 				// preserve topological sorting of dependencies for correct link order
388 				if (depbs.targetType == TargetType.staticLibrary || depbs.targetType == TargetType.dynamicLibrary)
389 					ti.linkDependencies = ti.linkDependencies.filter!(d => !depti.linkDependencies.canFind(d)).array ~ depti.linkDependencies;
390 			}
391 
392 			enforce(!(ti.buildSettings.targetType == TargetType.none && ti.dependencies.empty),
393 				"Package with target type \"none\" must have dependencies to build.");
394 		}
395 
396 		collectDependencies(rootPackage, *roottarget, targets);
397 		visited.clear();
398 
399 		// 1. downwards inherits versions, debugVersions, and inheritable build settings
400 		static void configureDependencies(const scope ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0)
401 		{
402 
403 			// do not use `visited` here as dependencies must inherit
404 			// configurations from *all* of their parents
405 			logDebug("%sConfigure dependencies of %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies);
406 			foreach (depname; ti.dependencies)
407 			{
408 				auto pti = &targets[depname];
409 				mergeFromDependent(ti.buildSettings, pti.buildSettings);
410 				configureDependencies(*pti, targets, level + 1);
411 			}
412 		}
413 
414 		configureDependencies(*roottarget, targets);
415 
416 		// 2. add Have_dependency_xyz for all direct dependencies of a target
417 		// (includes incorporated non-target dependencies and their dependencies)
418 		foreach (ref ti; targets.byValue)
419 		{
420 			import std.range : chain;
421 			import dub.internal.utils : stripDlangSpecialChars;
422 
423 			auto bs = &ti.buildSettings;
424 			auto pkgnames = ti.packages.map!(p => p.name).chain(ti.dependencies);
425 			bs.addVersions(pkgnames.map!(pn => "Have_" ~ stripDlangSpecialChars(pn)).array);
426 		}
427 
428 		// 3. upwards inherit full build configurations (import paths, versions, debugVersions, versionFilters, importPaths, ...)
429 
430 		// We do a check for if any dependency uses final binary injection source files,
431 		// otherwise can ignore that bit of workload entirely
432 		bool skipFinalBinaryMerging = true;
433 
434 		void configureDependents(ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0)
435 		{
436 			// use `visited` here as pkgs cannot depend on themselves
437 			if (ti.pack in visited)
438 				return;
439 			visited[ti.pack] = typeof(visited[ti.pack]).init;
440 
441 			logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies);
442 			// embedded non-binary dependencies
443 			foreach (deppack; ti.packages[1 .. $])
444 				ti.buildSettings.add(targets[deppack.name].buildSettings);
445 
446 			// binary dependencies
447 			foreach (depname; ti.dependencies)
448 			{
449 				auto pdepti = &targets[depname];
450 
451 				configureDependents(*pdepti, targets, level + 1);
452 				mergeFromDependency(pdepti.buildSettings, ti.buildSettings, genSettings.platform);
453 
454 				if (!pdepti.buildSettings.injectSourceFiles.empty)
455 					skipFinalBinaryMerging = false;
456 			}
457 		}
458 
459 		configureDependents(*roottarget, targets);
460 		visited.clear();
461 
462 		// 4. As an extension to configureDependents we need to copy any injectSourceFiles
463 		// in our dependencies (ignoring targetType)
464 		void configureDependentsFinalImages(ref TargetInfo ti, TargetInfo[string] targets, ref TargetInfo finalBinaryTarget, size_t level = 0)
465 		{
466 			// use `visited` here as pkgs cannot depend on themselves
467 			if (ti.pack in visited)
468 				return;
469 			visited[ti.pack] = typeof(visited[ti.pack]).init;
470 
471 			logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %) for injectSourceFiles", ' '.repeat(2 * level), ti.pack.name, ti.dependencies);
472 
473 			foreach (depname; ti.dependencies)
474 			{
475 				auto pdepti = &targets[depname];
476 
477 				if (!pdepti.buildSettings.injectSourceFiles.empty)
478 					finalBinaryTarget.buildSettings.addSourceFiles(pdepti.buildSettings.injectSourceFiles);
479 
480 				configureDependentsFinalImages(*pdepti, targets, finalBinaryTarget, level + 1);
481 			}
482 		}
483 
484 		if (!skipFinalBinaryMerging)
485 		{
486 			foreach (ref target; targets.byValue)
487 			{
488 				switch (target.buildSettings.targetType)
489 				{
490 					case TargetType.executable:
491 					case TargetType.dynamicLibrary:
492 					configureDependentsFinalImages(target, targets, target);
493 
494 					// We need to clear visited for each target that is executable dynamicLibrary
495 					// due to this process needing to be recursive based upon the final binary targets.
496 					visited.clear();
497 					break;
498 
499 					default:
500 					break;
501 				}
502 			}
503 		}
504 
505 		// 5. Filter applicable version and debug version identifiers
506 		if (genSettings.filterVersions)
507 		{
508 			foreach (name, ref ti; targets)
509 			{
510 				import std.algorithm.sorting : partition;
511 
512 				auto bs = &ti.buildSettings;
513 
514 				auto filtered = bs.versions.partition!(v => bs.versionFilters.canFind(v));
515 				logDebug("Filtering out unused versions for %s: %s", name, filtered);
516 				bs.versions = bs.versions[0 .. $ - filtered.length];
517 
518 				filtered = bs.debugVersions.partition!(v => bs.debugVersionFilters.canFind(v));
519 				logDebug("Filtering out unused debug versions for %s: %s", name, filtered);
520 				bs.debugVersions = bs.debugVersions[0 .. $ - filtered.length];
521 			}
522 		}
523 
524 		// 6. override string import files in dependencies
525 		static void overrideStringImports(ref TargetInfo target,
526 			ref TargetInfo parent, TargetInfo[string] targets, string[] overrides)
527 		{
528 			// Since string import paths are inherited from dependencies in the
529 			// inheritance step above (step 3), it is guaranteed that all
530 			// following dependencies will not have string import paths either,
531 			// so we can skip the recursion here
532 			if (!target.buildSettings.stringImportPaths.length)
533 				return;
534 
535 			// do not use visited here as string imports can be overridden by *any* parent
536 			//
537 			// special support for overriding string imports in parent packages
538 			// this is a candidate for deprecation, once an alternative approach
539 			// has been found
540 			bool any_override = false;
541 
542 			// override string import files (used for up to date checking)
543 			foreach (ref f; target.buildSettings.stringImportFiles)
544 			{
545 				foreach (o; overrides)
546 				{
547 					NativePath op;
548 					if (f != o && NativePath(f).head == (op = NativePath(o)).head) {
549 						logDebug("string import %s overridden by %s", f, o);
550 						f = o;
551 						any_override = true;
552 					}
553 				}
554 			}
555 
556 			// override string import paths by prepending to the list, in
557 			// case there is any overlapping file
558 			if (any_override)
559 				target.buildSettings.prependStringImportPaths(parent.buildSettings.stringImportPaths);
560 
561 			// add all files to overrides for recursion
562 			overrides ~= target.buildSettings.stringImportFiles;
563 
564 			// recursively override all dependencies with the accumulated files/paths
565 			foreach (depname; target.dependencies)
566 				overrideStringImports(targets[depname], target, targets, overrides);
567 		}
568 
569 		// push string import paths/files down to all direct and indirect
570 		// dependencies, overriding their own
571 		foreach (depname; roottarget.dependencies)
572 			overrideStringImports(targets[depname], *roottarget, targets,
573 				roottarget.buildSettings.stringImportFiles);
574 
575 		// 7. downwards inherits dependency build settings
576 		static void applyForcedSettings(const scope ref TargetInfo ti, TargetInfo[string] targets,
577 											BuildSettings[string] dependBS, size_t level = 0)
578 		{
579 
580 			static void apply(const scope ref BuildSettings forced, ref BuildSettings child) {
581 				child.addDFlags(forced.dflags);
582 			}
583 
584 			// apply to all dependencies
585 			foreach (depname; ti.dependencies)
586 			{
587 				BuildSettings forcedSettings;
588 				auto pti = &targets[depname];
589 
590 				// fetch the forced dependency build settings
591 				if (auto matchedSettings = depname in dependBS)
592 					forcedSettings = *matchedSettings;
593 				else if (auto matchedSettings = "*" in dependBS)
594 					forcedSettings = *matchedSettings;
595 
596 				apply(forcedSettings, pti.buildSettings);
597 
598 				// recursively apply forced settings to all dependencies of his dependency
599 				applyForcedSettings(*pti, targets, ["*" : forcedSettings], level + 1);
600 			}
601 		}
602 
603 		// apply both top level and configuration level forced dependency build settings
604 		void applyDependencyBuildSettings (const RecipeDependency[string] configured_dbs)
605 		{
606 			BuildSettings[string] dependencyBuildSettings;
607 			foreach (key, value; configured_dbs)
608 			{
609 				BuildSettings buildSettings;
610 				if (auto target = key in targets)
611 				{
612 					// get platform specific build settings and process dub variables (BuildSettingsTemplate => BuildSettings)
613 					value.settings.getPlatformSettings(buildSettings, genSettings.platform, target.pack.path);
614 					buildSettings.processVars(m_project, target.pack, buildSettings, genSettings, true);
615 					dependencyBuildSettings[key] = buildSettings;
616 				}
617 			}
618 			applyForcedSettings(*roottarget, targets, dependencyBuildSettings);
619 		}
620 		applyDependencyBuildSettings(rootPackage.recipe.buildSettings.dependencies);
621 		applyDependencyBuildSettings(rootPackage.getBuildSettings(genSettings.config).dependencies);
622 
623 		// remove targets without output
624 		foreach (name; targets.keys)
625 		{
626 			if (!hasOutput[name])
627 				targets.remove(name);
628 		}
629 	}
630 
631 	// infer applicable version identifiers
632 	private static void inferVersionFilters(ref TargetInfo ti)
633 	{
634 		import std.algorithm.searching : any;
635 		import std.file : timeLastModified;
636 		import std.path : extension;
637 		import std.range : chain;
638 		import std.regex : ctRegex, matchAll;
639 		import std.stdio : File;
640 		import std.datetime : Clock, SysTime, UTC;
641 		import dub.compilers.utils : isLinkerFile;
642 		import dub.internal.vibecompat.data.json : Json, JSONException;
643 
644 		auto bs = &ti.buildSettings;
645 
646 		// only infer if neither version filters are specified explicitly
647 		if (bs.versionFilters.length || bs.debugVersionFilters.length)
648 		{
649 			logDebug("Using specified versionFilters for %s: %s %s", ti.pack.name,
650 				bs.versionFilters, bs.debugVersionFilters);
651 			return;
652 		}
653 
654 		// check all existing source files for version identifiers
655 		static immutable dexts = [".d", ".di"];
656 		auto srcs = chain(bs.sourceFiles, bs.importFiles, bs.stringImportFiles)
657 			.filter!(f => dexts.canFind(f.extension)).filter!exists;
658 		// try to load cached filters first
659 		const cacheFilePath = packageCache(NativePath(ti.buildSettings.targetPath), ti.pack)
660 			~ "metadata_cache.json";
661 		enum silent_fail = true;
662 		auto cache = jsonFromFile(cacheFilePath, silent_fail);
663 		try
664 		{
665 			auto cachedFilters = cache["versionFilters"];
666 			if (cachedFilters.type != Json.Type.undefined)
667 				cachedFilters = cachedFilters[ti.config];
668 			if (cachedFilters.type != Json.Type.undefined)
669 			{
670 				immutable mtime = SysTime.fromISOExtString(cachedFilters["mtime"].get!string);
671 				if (!srcs.any!(src => src.timeLastModified > mtime))
672 				{
673 					auto versionFilters = cachedFilters["versions"][].map!(j => j.get!string).array;
674 					auto debugVersionFilters = cachedFilters["debugVersions"][].map!(j => j.get!string).array;
675 					logDebug("Using cached versionFilters for %s: %s %s", ti.pack.name,
676 						versionFilters, debugVersionFilters);
677 					bs.addVersionFilters(versionFilters);
678 					bs.addDebugVersionFilters(debugVersionFilters);
679 					return;
680 				}
681 			}
682 		}
683 		catch (JSONException e)
684 		{
685 			logWarn("Exception during loading invalid package cache %s.\n%s",
686 				ti.pack.path ~ ".dub/metadata_cache.json", e);
687 		}
688 
689 		// use ctRegex for performance reasons, only small compile time increase
690 		enum verRE = ctRegex!`(?:^|\s)version\s*\(\s*([^\s]*?)\s*\)`;
691 		enum debVerRE = ctRegex!`(?:^|\s)debug\s*\(\s*([^\s]*?)\s*\)`;
692 
693 		auto versionFilters = appender!(string[]);
694 		auto debugVersionFilters = appender!(string[]);
695 
696 		foreach (file; srcs)
697 		{
698 			foreach (line; File(file).byLine)
699 			{
700 				foreach (m; line.matchAll(verRE))
701 					if (!versionFilters.data.canFind(m[1]))
702 						versionFilters.put(m[1].idup);
703 				foreach (m; line.matchAll(debVerRE))
704 					if (!debugVersionFilters.data.canFind(m[1]))
705 						debugVersionFilters.put(m[1].idup);
706 			}
707 		}
708 		logDebug("Using inferred versionFilters for %s: %s %s", ti.pack.name,
709 			versionFilters.data, debugVersionFilters.data);
710 		bs.addVersionFilters(versionFilters.data);
711 		bs.addDebugVersionFilters(debugVersionFilters.data);
712 
713 		auto cachedFilters = cache["versionFilters"];
714 		if (cachedFilters.type == Json.Type.undefined)
715 			cachedFilters = cache["versionFilters"] = [ti.config: Json.emptyObject];
716 		cachedFilters[ti.config] = [
717 			"mtime": Json(Clock.currTime(UTC()).toISOExtString),
718 			"versions": Json(versionFilters.data.map!Json.array),
719 			"debugVersions": Json(debugVersionFilters.data.map!Json.array),
720 		];
721         enum create_if_missing = true;
722         if (isWritableDir(cacheFilePath.parentPath, create_if_missing))
723             writeJsonFile(cacheFilePath, cache);
724 	}
725 
726 	private static void mergeFromDependent(const scope ref BuildSettings parent, ref BuildSettings child)
727 	{
728 		child.addVersions(parent.versions);
729 		child.addDebugVersions(parent.debugVersions);
730 		child.addOptions(Flags!BuildOption(parent.options & inheritedBuildOptions));
731 	}
732 
733 	private static void mergeFromDependency(const scope ref BuildSettings child, ref BuildSettings parent, const scope ref BuildPlatform platform)
734 	{
735 		import dub.compilers.utils : isLinkerFile;
736 
737 		parent.addDFlags(child.dflags);
738 		parent.addVersions(child.versions);
739 		parent.addDebugVersions(child.debugVersions);
740 		parent.addVersionFilters(child.versionFilters);
741 		parent.addDebugVersionFilters(child.debugVersionFilters);
742 		parent.addImportPaths(child.importPaths);
743 		parent.addStringImportPaths(child.stringImportPaths);
744 		parent.addInjectSourceFiles(child.injectSourceFiles);
745 		// linker stuff propagates up from static *and* dynamic library deps
746 		if (child.targetType == TargetType.staticLibrary || child.targetType == TargetType.dynamicLibrary) {
747 			parent.addSourceFiles(child.sourceFiles.filter!(f => isLinkerFile(platform, f)).array);
748 			parent.addLibs(child.libs);
749 			parent.addLFlags(child.lflags);
750 		}
751 	}
752 
753 	// configure targets for build types such as release, or unittest-cov
754 	private void addBuildTypeSettings(TargetInfo[string] targets, in GeneratorSettings settings)
755 	{
756 		foreach (ref ti; targets.byValue) {
757 			ti.buildSettings.add(settings.buildSettings);
758 
759 			// add build type settings and convert plain DFLAGS to build options
760 			m_project.addBuildTypeSettings(ti.buildSettings, settings, ti.pack is m_project.rootPackage);
761 			settings.compiler.extractBuildOptions(ti.buildSettings);
762 
763 			auto tt = ti.buildSettings.targetType;
764 			enforce (tt != TargetType.sourceLibrary || ti.pack !is m_project.rootPackage || (ti.buildSettings.options & BuildOption.syntaxOnly),
765 				format("Main package must not have target type \"%s\". Cannot build.", tt));
766 		}
767 	}
768 }
769 
770 /**
771  * Compute and returns the path were artifacts are stored for a given package
772  *
773  * Artifacts are usually stored in:
774  * `$DUB_HOME/cache/$PKG_NAME/$PKG_VERSION[/+$SUB_PKG_NAME]/`
775  * Note that the leading `+` in the subpackage name is to avoid any ambiguity.
776  * Build artifacts are usually stored in a subfolder named "build",
777  * as their names are based on user-supplied values.
778  *
779  * Params:
780  *   cachePath = Base path at which the build cache is located,
781  *               e.g. `$HOME/.dub/cache/`
782  *	 pkg = The package. Cannot be `null`.
783  */
784 package(dub) NativePath packageCache(NativePath cachePath, in Package pkg)
785 {
786 	import std.algorithm.searching : findSplit;
787 
788 	assert(pkg !is null);
789 	assert(!cachePath.empty);
790 
791 	// For subpackages
792 	if (const names = pkg.name.findSplit(":"))
793 		return cachePath ~ names[0] ~ pkg.version_.toString()
794 			~ ("+" ~ names[2]);
795 	// For regular packages
796 	return cachePath ~ pkg.name ~ pkg.version_.toString();
797 }
798 
799 
800 struct GeneratorSettings {
801 	NativePath cache;
802 	BuildPlatform platform;
803 	Compiler compiler;
804 	string config;
805 	string buildType;
806 	BuildSettings buildSettings;
807 	BuildMode buildMode = BuildMode.separate;
808 	int targetExitStatus;
809 
810 	bool combined; // compile all in one go instead of each dependency separately
811 	bool filterVersions;
812 
813 	// only used for generator "build"
814 	bool run, force, direct, rdmd, tempBuild, parallelBuild;
815 
816 	/// single file dub package
817 	bool single;
818 
819 	string[] runArgs;
820 	void delegate(int status, string output) compileCallback;
821 	void delegate(int status, string output) linkCallback;
822 	void delegate(int status, string output) runCallback;
823 }
824 
825 
826 /**
827 	Determines the mode in which the compiler and linker are invoked.
828 */
829 enum BuildMode {
830 	separate,                 /// Compile and link separately
831 	allAtOnce,                /// Perform compile and link with a single compiler invocation
832 	singleFile,               /// Compile each file separately
833 	//multipleObjects,          /// Generate an object file per module
834 	//multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module
835 	//compileOnly               /// Do not invoke the linker (can be done using a post build command)
836 }
837 
838 
839 /**
840 	Creates a project generator of the given type for the specified project.
841 */
842 ProjectGenerator createProjectGenerator(string generator_type, Project project)
843 {
844 	assert(project !is null, "Project instance needed to create a generator.");
845 
846 	generator_type = generator_type.toLower();
847 	switch(generator_type) {
848 		default:
849 			throw new Exception("Unknown project generator: "~generator_type);
850 		case "build":
851 			logDebug("Creating build generator.");
852 			return new BuildGenerator(project);
853 		case "mono-d":
854 			throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead.");
855 		case "visuald":
856 			logDebug("Creating VisualD generator.");
857 			return new VisualDGenerator(project);
858 		case "sublimetext":
859 			logDebug("Creating SublimeText generator.");
860 			return new SublimeTextGenerator(project);
861 		case "cmake":
862 			logDebug("Creating CMake generator.");
863 			return new CMakeGenerator(project);
864 	}
865 }
866 
867 
868 /**
869 	Calls delegates on files and directories in the given path that match any globs.
870 */
871 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile, void delegate(string dir) addDir)
872 {
873 	import std.path : globMatch;
874 
875 	string[] globs;
876 	foreach (f; globList)
877 	{
878 		if (f.canFind("*", "?") ||
879 			(f.canFind("{") && f.balancedParens('{', '}')) ||
880 			(f.canFind("[") && f.balancedParens('[', ']')))
881 		{
882 			globs ~= f;
883 		}
884 		else
885 		{
886 			if (f.isDir)
887 				addDir(f);
888 			else
889 				addFile(f);
890 		}
891 	}
892 	if (globs.length) // Search all files for glob matches
893 		foreach (f; dirEntries(path.toNativeString(), SpanMode.breadth))
894 			foreach (glob; globs)
895 				if (f.name().globMatch(glob))
896 				{
897 					if (f.isDir)
898 						addDir(f);
899 					else
900 						addFile(f);
901 					break;
902 				}
903 }
904 
905 
906 /**
907 	Calls delegates on files in the given path that match any globs.
908 
909 	If a directory matches a glob, the delegate is called on all existing files inside it recursively
910 	in depth-first pre-order.
911 */
912 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile)
913 {
914 	void addDir(string dir)
915 	{
916 		foreach (f; dirEntries(dir, SpanMode.breadth))
917 			addFile(f);
918 	}
919 
920 	findFilesMatchingGlobs(path, globList, addFile, &addDir);
921 }
922 
923 
924 /**
925 	Runs pre-build commands and performs other required setup before project files are generated.
926 */
927 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
928 	in BuildSettings buildsettings)
929 {
930 	if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
931 		logInfo("Pre-gen", Color.light_green, "Running commands for %s", pack.name);
932 		runBuildCommands(CommandType.preGenerate, buildsettings.preGenerateCommands, pack, proj, settings, buildsettings);
933 	}
934 }
935 
936 /**
937 	Runs post-build commands and copies required files to the binary directory.
938 */
939 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
940 	in BuildSettings buildsettings, NativePath target_path, bool generate_binary)
941 {
942 	if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
943 		logInfo("Post-gen", Color.light_green, "Running commands for %s", pack.name);
944 		runBuildCommands(CommandType.postGenerate, buildsettings.postGenerateCommands, pack, proj, settings, buildsettings);
945 	}
946 
947 	if (generate_binary) {
948 		ensureDirectory(NativePath(buildsettings.targetPath));
949 
950 		if (buildsettings.copyFiles.length) {
951 			void copyFolderRec(NativePath folder, NativePath dstfolder)
952 			{
953 				ensureDirectory(dstfolder);
954 				foreach (de; iterateDirectory(folder.toNativeString())) {
955 					if (de.isDirectory) {
956 						copyFolderRec(folder ~ de.name, dstfolder ~ de.name);
957 					} else {
958 						try hardLinkFile(folder ~ de.name, dstfolder ~ de.name, true);
959 						catch (Exception e) {
960 							logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg);
961 						}
962 					}
963 				}
964 			}
965 
966 			void tryCopyDir(string file)
967 			{
968 				auto src = NativePath(file);
969 				if (!src.absolute) src = pack.path ~ src;
970 				auto dst = target_path ~ NativePath(file).head;
971 				if (src == dst) {
972 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
973 					return;
974 				}
975 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
976 				try {
977 					copyFolderRec(src, dst);
978 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
979 			}
980 
981 			void tryCopyFile(string file)
982 			{
983 				auto src = NativePath(file);
984 				if (!src.absolute) src = pack.path ~ src;
985 				auto dst = target_path ~ NativePath(file).head;
986 				if (src == dst) {
987 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
988 					return;
989 				}
990 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
991 				try {
992 					hardLinkFile(src, dst, true);
993 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
994 			}
995 			logInfo("Copying files for %s...", pack.name);
996 			findFilesMatchingGlobs(pack.path, buildsettings.copyFiles, &tryCopyFile, &tryCopyDir);
997 		}
998 
999 	}
1000 }
1001 
1002 
1003 /** Runs a list of build commands for a particular package.
1004 
1005 	This function sets all DUB speficic environment variables and makes sure
1006 	that recursive dub invocations are detected and don't result in infinite
1007 	command execution loops. The latter could otherwise happen when a command
1008 	runs "dub describe" or similar functionality.
1009 */
1010 void runBuildCommands(CommandType type, in string[] commands, in Package pack, in Project proj,
1011 	in GeneratorSettings settings, in BuildSettings build_settings, in string[string][] extraVars = null)
1012 {
1013 	import dub.internal.utils : runCommands;
1014 
1015 	auto env = makeCommandEnvironmentVariables(type, pack, proj, settings, build_settings, extraVars);
1016 	auto sub_commands = processVars(proj, pack, settings, commands, false, env);
1017 
1018 	auto depNames = proj.dependencies.map!((a) => a.name).array();
1019 	storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames);
1020 
1021 	runCommands(sub_commands, env.collapseEnv, pack.path().toString());
1022 }
1023 
1024 const(string[string])[] makeCommandEnvironmentVariables(CommandType type,
1025 	in Package pack, in Project proj, in GeneratorSettings settings,
1026 	in BuildSettings build_settings, in string[string][] extraVars = null)
1027 {
1028 	import dub.internal.utils : getDUBExePath;
1029 	import std.conv : to, text;
1030 	import std.process : environment, escapeShellFileName;
1031 
1032 	string[string] env;
1033 	// TODO: do more elaborate things here
1034 	// TODO: escape/quote individual items appropriately
1035 	env["VERSIONS"]              = join(build_settings.versions, " ");
1036 	env["LIBS"]                  = join(build_settings.libs, " ");
1037 	env["SOURCE_FILES"]          = join(build_settings.sourceFiles, " ");
1038 	env["IMPORT_PATHS"]          = join(build_settings.importPaths, " ");
1039 	env["STRING_IMPORT_PATHS"]   = join(build_settings.stringImportPaths, " ");
1040 
1041 	env["DC"]                    = settings.platform.compilerBinary;
1042 	env["DC_BASE"]               = settings.platform.compiler;
1043 	env["D_FRONTEND_VER"]        = to!string(settings.platform.frontendVersion);
1044 
1045 	env["DUB_EXE"]               = getDUBExePath(settings.platform.compilerBinary);
1046 	env["DUB_PLATFORM"]          = join(settings.platform.platform, " ");
1047 	env["DUB_ARCH"]              = join(settings.platform.architecture, " ");
1048 
1049 	env["DUB_TARGET_TYPE"]       = to!string(build_settings.targetType);
1050 	env["DUB_TARGET_PATH"]       = build_settings.targetPath;
1051 	env["DUB_TARGET_NAME"]       = build_settings.targetName;
1052 	env["DUB_TARGET_EXIT_STATUS"] = settings.targetExitStatus.text;
1053 	env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory;
1054 	env["DUB_MAIN_SOURCE_FILE"]  = build_settings.mainSourceFile;
1055 
1056 	env["DUB_CONFIG"]            = settings.config;
1057 	env["DUB_BUILD_TYPE"]        = settings.buildType;
1058 	env["DUB_BUILD_MODE"]        = to!string(settings.buildMode);
1059 	env["DUB_PACKAGE"]           = pack.name;
1060 	env["DUB_PACKAGE_DIR"]       = pack.path.toNativeString();
1061 	env["DUB_ROOT_PACKAGE"]      = proj.rootPackage.name;
1062 	env["DUB_ROOT_PACKAGE_DIR"]  = proj.rootPackage.path.toNativeString();
1063 	env["DUB_PACKAGE_VERSION"]   = pack.version_.toString();
1064 
1065 	env["DUB_COMBINED"]          = settings.combined?      "TRUE" : "";
1066 	env["DUB_RUN"]               = settings.run?           "TRUE" : "";
1067 	env["DUB_FORCE"]             = settings.force?         "TRUE" : "";
1068 	env["DUB_DIRECT"]            = settings.direct?        "TRUE" : "";
1069 	env["DUB_RDMD"]              = settings.rdmd?          "TRUE" : "";
1070 	env["DUB_TEMP_BUILD"]        = settings.tempBuild?     "TRUE" : "";
1071 	env["DUB_PARALLEL_BUILD"]    = settings.parallelBuild? "TRUE" : "";
1072 
1073 	env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" ");
1074 
1075 	auto cfgs = proj.getPackageConfigs(settings.platform, settings.config, true);
1076 	auto rootPackageBuildSettings = proj.rootPackage.getBuildSettings(settings.platform, cfgs[proj.rootPackage.name]);
1077 	env["DUB_ROOT_PACKAGE_TARGET_TYPE"] = to!string(rootPackageBuildSettings.targetType);
1078 	env["DUB_ROOT_PACKAGE_TARGET_PATH"] = rootPackageBuildSettings.targetPath;
1079 	env["DUB_ROOT_PACKAGE_TARGET_NAME"] = rootPackageBuildSettings.targetName;
1080 
1081 	const(string[string])[] typeEnvVars;
1082 	with (build_settings) final switch (type)
1083 	{
1084 		// pre/postGenerate don't have generateEnvironments, but reuse buildEnvironments
1085 		case CommandType.preGenerate: typeEnvVars = [environments, buildEnvironments, preGenerateEnvironments]; break;
1086 		case CommandType.postGenerate: typeEnvVars = [environments, buildEnvironments, postGenerateEnvironments]; break;
1087 		case CommandType.preBuild: typeEnvVars = [environments, buildEnvironments, preBuildEnvironments]; break;
1088 		case CommandType.postBuild: typeEnvVars = [environments, buildEnvironments, postBuildEnvironments]; break;
1089 		case CommandType.preRun: typeEnvVars = [environments, runEnvironments, preRunEnvironments]; break;
1090 		case CommandType.postRun: typeEnvVars = [environments, runEnvironments, postRunEnvironments]; break;
1091 	}
1092 
1093 	return [environment.toAA()] ~ env ~ typeEnvVars ~ extraVars;
1094 }
1095 
1096 string[string] collapseEnv(in string[string][] envs)
1097 {
1098 	string[string] ret;
1099 	foreach (subEnv; envs)
1100 	{
1101 		foreach (k, v; subEnv)
1102 			ret[k] = v;
1103 	}
1104 	return ret;
1105 }
1106 
1107 /// Type to specify where CLI commands that need to be run came from. Needed for
1108 /// proper substitution with support for the different environments.
1109 enum CommandType
1110 {
1111 	/// Defined in the preGenerateCommands setting
1112 	preGenerate,
1113 	/// Defined in the postGenerateCommands setting
1114 	postGenerate,
1115 	/// Defined in the preBuildCommands setting
1116 	preBuild,
1117 	/// Defined in the postBuildCommands setting
1118 	postBuild,
1119 	/// Defined in the preRunCommands setting
1120 	preRun,
1121 	/// Defined in the postRunCommands setting
1122 	postRun
1123 }
1124 
1125 private bool isRecursiveInvocation(string pack)
1126 {
1127 	import std.algorithm : canFind, splitter;
1128 	import std.process : environment;
1129 
1130 	return environment
1131 		.get("DUB_PACKAGES_USED", "")
1132 		.splitter(",")
1133 		.canFind(pack);
1134 }
1135 
1136 private void storeRecursiveInvokations(ref const(string[string])[] env, string[] packs)
1137 {
1138 	import std.algorithm : canFind, splitter;
1139 	import std.range : chain;
1140 	import std.process : environment;
1141 
1142 	env ~= [
1143 		"DUB_PACKAGES_USED": environment
1144 			.get("DUB_PACKAGES_USED", "")
1145 			.splitter(",")
1146 			.chain(packs)
1147 			.join(",")
1148 	];
1149 }
1150 
1151 version(Posix) {
1152     // https://github.com/dlang/dub/issues/2238
1153 	unittest {
1154 		import dub.internal.vibecompat.data.json : parseJsonString;
1155 		import dub.compilers.gdc : GDCCompiler;
1156 		import std.algorithm : canFind;
1157 		import std.path : absolutePath;
1158 		import std.file : rmdirRecurse, write;
1159 
1160 		mkdirRecurse("dubtest/preGen/source");
1161 		write("dubtest/preGen/source/foo.d", "");
1162 		scope(exit) rmdirRecurse("dubtest");
1163 
1164 		auto desc = parseJsonString(`{"name": "test", "targetType": "library", "preGenerateCommands": ["touch $PACKAGE_DIR/source/bar.d"]}`);
1165 		auto pack = new Package(desc, NativePath("dubtest/preGen".absolutePath));
1166 		auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false);
1167 		auto prj = new Project(pman, pack);
1168 
1169 		final static class TestCompiler : GDCCompiler {
1170 			override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) {
1171 				assert(false);
1172 			}
1173 			override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) {
1174 				assert(false);
1175 			}
1176 		}
1177 
1178 		GeneratorSettings settings;
1179 		settings.compiler = new TestCompiler;
1180 		settings.buildType = "debug";
1181 
1182 		final static class TestGenerator : ProjectGenerator {
1183 			this(Project project) {
1184 			 	super(project);
1185 			}
1186 
1187 			override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) {
1188                 import std.conv : text;
1189 				const sourceFiles = targets["test"].buildSettings.sourceFiles;
1190                 assert(sourceFiles.canFind("dubtest/preGen/source/bar.d".absolutePath), sourceFiles.text);
1191 			}
1192 		}
1193 
1194 		auto gen = new TestGenerator(prj);
1195 		gen.generate(settings);
1196 	}
1197 }