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.addCImportPaths(child.cImportPaths);
744 		parent.addStringImportPaths(child.stringImportPaths);
745 		parent.addInjectSourceFiles(child.injectSourceFiles);
746 		// linker stuff propagates up from static *and* dynamic library deps
747 		if (child.targetType == TargetType.staticLibrary || child.targetType == TargetType.dynamicLibrary) {
748 			parent.addSourceFiles(child.sourceFiles.filter!(f => isLinkerFile(platform, f)).array);
749 			parent.addLibs(child.libs);
750 			parent.addLFlags(child.lflags);
751 		}
752 	}
753 
754 	// configure targets for build types such as release, or unittest-cov
755 	private void addBuildTypeSettings(TargetInfo[string] targets, in GeneratorSettings settings)
756 	{
757 		foreach (ref ti; targets.byValue) {
758 			ti.buildSettings.add(settings.buildSettings);
759 
760 			// add build type settings and convert plain DFLAGS to build options
761 			m_project.addBuildTypeSettings(ti.buildSettings, settings, ti.pack is m_project.rootPackage);
762 			settings.compiler.extractBuildOptions(ti.buildSettings);
763 
764 			auto tt = ti.buildSettings.targetType;
765 			enforce (tt != TargetType.sourceLibrary || ti.pack !is m_project.rootPackage || (ti.buildSettings.options & BuildOption.syntaxOnly),
766 				format("Main package must not have target type \"%s\". Cannot build.", tt));
767 		}
768 	}
769 }
770 
771 /**
772  * Compute and returns the path were artifacts are stored for a given package
773  *
774  * Artifacts are stored in:
775  * `$DUB_HOME/cache/$PKG_NAME/$PKG_VERSION[/+$SUB_PKG_NAME]/`
776  * Note that the leading `+` in the sub-package name is to avoid any ambiguity.
777  *
778  * Dub writes in the returned path a Json description file of the available
779  * artifacts in this cache location. This Json file is read by 3rd party
780  * software (e.g. Meson). Returned path should therefore not change across
781  * future Dub versions.
782  *
783  * Build artifacts are usually stored in a sub-folder named "build",
784  * as their names are based on user-supplied values.
785  *
786  * Params:
787  *   cachePath = Base path at which the build cache is located,
788  *               e.g. `$HOME/.dub/cache/`
789  *	 pkg = The package. Cannot be `null`.
790  */
791 package(dub) NativePath packageCache(NativePath cachePath, in Package pkg)
792 {
793 	import std.algorithm.searching : findSplit;
794 
795 	assert(pkg !is null);
796 	assert(!cachePath.empty);
797 
798 	// For subpackages
799 	if (const names = pkg.name.findSplit(":"))
800 		return cachePath ~ names[0] ~ pkg.version_.toString()
801 			~ ("+" ~ names[2]);
802 	// For regular packages
803 	return cachePath ~ pkg.name ~ pkg.version_.toString();
804 }
805 
806 /**
807  * Compute and return the directory where a target should be cached.
808  *
809  * Params:
810  *   cachePath = Base path at which the build cache is located,
811  *               e.g. `$HOME/.dub/cache/`
812  *	 pkg = The package. Cannot be `null`.
813  *   buildId = The build identifier of the target.
814  */
815 package(dub) NativePath targetCacheDir(NativePath cachePath, in Package pkg, string buildId)
816 {
817 	return packageCache(cachePath, pkg) ~ "build" ~ buildId;
818 }
819 
820 /**
821  * Provides a unique (per build) identifier
822  *
823  * When building a package, it is important to have a unique but stable
824  * identifier to differentiate builds and allow their caching.
825  * This function provides such an identifier.
826  * Example:
827  * ```
828  * library-debug-Z7qINYX4IxM8muBSlyNGrw
829  * ```
830  */
831 package(dub) string computeBuildID(in BuildSettings buildsettings,
832 	in NativePath packagePath, string config, GeneratorSettings settings)
833 {
834 	import std.conv : to;
835 
836 	const(string[])[] hashing = [
837 		buildsettings.versions,
838 		buildsettings.debugVersions,
839 		buildsettings.dflags,
840 		buildsettings.lflags,
841 		buildsettings.stringImportPaths,
842 		buildsettings.importPaths,
843 		buildsettings.cImportPaths,
844 		settings.platform.architecture,
845 		[
846 			(cast(uint)(buildsettings.options & ~BuildOption.color)).to!string, // exclude color option from id
847 			// Needed for things such as `__FULL_FILE_PATH__`
848 			packagePath.toNativeString(),
849 			settings.platform.compilerBinary,
850 			settings.platform.compiler,
851 			settings.platform.compilerVersion,
852 		],
853 	];
854 
855 	return computeBuildName(config, settings, hashing);
856 }
857 
858 struct GeneratorSettings {
859 	NativePath cache;
860 	BuildPlatform platform;
861 	Compiler compiler;
862 	string config;
863 	string recipeName;
864 	string buildType;
865 	BuildSettings buildSettings;
866 	BuildMode buildMode = BuildMode.separate;
867 	int targetExitStatus;
868 	NativePath overrideToolWorkingDirectory;
869 
870 	bool combined; // compile all in one go instead of each dependency separately
871 	bool filterVersions;
872 
873 	// only used for generator "build"
874 	bool run, force, rdmd, tempBuild, parallelBuild;
875 
876 	/// single file dub package
877 	bool single;
878 
879 	/// build all dependencies for static libraries
880 	bool buildDeep;
881 
882 	string[] runArgs;
883 	void delegate(int status, string output) compileCallback;
884 	void delegate(int status, string output) linkCallback;
885 	void delegate(int status, string output) runCallback;
886 
887 	/// Returns `overrideToolWorkingDirectory` or if that's not set, just the
888 	/// current working directory of the application. This may differ if dub is
889 	/// called with the `--root` parameter or when using DUB as a library.
890 	NativePath toolWorkingDirectory() const
891 	{
892 		return overrideToolWorkingDirectory is NativePath.init
893 			? getWorkingDirectory()
894 			: overrideToolWorkingDirectory;
895 	}
896 }
897 
898 
899 /**
900 	Determines the mode in which the compiler and linker are invoked.
901 */
902 enum BuildMode {
903 	separate,                 /// Compile and link separately
904 	allAtOnce,                /// Perform compile and link with a single compiler invocation
905 	singleFile,               /// Compile each file separately
906 	//multipleObjects,          /// Generate an object file per module
907 	//multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module
908 	//compileOnly               /// Do not invoke the linker (can be done using a post build command)
909 }
910 
911 
912 /**
913 	Creates a project generator of the given type for the specified project.
914 */
915 ProjectGenerator createProjectGenerator(string generator_type, Project project)
916 {
917 	assert(project !is null, "Project instance needed to create a generator.");
918 
919 	generator_type = generator_type.toLower();
920 	switch(generator_type) {
921 		default:
922 			throw new Exception("Unknown project generator: "~generator_type);
923 		case "build":
924 			logDebug("Creating build generator.");
925 			return new BuildGenerator(project);
926 		case "mono-d":
927 			throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead.");
928 		case "visuald":
929 			logDebug("Creating VisualD generator.");
930 			return new VisualDGenerator(project);
931 		case "sublimetext":
932 			logDebug("Creating SublimeText generator.");
933 			return new SublimeTextGenerator(project);
934 		case "cmake":
935 			logDebug("Creating CMake generator.");
936 			return new CMakeGenerator(project);
937 	}
938 }
939 
940 
941 /**
942 	Calls delegates on files and directories in the given path that match any globs.
943 */
944 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile, void delegate(string dir) addDir)
945 {
946 	import std.path : globMatch;
947 
948 	string[] globs;
949 	foreach (f; globList)
950 	{
951 		if (f.canFind("*", "?") ||
952 			(f.canFind("{") && f.balancedParens('{', '}')) ||
953 			(f.canFind("[") && f.balancedParens('[', ']')))
954 		{
955 			globs ~= f;
956 		}
957 		else
958 		{
959 			if (f.isDir)
960 				addDir(f);
961 			else
962 				addFile(f);
963 		}
964 	}
965 	if (globs.length) // Search all files for glob matches
966 		foreach (f; dirEntries(path.toNativeString(), SpanMode.breadth))
967 			foreach (glob; globs)
968 				if (f.name().globMatch(glob))
969 				{
970 					if (f.isDir)
971 						addDir(f);
972 					else
973 						addFile(f);
974 					break;
975 				}
976 }
977 
978 
979 /**
980 	Calls delegates on files in the given path that match any globs.
981 
982 	If a directory matches a glob, the delegate is called on all existing files inside it recursively
983 	in depth-first pre-order.
984 */
985 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile)
986 {
987 	void addDir(string dir)
988 	{
989 		foreach (f; dirEntries(dir, SpanMode.breadth))
990 			addFile(f);
991 	}
992 
993 	findFilesMatchingGlobs(path, globList, addFile, &addDir);
994 }
995 
996 
997 /**
998 	Runs pre-build commands and performs other required setup before project files are generated.
999 */
1000 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
1001 	in BuildSettings buildsettings)
1002 {
1003 	if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
1004 		logInfo("Pre-gen", Color.light_green, "Running commands for %s", pack.name);
1005 		runBuildCommands(CommandType.preGenerate, buildsettings.preGenerateCommands, pack, proj, settings, buildsettings);
1006 	}
1007 }
1008 
1009 /**
1010 	Runs post-build commands and copies required files to the binary directory.
1011 */
1012 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
1013 	in BuildSettings buildsettings, NativePath target_path, bool generate_binary)
1014 {
1015 	if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
1016 		logInfo("Post-gen", Color.light_green, "Running commands for %s", pack.name);
1017 		runBuildCommands(CommandType.postGenerate, buildsettings.postGenerateCommands, pack, proj, settings, buildsettings);
1018 	}
1019 
1020 	if (generate_binary) {
1021 		if (!settings.tempBuild)
1022 			ensureDirectory(NativePath(buildsettings.targetPath));
1023 
1024 		if (buildsettings.copyFiles.length) {
1025 			void copyFolderRec(NativePath folder, NativePath dstfolder)
1026 			{
1027 				ensureDirectory(dstfolder);
1028 				foreach (de; iterateDirectory(folder)) {
1029 					if (de.isDirectory) {
1030 						copyFolderRec(folder ~ de.name, dstfolder ~ de.name);
1031 					} else {
1032 						try hardLinkFile(folder ~ de.name, dstfolder ~ de.name, true);
1033 						catch (Exception e) {
1034 							logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg);
1035 						}
1036 					}
1037 				}
1038 			}
1039 
1040 			void tryCopyDir(string file)
1041 			{
1042 				auto src = NativePath(file);
1043 				if (!src.absolute) src = pack.path ~ src;
1044 				auto dst = target_path ~ NativePath(file).head;
1045 				if (src == dst) {
1046 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
1047 					return;
1048 				}
1049 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
1050 				try {
1051 					copyFolderRec(src, dst);
1052 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
1053 			}
1054 
1055 			void tryCopyFile(string file)
1056 			{
1057 				auto src = NativePath(file);
1058 				if (!src.absolute) src = pack.path ~ src;
1059 				auto dst = target_path ~ NativePath(file).head;
1060 				if (src == dst) {
1061 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
1062 					return;
1063 				}
1064 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
1065 				try {
1066 					hardLinkFile(src, dst, true);
1067 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
1068 			}
1069 			logInfo("Copying files for %s...", pack.name);
1070 			findFilesMatchingGlobs(pack.path, buildsettings.copyFiles, &tryCopyFile, &tryCopyDir);
1071 		}
1072 
1073 	}
1074 }
1075 
1076 
1077 /** Runs a list of build commands for a particular package.
1078 
1079 	This function sets all DUB specific environment variables and makes sure
1080 	that recursive dub invocations are detected and don't result in infinite
1081 	command execution loops. The latter could otherwise happen when a command
1082 	runs "dub describe" or similar functionality.
1083 */
1084 void runBuildCommands(CommandType type, in string[] commands, in Package pack, in Project proj,
1085 	in GeneratorSettings settings, in BuildSettings build_settings, in string[string][] extraVars = null)
1086 {
1087 	import dub.internal.utils : runCommands;
1088 
1089 	auto env = makeCommandEnvironmentVariables(type, pack, proj, settings, build_settings, extraVars);
1090 	auto sub_commands = processVars(proj, pack, settings, commands, false, env);
1091 
1092 	auto depNames = proj.dependencies.map!((a) => a.name).array();
1093 	storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames);
1094 
1095 	runCommands(sub_commands, env.collapseEnv, pack.path().toString());
1096 }
1097 
1098 const(string[string])[] makeCommandEnvironmentVariables(CommandType type,
1099 	in Package pack, in Project proj, in GeneratorSettings settings,
1100 	in BuildSettings build_settings, in string[string][] extraVars = null)
1101 {
1102 	import dub.internal.utils : getDUBExePath;
1103 	import std.conv : to, text;
1104 	import std.process : environment, escapeShellFileName;
1105 
1106 	string[string] env;
1107 	// TODO: do more elaborate things here
1108 	// TODO: escape/quote individual items appropriately
1109 	env["VERSIONS"]              = join(build_settings.versions, " ");
1110 	env["LIBS"]                  = join(build_settings.libs, " ");
1111 	env["SOURCE_FILES"]          = join(build_settings.sourceFiles, " ");
1112 	env["IMPORT_PATHS"]          = join(build_settings.importPaths, " ");
1113 	env["C_IMPORT_PATHS"]        = join(build_settings.cImportPaths, " ");
1114 	env["STRING_IMPORT_PATHS"]   = join(build_settings.stringImportPaths, " ");
1115 
1116 	env["DC"]                    = settings.platform.compilerBinary;
1117 	env["DC_BASE"]               = settings.platform.compiler;
1118 	env["D_FRONTEND_VER"]        = to!string(settings.platform.frontendVersion);
1119 
1120 	env["DUB_EXE"]               = getDUBExePath(settings.platform.compilerBinary).toNativeString();
1121 	env["DUB_PLATFORM"]          = join(settings.platform.platform, " ");
1122 	env["DUB_ARCH"]              = join(settings.platform.architecture, " ");
1123 
1124 	env["DUB_TARGET_TYPE"]       = to!string(build_settings.targetType);
1125 	env["DUB_TARGET_PATH"]       = build_settings.targetPath;
1126 	env["DUB_TARGET_NAME"]       = build_settings.targetName;
1127 	env["DUB_TARGET_EXIT_STATUS"] = settings.targetExitStatus.text;
1128 	env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory;
1129 	env["DUB_MAIN_SOURCE_FILE"]  = build_settings.mainSourceFile;
1130 
1131 	env["DUB_CONFIG"]            = settings.config;
1132 	env["DUB_BUILD_TYPE"]        = settings.buildType;
1133 	env["DUB_BUILD_MODE"]        = to!string(settings.buildMode);
1134 	env["DUB_PACKAGE"]           = pack.name;
1135 	env["DUB_PACKAGE_DIR"]       = pack.path.toNativeString();
1136 	env["DUB_ROOT_PACKAGE"]      = proj.rootPackage.name;
1137 	env["DUB_ROOT_PACKAGE_DIR"]  = proj.rootPackage.path.toNativeString();
1138 	env["DUB_PACKAGE_VERSION"]   = pack.version_.toString();
1139 
1140 	env["DUB_COMBINED"]          = settings.combined?      "TRUE" : "";
1141 	env["DUB_RUN"]               = settings.run?           "TRUE" : "";
1142 	env["DUB_FORCE"]             = settings.force?         "TRUE" : "";
1143 	env["DUB_RDMD"]              = settings.rdmd?          "TRUE" : "";
1144 	env["DUB_TEMP_BUILD"]        = settings.tempBuild?     "TRUE" : "";
1145 	env["DUB_PARALLEL_BUILD"]    = settings.parallelBuild? "TRUE" : "";
1146 
1147 	env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" ");
1148 
1149 	auto cfgs = proj.getPackageConfigs(settings.platform, settings.config, true);
1150 	auto rootPackageBuildSettings = proj.rootPackage.getBuildSettings(settings.platform, cfgs[proj.rootPackage.name]);
1151 	env["DUB_ROOT_PACKAGE_TARGET_TYPE"] = to!string(rootPackageBuildSettings.targetType);
1152 	env["DUB_ROOT_PACKAGE_TARGET_PATH"] = rootPackageBuildSettings.targetPath;
1153 	env["DUB_ROOT_PACKAGE_TARGET_NAME"] = rootPackageBuildSettings.targetName;
1154 
1155 	const(string[string])[] typeEnvVars;
1156 	with (build_settings) final switch (type)
1157 	{
1158 		// pre/postGenerate don't have generateEnvironments, but reuse buildEnvironments
1159 		case CommandType.preGenerate: typeEnvVars = [environments, buildEnvironments, preGenerateEnvironments]; break;
1160 		case CommandType.postGenerate: typeEnvVars = [environments, buildEnvironments, postGenerateEnvironments]; break;
1161 		case CommandType.preBuild: typeEnvVars = [environments, buildEnvironments, preBuildEnvironments]; break;
1162 		case CommandType.postBuild: typeEnvVars = [environments, buildEnvironments, postBuildEnvironments]; break;
1163 		case CommandType.preRun: typeEnvVars = [environments, runEnvironments, preRunEnvironments]; break;
1164 		case CommandType.postRun: typeEnvVars = [environments, runEnvironments, postRunEnvironments]; break;
1165 	}
1166 
1167 	return [environment.toAA()] ~ env ~ typeEnvVars ~ extraVars;
1168 }
1169 
1170 string[string] collapseEnv(in string[string][] envs)
1171 {
1172 	string[string] ret;
1173 	foreach (subEnv; envs)
1174 	{
1175 		foreach (k, v; subEnv)
1176 			ret[k] = v;
1177 	}
1178 	return ret;
1179 }
1180 
1181 /// Type to specify where CLI commands that need to be run came from. Needed for
1182 /// proper substitution with support for the different environments.
1183 enum CommandType
1184 {
1185 	/// Defined in the preGenerateCommands setting
1186 	preGenerate,
1187 	/// Defined in the postGenerateCommands setting
1188 	postGenerate,
1189 	/// Defined in the preBuildCommands setting
1190 	preBuild,
1191 	/// Defined in the postBuildCommands setting
1192 	postBuild,
1193 	/// Defined in the preRunCommands setting
1194 	preRun,
1195 	/// Defined in the postRunCommands setting
1196 	postRun
1197 }
1198 
1199 private bool isRecursiveInvocation(string pack)
1200 {
1201 	import std.algorithm : canFind, splitter;
1202 	import std.process : environment;
1203 
1204 	return environment
1205 		.get("DUB_PACKAGES_USED", "")
1206 		.splitter(",")
1207 		.canFind(pack);
1208 }
1209 
1210 private void storeRecursiveInvokations(ref const(string[string])[] env, string[] packs)
1211 {
1212 	import std.algorithm : canFind, splitter;
1213 	import std.range : chain;
1214 	import std.process : environment;
1215 
1216 	env ~= [
1217 		"DUB_PACKAGES_USED": environment
1218 			.get("DUB_PACKAGES_USED", "")
1219 			.splitter(",")
1220 			.chain(packs)
1221 			.join(",")
1222 	];
1223 }
1224 
1225 version(Posix) {
1226     // https://github.com/dlang/dub/issues/2238
1227 	unittest {
1228 		import dub.internal.vibecompat.data.json : parseJsonString;
1229 		import dub.compilers.gdc : GDCCompiler;
1230 		import std.algorithm : canFind;
1231 		import std.path : absolutePath;
1232 		import std.file : rmdirRecurse, write;
1233 
1234 		mkdirRecurse("dubtest/preGen/source");
1235 		write("dubtest/preGen/source/foo.d", "");
1236 		scope(exit) rmdirRecurse("dubtest");
1237 
1238 		auto desc = parseJsonString(`{"name": "test", "targetType": "library", "preGenerateCommands": ["touch $PACKAGE_DIR/source/bar.d"]}`);
1239 		auto pack = new Package(desc, NativePath("dubtest/preGen".absolutePath));
1240 		auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false);
1241 		auto prj = new Project(pman, pack);
1242 
1243 		final static class TestCompiler : GDCCompiler {
1244 			override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd) {
1245 				assert(false);
1246 			}
1247 			override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd) {
1248 				assert(false);
1249 			}
1250 		}
1251 
1252 		GeneratorSettings settings;
1253 		settings.compiler = new TestCompiler;
1254 		settings.buildType = "debug";
1255 
1256 		final static class TestGenerator : ProjectGenerator {
1257 			this(Project project) {
1258 			 	super(project);
1259 			}
1260 
1261 			override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) {
1262                 import std.conv : text;
1263 				const sourceFiles = targets["test"].buildSettings.sourceFiles;
1264                 assert(sourceFiles.canFind("dubtest/preGen/source/bar.d".absolutePath), sourceFiles.text);
1265 			}
1266 		}
1267 
1268 		auto gen = new TestGenerator(prj);
1269 		gen.generate(settings);
1270 	}
1271 }