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 sub-package name is to avoid any ambiguity.
776  * Build artifacts are usually stored in a sub-folder 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 	NativePath overrideToolWorkingDirectory;
810 
811 	bool combined; // compile all in one go instead of each dependency separately
812 	bool filterVersions;
813 
814 	// only used for generator "build"
815 	bool run, force, rdmd, tempBuild, parallelBuild;
816 
817 	/// single file dub package
818 	bool single;
819 
820 	string[] runArgs;
821 	void delegate(int status, string output) compileCallback;
822 	void delegate(int status, string output) linkCallback;
823 	void delegate(int status, string output) runCallback;
824 
825 	/// Returns `overrideToolWorkingDirectory` or if that's not set, just the
826 	/// current working directory of the application. This may differ if dub is
827 	/// called with the `--root` parameter or when using DUB as a library.
828 	NativePath toolWorkingDirectory() const
829 	{
830 		return overrideToolWorkingDirectory is NativePath.init
831 			? getWorkingDirectory()
832 			: overrideToolWorkingDirectory;
833 	}
834 }
835 
836 
837 /**
838 	Determines the mode in which the compiler and linker are invoked.
839 */
840 enum BuildMode {
841 	separate,                 /// Compile and link separately
842 	allAtOnce,                /// Perform compile and link with a single compiler invocation
843 	singleFile,               /// Compile each file separately
844 	//multipleObjects,          /// Generate an object file per module
845 	//multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module
846 	//compileOnly               /// Do not invoke the linker (can be done using a post build command)
847 }
848 
849 
850 /**
851 	Creates a project generator of the given type for the specified project.
852 */
853 ProjectGenerator createProjectGenerator(string generator_type, Project project)
854 {
855 	assert(project !is null, "Project instance needed to create a generator.");
856 
857 	generator_type = generator_type.toLower();
858 	switch(generator_type) {
859 		default:
860 			throw new Exception("Unknown project generator: "~generator_type);
861 		case "build":
862 			logDebug("Creating build generator.");
863 			return new BuildGenerator(project);
864 		case "mono-d":
865 			throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead.");
866 		case "visuald":
867 			logDebug("Creating VisualD generator.");
868 			return new VisualDGenerator(project);
869 		case "sublimetext":
870 			logDebug("Creating SublimeText generator.");
871 			return new SublimeTextGenerator(project);
872 		case "cmake":
873 			logDebug("Creating CMake generator.");
874 			return new CMakeGenerator(project);
875 	}
876 }
877 
878 
879 /**
880 	Calls delegates on files and directories in the given path that match any globs.
881 */
882 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile, void delegate(string dir) addDir)
883 {
884 	import std.path : globMatch;
885 
886 	string[] globs;
887 	foreach (f; globList)
888 	{
889 		if (f.canFind("*", "?") ||
890 			(f.canFind("{") && f.balancedParens('{', '}')) ||
891 			(f.canFind("[") && f.balancedParens('[', ']')))
892 		{
893 			globs ~= f;
894 		}
895 		else
896 		{
897 			if (f.isDir)
898 				addDir(f);
899 			else
900 				addFile(f);
901 		}
902 	}
903 	if (globs.length) // Search all files for glob matches
904 		foreach (f; dirEntries(path.toNativeString(), SpanMode.breadth))
905 			foreach (glob; globs)
906 				if (f.name().globMatch(glob))
907 				{
908 					if (f.isDir)
909 						addDir(f);
910 					else
911 						addFile(f);
912 					break;
913 				}
914 }
915 
916 
917 /**
918 	Calls delegates on files in the given path that match any globs.
919 
920 	If a directory matches a glob, the delegate is called on all existing files inside it recursively
921 	in depth-first pre-order.
922 */
923 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile)
924 {
925 	void addDir(string dir)
926 	{
927 		foreach (f; dirEntries(dir, SpanMode.breadth))
928 			addFile(f);
929 	}
930 
931 	findFilesMatchingGlobs(path, globList, addFile, &addDir);
932 }
933 
934 
935 /**
936 	Runs pre-build commands and performs other required setup before project files are generated.
937 */
938 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
939 	in BuildSettings buildsettings)
940 {
941 	if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
942 		logInfo("Pre-gen", Color.light_green, "Running commands for %s", pack.name);
943 		runBuildCommands(CommandType.preGenerate, buildsettings.preGenerateCommands, pack, proj, settings, buildsettings);
944 	}
945 }
946 
947 /**
948 	Runs post-build commands and copies required files to the binary directory.
949 */
950 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
951 	in BuildSettings buildsettings, NativePath target_path, bool generate_binary)
952 {
953 	if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
954 		logInfo("Post-gen", Color.light_green, "Running commands for %s", pack.name);
955 		runBuildCommands(CommandType.postGenerate, buildsettings.postGenerateCommands, pack, proj, settings, buildsettings);
956 	}
957 
958 	if (generate_binary) {
959 		ensureDirectory(NativePath(buildsettings.targetPath));
960 
961 		if (buildsettings.copyFiles.length) {
962 			void copyFolderRec(NativePath folder, NativePath dstfolder)
963 			{
964 				ensureDirectory(dstfolder);
965 				foreach (de; iterateDirectory(folder.toNativeString())) {
966 					if (de.isDirectory) {
967 						copyFolderRec(folder ~ de.name, dstfolder ~ de.name);
968 					} else {
969 						try hardLinkFile(folder ~ de.name, dstfolder ~ de.name, true);
970 						catch (Exception e) {
971 							logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg);
972 						}
973 					}
974 				}
975 			}
976 
977 			void tryCopyDir(string file)
978 			{
979 				auto src = NativePath(file);
980 				if (!src.absolute) src = pack.path ~ src;
981 				auto dst = target_path ~ NativePath(file).head;
982 				if (src == dst) {
983 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
984 					return;
985 				}
986 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
987 				try {
988 					copyFolderRec(src, dst);
989 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
990 			}
991 
992 			void tryCopyFile(string file)
993 			{
994 				auto src = NativePath(file);
995 				if (!src.absolute) src = pack.path ~ src;
996 				auto dst = target_path ~ NativePath(file).head;
997 				if (src == dst) {
998 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
999 					return;
1000 				}
1001 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
1002 				try {
1003 					hardLinkFile(src, dst, true);
1004 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
1005 			}
1006 			logInfo("Copying files for %s...", pack.name);
1007 			findFilesMatchingGlobs(pack.path, buildsettings.copyFiles, &tryCopyFile, &tryCopyDir);
1008 		}
1009 
1010 	}
1011 }
1012 
1013 
1014 /** Runs a list of build commands for a particular package.
1015 
1016 	This function sets all DUB specific environment variables and makes sure
1017 	that recursive dub invocations are detected and don't result in infinite
1018 	command execution loops. The latter could otherwise happen when a command
1019 	runs "dub describe" or similar functionality.
1020 */
1021 void runBuildCommands(CommandType type, in string[] commands, in Package pack, in Project proj,
1022 	in GeneratorSettings settings, in BuildSettings build_settings, in string[string][] extraVars = null)
1023 {
1024 	import dub.internal.utils : runCommands;
1025 
1026 	auto env = makeCommandEnvironmentVariables(type, pack, proj, settings, build_settings, extraVars);
1027 	auto sub_commands = processVars(proj, pack, settings, commands, false, env);
1028 
1029 	auto depNames = proj.dependencies.map!((a) => a.name).array();
1030 	storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames);
1031 
1032 	runCommands(sub_commands, env.collapseEnv, pack.path().toString());
1033 }
1034 
1035 const(string[string])[] makeCommandEnvironmentVariables(CommandType type,
1036 	in Package pack, in Project proj, in GeneratorSettings settings,
1037 	in BuildSettings build_settings, in string[string][] extraVars = null)
1038 {
1039 	import dub.internal.utils : getDUBExePath;
1040 	import std.conv : to, text;
1041 	import std.process : environment, escapeShellFileName;
1042 
1043 	string[string] env;
1044 	// TODO: do more elaborate things here
1045 	// TODO: escape/quote individual items appropriately
1046 	env["VERSIONS"]              = join(build_settings.versions, " ");
1047 	env["LIBS"]                  = join(build_settings.libs, " ");
1048 	env["SOURCE_FILES"]          = join(build_settings.sourceFiles, " ");
1049 	env["IMPORT_PATHS"]          = join(build_settings.importPaths, " ");
1050 	env["STRING_IMPORT_PATHS"]   = join(build_settings.stringImportPaths, " ");
1051 
1052 	env["DC"]                    = settings.platform.compilerBinary;
1053 	env["DC_BASE"]               = settings.platform.compiler;
1054 	env["D_FRONTEND_VER"]        = to!string(settings.platform.frontendVersion);
1055 
1056 	env["DUB_EXE"]               = getDUBExePath(settings.platform.compilerBinary).toNativeString();
1057 	env["DUB_PLATFORM"]          = join(settings.platform.platform, " ");
1058 	env["DUB_ARCH"]              = join(settings.platform.architecture, " ");
1059 
1060 	env["DUB_TARGET_TYPE"]       = to!string(build_settings.targetType);
1061 	env["DUB_TARGET_PATH"]       = build_settings.targetPath;
1062 	env["DUB_TARGET_NAME"]       = build_settings.targetName;
1063 	env["DUB_TARGET_EXIT_STATUS"] = settings.targetExitStatus.text;
1064 	env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory;
1065 	env["DUB_MAIN_SOURCE_FILE"]  = build_settings.mainSourceFile;
1066 
1067 	env["DUB_CONFIG"]            = settings.config;
1068 	env["DUB_BUILD_TYPE"]        = settings.buildType;
1069 	env["DUB_BUILD_MODE"]        = to!string(settings.buildMode);
1070 	env["DUB_PACKAGE"]           = pack.name;
1071 	env["DUB_PACKAGE_DIR"]       = pack.path.toNativeString();
1072 	env["DUB_ROOT_PACKAGE"]      = proj.rootPackage.name;
1073 	env["DUB_ROOT_PACKAGE_DIR"]  = proj.rootPackage.path.toNativeString();
1074 	env["DUB_PACKAGE_VERSION"]   = pack.version_.toString();
1075 
1076 	env["DUB_COMBINED"]          = settings.combined?      "TRUE" : "";
1077 	env["DUB_RUN"]               = settings.run?           "TRUE" : "";
1078 	env["DUB_FORCE"]             = settings.force?         "TRUE" : "";
1079 	env["DUB_RDMD"]              = settings.rdmd?          "TRUE" : "";
1080 	env["DUB_TEMP_BUILD"]        = settings.tempBuild?     "TRUE" : "";
1081 	env["DUB_PARALLEL_BUILD"]    = settings.parallelBuild? "TRUE" : "";
1082 
1083 	env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" ");
1084 
1085 	auto cfgs = proj.getPackageConfigs(settings.platform, settings.config, true);
1086 	auto rootPackageBuildSettings = proj.rootPackage.getBuildSettings(settings.platform, cfgs[proj.rootPackage.name]);
1087 	env["DUB_ROOT_PACKAGE_TARGET_TYPE"] = to!string(rootPackageBuildSettings.targetType);
1088 	env["DUB_ROOT_PACKAGE_TARGET_PATH"] = rootPackageBuildSettings.targetPath;
1089 	env["DUB_ROOT_PACKAGE_TARGET_NAME"] = rootPackageBuildSettings.targetName;
1090 
1091 	const(string[string])[] typeEnvVars;
1092 	with (build_settings) final switch (type)
1093 	{
1094 		// pre/postGenerate don't have generateEnvironments, but reuse buildEnvironments
1095 		case CommandType.preGenerate: typeEnvVars = [environments, buildEnvironments, preGenerateEnvironments]; break;
1096 		case CommandType.postGenerate: typeEnvVars = [environments, buildEnvironments, postGenerateEnvironments]; break;
1097 		case CommandType.preBuild: typeEnvVars = [environments, buildEnvironments, preBuildEnvironments]; break;
1098 		case CommandType.postBuild: typeEnvVars = [environments, buildEnvironments, postBuildEnvironments]; break;
1099 		case CommandType.preRun: typeEnvVars = [environments, runEnvironments, preRunEnvironments]; break;
1100 		case CommandType.postRun: typeEnvVars = [environments, runEnvironments, postRunEnvironments]; break;
1101 	}
1102 
1103 	return [environment.toAA()] ~ env ~ typeEnvVars ~ extraVars;
1104 }
1105 
1106 string[string] collapseEnv(in string[string][] envs)
1107 {
1108 	string[string] ret;
1109 	foreach (subEnv; envs)
1110 	{
1111 		foreach (k, v; subEnv)
1112 			ret[k] = v;
1113 	}
1114 	return ret;
1115 }
1116 
1117 /// Type to specify where CLI commands that need to be run came from. Needed for
1118 /// proper substitution with support for the different environments.
1119 enum CommandType
1120 {
1121 	/// Defined in the preGenerateCommands setting
1122 	preGenerate,
1123 	/// Defined in the postGenerateCommands setting
1124 	postGenerate,
1125 	/// Defined in the preBuildCommands setting
1126 	preBuild,
1127 	/// Defined in the postBuildCommands setting
1128 	postBuild,
1129 	/// Defined in the preRunCommands setting
1130 	preRun,
1131 	/// Defined in the postRunCommands setting
1132 	postRun
1133 }
1134 
1135 private bool isRecursiveInvocation(string pack)
1136 {
1137 	import std.algorithm : canFind, splitter;
1138 	import std.process : environment;
1139 
1140 	return environment
1141 		.get("DUB_PACKAGES_USED", "")
1142 		.splitter(",")
1143 		.canFind(pack);
1144 }
1145 
1146 private void storeRecursiveInvokations(ref const(string[string])[] env, string[] packs)
1147 {
1148 	import std.algorithm : canFind, splitter;
1149 	import std.range : chain;
1150 	import std.process : environment;
1151 
1152 	env ~= [
1153 		"DUB_PACKAGES_USED": environment
1154 			.get("DUB_PACKAGES_USED", "")
1155 			.splitter(",")
1156 			.chain(packs)
1157 			.join(",")
1158 	];
1159 }
1160 
1161 version(Posix) {
1162     // https://github.com/dlang/dub/issues/2238
1163 	unittest {
1164 		import dub.internal.vibecompat.data.json : parseJsonString;
1165 		import dub.compilers.gdc : GDCCompiler;
1166 		import std.algorithm : canFind;
1167 		import std.path : absolutePath;
1168 		import std.file : rmdirRecurse, write;
1169 
1170 		mkdirRecurse("dubtest/preGen/source");
1171 		write("dubtest/preGen/source/foo.d", "");
1172 		scope(exit) rmdirRecurse("dubtest");
1173 
1174 		auto desc = parseJsonString(`{"name": "test", "targetType": "library", "preGenerateCommands": ["touch $PACKAGE_DIR/source/bar.d"]}`);
1175 		auto pack = new Package(desc, NativePath("dubtest/preGen".absolutePath));
1176 		auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false);
1177 		auto prj = new Project(pman, pack);
1178 
1179 		final static class TestCompiler : GDCCompiler {
1180 			override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd) {
1181 				assert(false);
1182 			}
1183 			override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd) {
1184 				assert(false);
1185 			}
1186 		}
1187 
1188 		GeneratorSettings settings;
1189 		settings.compiler = new TestCompiler;
1190 		settings.buildType = "debug";
1191 
1192 		final static class TestGenerator : ProjectGenerator {
1193 			this(Project project) {
1194 			 	super(project);
1195 			}
1196 
1197 			override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) {
1198                 import std.conv : text;
1199 				const sourceFiles = targets["test"].buildSettings.sourceFiles;
1200                 assert(sourceFiles.canFind("dubtest/preGen/source/bar.d".absolutePath), sourceFiles.text);
1201 			}
1202 		}
1203 
1204 		auto gen = new TestGenerator(prj);
1205 		gen.generate(settings);
1206 	}
1207 }