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 		// Handle the destination directory being overridden.
263 		auto cwd = genSettings.toolWorkingDirectory;
264 		auto dst = genSettings.destinationDirectory;
265 		if (!dst.empty) {
266 			auto targetPath = NativePath(roottarget.buildSettings.targetPath);
267 			auto workdirPath = NativePath(roottarget.buildSettings.workingDirectory);
268 			auto relTargetPath = targetPath.absolute ? targetPath.relativeTo(cwd) : targetPath;
269 			auto relWorkdirPath = workdirPath.absolute ? workdirPath.relativeTo(cwd) : workdirPath;
270 			auto relDestPath = dst.absolute ? dst.relativeTo(cwd) : dst;
271 
272 			roottarget.buildSettings.targetPath = (relDestPath ~ relTargetPath).toString();
273 			roottarget.buildSettings.workingDirectory = (relDestPath ~ relWorkdirPath).toString();
274 		}
275 
276 		// 0. do shallow configuration (not including dependencies) of all packages
277 		TargetType determineTargetType(const ref TargetInfo ti, const ref GeneratorSettings genSettings)
278 		{
279 			TargetType tt = ti.buildSettings.targetType;
280 			if (ti.pack is rootPackage) {
281 				if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary;
282 			} else {
283 				if (tt == TargetType.autodetect || tt == TargetType.library) tt = genSettings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary;
284 				else if (genSettings.platform.architecture.canFind("x86_omf") && tt == TargetType.dynamicLibrary) {
285 					// Unfortunately we cannot remove this check for OMF targets,
286 					// due to Optlink not producing shared libraries without a lot of user intervention.
287 					// For other targets, say MSVC it'll do the right thing for the most part,
288 					// export is still a problem as of this writing, which means static libraries cannot have linking to them removed.
289 					// But that is for the most part up to the developer, to get it working correctly.
290 
291 					logWarn("Dynamic libraries are not yet supported as dependencies for Windows target OMF - building as static library.");
292 					tt = TargetType.staticLibrary;
293 				}
294 			}
295 			if (tt != TargetType.none && tt != TargetType.sourceLibrary && ti.buildSettings.sourceFiles.empty) {
296 				logWarn(`Configuration [%s] of package %s contains no source files. Please add %s to its package description to avoid building it.`,
297 						ti.config.color(Color.blue), ti.pack.name.color(Mode.bold), `{"targetType": "none"}`.color(Mode.bold));
298 				tt = TargetType.none;
299 			}
300 			return tt;
301 		}
302 
303 		string[] mainSourceFiles;
304 		bool[string] hasOutput;
305 
306 		foreach (ref ti; targets.byValue)
307 		{
308 			auto bs = &ti.buildSettings;
309 			// determine the actual target type
310 			bs.targetType = determineTargetType(ti, genSettings);
311 
312 			switch (bs.targetType)
313 			{
314 			case TargetType.none:
315 				// ignore any build settings for targetType none (only dependencies will be processed)
316 				*bs = BuildSettings.init;
317 				bs.targetType = TargetType.none;
318 				break;
319 
320 			case TargetType.executable:
321 				break;
322 
323 			case TargetType.dynamicLibrary:
324 				// set -fPIC for dynamic library builds
325 				ti.buildSettings.addOptions(BuildOption.pic);
326 				goto default;
327 
328 			default:
329 				// remove any mainSourceFile from non-executable builds
330 				if (bs.mainSourceFile.length) {
331 					bs.sourceFiles = bs.sourceFiles.remove!(f => f == bs.mainSourceFile);
332 					mainSourceFiles ~= bs.mainSourceFile;
333 				}
334 				break;
335 			}
336 			bool generatesBinary = bs.targetType != TargetType.sourceLibrary && bs.targetType != TargetType.none;
337 			hasOutput[ti.pack.name] = generatesBinary || ti.pack is rootPackage;
338 		}
339 
340 		// add main source files to root executable
341 		{
342 			auto bs = &roottarget.buildSettings;
343 			if (bs.targetType == TargetType.executable || genSettings.single) bs.addSourceFiles(mainSourceFiles);
344 		}
345 
346 		if (genSettings.filterVersions)
347 			foreach (ref ti; targets.byValue)
348 				inferVersionFilters(ti);
349 
350 		// mark packages as visited (only used during upwards propagation)
351 		void[0][Package] visited;
352 
353 		// collect all dependencies
354 		void collectDependencies(Package pack, ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0)
355 		{
356 			// use `visited` here as pkgs cannot depend on themselves
357 			if (pack in visited)
358 				return;
359 			// transitive dependencies must be visited multiple times, see #1350
360 			immutable transitive = !hasOutput[pack.name];
361 			if (!transitive)
362 				visited[pack] = typeof(visited[pack]).init;
363 
364 			auto bs = &ti.buildSettings;
365 			if (hasOutput[pack.name])
366 				logDebug("%sConfiguring target %s (%s %s %s)", ' '.repeat(2 * level), pack.name, bs.targetType, bs.targetPath, bs.targetName);
367 			else
368 				logDebug("%sConfiguring target without output %s", ' '.repeat(2 * level), pack.name);
369 
370 			// get specified dependencies, e.g. vibe-d ~0.8.1
371 			auto deps = pack.getDependencies(targets[pack.name].config);
372 			logDebug("deps: %s -> %(%s, %)", pack.name, deps.byKey);
373 			foreach (depname; deps.keys.sort())
374 			{
375 				auto depspec = deps[depname];
376 				// get selected package for that dependency, e.g. vibe-d 0.8.2-beta.2
377 				auto deppack = m_project.getDependency(depname, depspec.optional);
378 				if (deppack is null) continue; // optional and not selected
379 
380 				// if dependency has no output
381 				if (!hasOutput[depname]) {
382 					// add itself
383 					ti.packages ~= deppack;
384 					// and it's transitive dependencies to current target
385 					collectDependencies(deppack, ti, targets, level + 1);
386 					continue;
387 				}
388 				auto depti = &targets[depname];
389 				const depbs = &depti.buildSettings;
390 				if (depbs.targetType == TargetType.executable && ti.buildSettings.targetType != TargetType.none)
391 					continue;
392 
393 				// add to (link) dependencies
394 				ti.dependencies ~= depname;
395 				ti.linkDependencies ~= depname;
396 
397 				// recurse
398 				collectDependencies(deppack, *depti, targets, level + 1);
399 
400 				// also recursively add all link dependencies of static *and* dynamic libraries
401 				// preserve topological sorting of dependencies for correct link order
402 				if (depbs.targetType == TargetType.staticLibrary || depbs.targetType == TargetType.dynamicLibrary)
403 					ti.linkDependencies = ti.linkDependencies.filter!(d => !depti.linkDependencies.canFind(d)).array ~ depti.linkDependencies;
404 			}
405 
406 			enforce(!(ti.buildSettings.targetType == TargetType.none && ti.dependencies.empty),
407 				"Package with target type \"none\" must have dependencies to build.");
408 		}
409 
410 		collectDependencies(rootPackage, *roottarget, targets);
411 		visited.clear();
412 
413 		// 1. downwards inherits versions, debugVersions, and inheritable build settings
414 		static void configureDependencies(const scope ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0)
415 		{
416 
417 			// do not use `visited` here as dependencies must inherit
418 			// configurations from *all* of their parents
419 			logDebug("%sConfigure dependencies of %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies);
420 			foreach (depname; ti.dependencies)
421 			{
422 				auto pti = &targets[depname];
423 				mergeFromDependent(ti.buildSettings, pti.buildSettings);
424 				configureDependencies(*pti, targets, level + 1);
425 			}
426 		}
427 
428 		configureDependencies(*roottarget, targets);
429 
430 		// 2. add Have_dependency_xyz for all direct dependencies of a target
431 		// (includes incorporated non-target dependencies and their dependencies)
432 		foreach (ref ti; targets.byValue)
433 		{
434 			import std.range : chain;
435 			import dub.internal.utils : stripDlangSpecialChars;
436 
437 			auto bs = &ti.buildSettings;
438 			auto pkgnames = ti.packages.map!(p => p.name).chain(ti.dependencies);
439 			bs.addVersions(pkgnames.map!(pn => "Have_" ~ stripDlangSpecialChars(pn)).array);
440 		}
441 
442 		// 3. upwards inherit full build configurations (import paths, versions, debugVersions, versionFilters, importPaths, ...)
443 
444 		// We do a check for if any dependency uses final binary injection source files,
445 		// otherwise can ignore that bit of workload entirely
446 		bool skipFinalBinaryMerging = true;
447 
448 		void configureDependents(ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0)
449 		{
450 			// use `visited` here as pkgs cannot depend on themselves
451 			if (ti.pack in visited)
452 				return;
453 			visited[ti.pack] = typeof(visited[ti.pack]).init;
454 
455 			logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies);
456 			// embedded non-binary dependencies
457 			foreach (deppack; ti.packages[1 .. $])
458 				ti.buildSettings.add(targets[deppack.name].buildSettings);
459 
460 			// binary dependencies
461 			foreach (depname; ti.dependencies)
462 			{
463 				auto pdepti = &targets[depname];
464 
465 				configureDependents(*pdepti, targets, level + 1);
466 				mergeFromDependency(pdepti.buildSettings, ti.buildSettings, genSettings.platform);
467 
468 				if (!pdepti.buildSettings.injectSourceFiles.empty)
469 					skipFinalBinaryMerging = false;
470 			}
471 		}
472 
473 		configureDependents(*roottarget, targets);
474 		visited.clear();
475 
476 		// 4. As an extension to configureDependents we need to copy any injectSourceFiles
477 		// in our dependencies (ignoring targetType)
478 		void configureDependentsFinalImages(ref TargetInfo ti, TargetInfo[string] targets, ref TargetInfo finalBinaryTarget, size_t level = 0)
479 		{
480 			// use `visited` here as pkgs cannot depend on themselves
481 			if (ti.pack in visited)
482 				return;
483 			visited[ti.pack] = typeof(visited[ti.pack]).init;
484 
485 			logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %) for injectSourceFiles", ' '.repeat(2 * level), ti.pack.name, ti.dependencies);
486 
487 			foreach (depname; ti.dependencies)
488 			{
489 				auto pdepti = &targets[depname];
490 
491 				if (!pdepti.buildSettings.injectSourceFiles.empty)
492 					finalBinaryTarget.buildSettings.addSourceFiles(pdepti.buildSettings.injectSourceFiles);
493 
494 				configureDependentsFinalImages(*pdepti, targets, finalBinaryTarget, level + 1);
495 			}
496 		}
497 
498 		if (!skipFinalBinaryMerging)
499 		{
500 			foreach (ref target; targets.byValue)
501 			{
502 				switch (target.buildSettings.targetType)
503 				{
504 					case TargetType.executable:
505 					case TargetType.dynamicLibrary:
506 					configureDependentsFinalImages(target, targets, target);
507 
508 					// We need to clear visited for each target that is executable dynamicLibrary
509 					// due to this process needing to be recursive based upon the final binary targets.
510 					visited.clear();
511 					break;
512 
513 					default:
514 					break;
515 				}
516 			}
517 		}
518 
519 		// 5. Filter applicable version and debug version identifiers
520 		if (genSettings.filterVersions)
521 		{
522 			foreach (name, ref ti; targets)
523 			{
524 				import std.algorithm.sorting : partition;
525 
526 				auto bs = &ti.buildSettings;
527 
528 				auto filtered = bs.versions.partition!(v => bs.versionFilters.canFind(v));
529 				logDebug("Filtering out unused versions for %s: %s", name, filtered);
530 				bs.versions = bs.versions[0 .. $ - filtered.length];
531 
532 				filtered = bs.debugVersions.partition!(v => bs.debugVersionFilters.canFind(v));
533 				logDebug("Filtering out unused debug versions for %s: %s", name, filtered);
534 				bs.debugVersions = bs.debugVersions[0 .. $ - filtered.length];
535 			}
536 		}
537 
538 		// 6. override string import files in dependencies
539 		static void overrideStringImports(ref TargetInfo target,
540 			ref TargetInfo parent, TargetInfo[string] targets, string[] overrides)
541 		{
542 			// Since string import paths are inherited from dependencies in the
543 			// inheritance step above (step 3), it is guaranteed that all
544 			// following dependencies will not have string import paths either,
545 			// so we can skip the recursion here
546 			if (!target.buildSettings.stringImportPaths.length)
547 				return;
548 
549 			// do not use visited here as string imports can be overridden by *any* parent
550 			//
551 			// special support for overriding string imports in parent packages
552 			// this is a candidate for deprecation, once an alternative approach
553 			// has been found
554 			bool any_override = false;
555 
556 			// override string import files (used for up to date checking)
557 			foreach (ref f; target.buildSettings.stringImportFiles)
558 			{
559 				foreach (o; overrides)
560 				{
561 					NativePath op;
562 					if (f != o && NativePath(f).head == (op = NativePath(o)).head) {
563 						logDebug("string import %s overridden by %s", f, o);
564 						f = o;
565 						any_override = true;
566 					}
567 				}
568 			}
569 
570 			// override string import paths by prepending to the list, in
571 			// case there is any overlapping file
572 			if (any_override)
573 				target.buildSettings.prependStringImportPaths(parent.buildSettings.stringImportPaths);
574 
575 			// add all files to overrides for recursion
576 			overrides ~= target.buildSettings.stringImportFiles;
577 
578 			// recursively override all dependencies with the accumulated files/paths
579 			foreach (depname; target.dependencies)
580 				overrideStringImports(targets[depname], target, targets, overrides);
581 		}
582 
583 		// push string import paths/files down to all direct and indirect
584 		// dependencies, overriding their own
585 		foreach (depname; roottarget.dependencies)
586 			overrideStringImports(targets[depname], *roottarget, targets,
587 				roottarget.buildSettings.stringImportFiles);
588 
589 		// 7. downwards inherits dependency build settings
590 		static void applyForcedSettings(const scope ref TargetInfo ti, TargetInfo[string] targets,
591 											BuildSettings[string] dependBS, size_t level = 0)
592 		{
593 
594 			static void apply(const scope ref BuildSettings forced, ref BuildSettings child) {
595 				child.addDFlags(forced.dflags);
596 			}
597 
598 			// apply to all dependencies
599 			foreach (depname; ti.dependencies)
600 			{
601 				BuildSettings forcedSettings;
602 				auto pti = &targets[depname];
603 
604 				// fetch the forced dependency build settings
605 				if (auto matchedSettings = depname in dependBS)
606 					forcedSettings = *matchedSettings;
607 				else if (auto matchedSettings = "*" in dependBS)
608 					forcedSettings = *matchedSettings;
609 
610 				apply(forcedSettings, pti.buildSettings);
611 
612 				// recursively apply forced settings to all dependencies of his dependency
613 				applyForcedSettings(*pti, targets, ["*" : forcedSettings], level + 1);
614 			}
615 		}
616 
617 		// apply both top level and configuration level forced dependency build settings
618 		void applyDependencyBuildSettings (const RecipeDependency[string] configured_dbs)
619 		{
620 			BuildSettings[string] dependencyBuildSettings;
621 			foreach (key, value; configured_dbs)
622 			{
623 				BuildSettings buildSettings;
624 				if (auto target = key in targets)
625 				{
626 					// get platform specific build settings and process dub variables (BuildSettingsTemplate => BuildSettings)
627 					value.settings.getPlatformSettings(buildSettings, genSettings.platform, target.pack.path);
628 					buildSettings.processVars(m_project, target.pack, buildSettings, genSettings, true);
629 					dependencyBuildSettings[key] = buildSettings;
630 				}
631 			}
632 			applyForcedSettings(*roottarget, targets, dependencyBuildSettings);
633 		}
634 		applyDependencyBuildSettings(rootPackage.recipe.buildSettings.dependencies);
635 		applyDependencyBuildSettings(rootPackage.getBuildSettings(genSettings.config).dependencies);
636 
637 		// remove targets without output
638 		foreach (name; targets.keys)
639 		{
640 			if (!hasOutput[name])
641 				targets.remove(name);
642 		}
643 	}
644 
645 	// infer applicable version identifiers
646 	private static void inferVersionFilters(ref TargetInfo ti)
647 	{
648 		import std.algorithm.searching : any;
649 		import std.file : timeLastModified;
650 		import std.path : extension;
651 		import std.range : chain;
652 		import std.regex : ctRegex, matchAll;
653 		import std.stdio : File;
654 		import std.datetime : Clock, SysTime, UTC;
655 		import dub.compilers.utils : isLinkerFile;
656 		import dub.internal.vibecompat.data.json : Json, JSONException;
657 
658 		auto bs = &ti.buildSettings;
659 
660 		// only infer if neither version filters are specified explicitly
661 		if (bs.versionFilters.length || bs.debugVersionFilters.length)
662 		{
663 			logDebug("Using specified versionFilters for %s: %s %s", ti.pack.name,
664 				bs.versionFilters, bs.debugVersionFilters);
665 			return;
666 		}
667 
668 		// check all existing source files for version identifiers
669 		static immutable dexts = [".d", ".di"];
670 		auto srcs = chain(bs.sourceFiles, bs.importFiles, bs.stringImportFiles)
671 			.filter!(f => dexts.canFind(f.extension)).filter!exists;
672 		// try to load cached filters first
673 		const cacheFilePath = packageCache(NativePath(ti.buildSettings.targetPath), ti.pack)
674 			~ "metadata_cache.json";
675 		enum silent_fail = true;
676 		auto cache = jsonFromFile(cacheFilePath, silent_fail);
677 		try
678 		{
679 			auto cachedFilters = cache["versionFilters"];
680 			if (cachedFilters.type != Json.Type.undefined)
681 				cachedFilters = cachedFilters[ti.config];
682 			if (cachedFilters.type != Json.Type.undefined)
683 			{
684 				immutable mtime = SysTime.fromISOExtString(cachedFilters["mtime"].get!string);
685 				if (!srcs.any!(src => src.timeLastModified > mtime))
686 				{
687 					auto versionFilters = cachedFilters["versions"][].map!(j => j.get!string).array;
688 					auto debugVersionFilters = cachedFilters["debugVersions"][].map!(j => j.get!string).array;
689 					logDebug("Using cached versionFilters for %s: %s %s", ti.pack.name,
690 						versionFilters, debugVersionFilters);
691 					bs.addVersionFilters(versionFilters);
692 					bs.addDebugVersionFilters(debugVersionFilters);
693 					return;
694 				}
695 			}
696 		}
697 		catch (JSONException e)
698 		{
699 			logWarn("Exception during loading invalid package cache %s.\n%s",
700 				ti.pack.path ~ ".dub/metadata_cache.json", e);
701 		}
702 
703 		// use ctRegex for performance reasons, only small compile time increase
704 		enum verRE = ctRegex!`(?:^|\s)version\s*\(\s*([^\s]*?)\s*\)`;
705 		enum debVerRE = ctRegex!`(?:^|\s)debug\s*\(\s*([^\s]*?)\s*\)`;
706 
707 		auto versionFilters = appender!(string[]);
708 		auto debugVersionFilters = appender!(string[]);
709 
710 		foreach (file; srcs)
711 		{
712 			foreach (line; File(file).byLine)
713 			{
714 				foreach (m; line.matchAll(verRE))
715 					if (!versionFilters.data.canFind(m[1]))
716 						versionFilters.put(m[1].idup);
717 				foreach (m; line.matchAll(debVerRE))
718 					if (!debugVersionFilters.data.canFind(m[1]))
719 						debugVersionFilters.put(m[1].idup);
720 			}
721 		}
722 		logDebug("Using inferred versionFilters for %s: %s %s", ti.pack.name,
723 			versionFilters.data, debugVersionFilters.data);
724 		bs.addVersionFilters(versionFilters.data);
725 		bs.addDebugVersionFilters(debugVersionFilters.data);
726 
727 		auto cachedFilters = cache["versionFilters"];
728 		if (cachedFilters.type == Json.Type.undefined)
729 			cachedFilters = cache["versionFilters"] = [ti.config: Json.emptyObject];
730 		cachedFilters[ti.config] = [
731 			"mtime": Json(Clock.currTime(UTC()).toISOExtString),
732 			"versions": Json(versionFilters.data.map!Json.array),
733 			"debugVersions": Json(debugVersionFilters.data.map!Json.array),
734 		];
735         enum create_if_missing = true;
736         if (isWritableDir(cacheFilePath.parentPath, create_if_missing))
737             writeJsonFile(cacheFilePath, cache);
738 	}
739 
740 	private static void mergeFromDependent(const scope ref BuildSettings parent, ref BuildSettings child)
741 	{
742 		child.addVersions(parent.versions);
743 		child.addDebugVersions(parent.debugVersions);
744 		child.addOptions(Flags!BuildOption(parent.options & inheritedBuildOptions));
745 	}
746 
747 	private static void mergeFromDependency(const scope ref BuildSettings child, ref BuildSettings parent, const scope ref BuildPlatform platform)
748 	{
749 		import dub.compilers.utils : isLinkerFile;
750 
751 		parent.addDFlags(child.dflags);
752 		parent.addVersions(child.versions);
753 		parent.addDebugVersions(child.debugVersions);
754 		parent.addVersionFilters(child.versionFilters);
755 		parent.addDebugVersionFilters(child.debugVersionFilters);
756 		parent.addImportPaths(child.importPaths);
757 		parent.addCImportPaths(child.cImportPaths);
758 		parent.addStringImportPaths(child.stringImportPaths);
759 		parent.addInjectSourceFiles(child.injectSourceFiles);
760 		// linker stuff propagates up from static *and* dynamic library deps
761 		if (child.targetType == TargetType.staticLibrary || child.targetType == TargetType.dynamicLibrary) {
762 			parent.addSourceFiles(child.sourceFiles.filter!(f => isLinkerFile(platform, f)).array);
763 			parent.addLibs(child.libs);
764 			parent.addFrameworks(child.frameworks);
765 			parent.addLFlags(child.lflags);
766 		}
767 	}
768 
769 	// configure targets for build types such as release, or unittest-cov
770 	private void addBuildTypeSettings(TargetInfo[string] targets, in GeneratorSettings settings)
771 	{
772 		foreach (ref ti; targets.byValue) {
773 			ti.buildSettings.add(settings.buildSettings);
774 
775 			// add build type settings and convert plain DFLAGS to build options
776 			m_project.addBuildTypeSettings(ti.buildSettings, settings, ti.pack is m_project.rootPackage);
777 			settings.compiler.extractBuildOptions(ti.buildSettings);
778 
779 			auto tt = ti.buildSettings.targetType;
780 			enforce (tt != TargetType.sourceLibrary || ti.pack !is m_project.rootPackage || (ti.buildSettings.options & BuildOption.syntaxOnly),
781 				format("Main package must not have target type \"%s\". Cannot build.", tt));
782 		}
783 	}
784 }
785 
786 /**
787  * Compute and returns the path were artifacts are stored for a given package
788  *
789  * Artifacts are stored in:
790  * `$DUB_HOME/cache/$PKG_NAME/$PKG_VERSION[/+$SUB_PKG_NAME]/`
791  * Note that the leading `+` in the sub-package name is to avoid any ambiguity.
792  *
793  * Dub writes in the returned path a Json description file of the available
794  * artifacts in this cache location. This Json file is read by 3rd party
795  * software (e.g. Meson). Returned path should therefore not change across
796  * future Dub versions.
797  *
798  * Build artifacts are usually stored in a sub-folder named "build",
799  * as their names are based on user-supplied values.
800  *
801  * Params:
802  *   cachePath = Base path at which the build cache is located,
803  *               e.g. `$HOME/.dub/cache/`
804  *	 pkg = The package. Cannot be `null`.
805  */
806 package(dub) NativePath packageCache(NativePath cachePath, in Package pkg)
807 {
808 	import std.algorithm.searching : findSplit;
809 
810 	assert(pkg !is null);
811 	assert(!cachePath.empty);
812 
813 	// For subpackages
814 	if (const names = pkg.name.findSplit(":"))
815 		return cachePath ~ names[0] ~ pkg.version_.toString()
816 			~ ("+" ~ names[2]);
817 	// For regular packages
818 	return cachePath ~ pkg.name ~ pkg.version_.toString();
819 }
820 
821 /**
822  * Compute and return the directory where a target should be cached.
823  *
824  * Params:
825  *   cachePath = Base path at which the build cache is located,
826  *               e.g. `$HOME/.dub/cache/`
827  *	 pkg = The package. Cannot be `null`.
828  *   buildId = The build identifier of the target.
829  */
830 package(dub) NativePath targetCacheDir(NativePath cachePath, in Package pkg, string buildId)
831 {
832 	return packageCache(cachePath, pkg) ~ "build" ~ buildId;
833 }
834 
835 /**
836  * Provides a unique (per build) identifier
837  *
838  * When building a package, it is important to have a unique but stable
839  * identifier to differentiate builds and allow their caching.
840  * This function provides such an identifier.
841  * Example:
842  * ```
843  * library-debug-Z7qINYX4IxM8muBSlyNGrw
844  * ```
845  */
846 package(dub) string computeBuildID(in BuildSettings buildsettings,
847 	in NativePath packagePath, string config, GeneratorSettings settings)
848 {
849 	import std.conv : to;
850 
851 	const(string[])[] hashing = [
852 		buildsettings.versions,
853 		buildsettings.debugVersions,
854 		buildsettings.dflags,
855 		buildsettings.lflags,
856 		buildsettings.stringImportPaths,
857 		buildsettings.importPaths,
858 		buildsettings.cImportPaths,
859 		settings.platform.architecture,
860 		[
861 			(cast(uint)(buildsettings.options & ~BuildOption.color)).to!string, // exclude color option from id
862 			// Needed for things such as `__FULL_FILE_PATH__`
863 			packagePath.toNativeString(),
864 			settings.platform.compilerBinary,
865 			settings.platform.compiler,
866 			settings.platform.compilerVersion,
867 		],
868 	];
869 
870 	return computeBuildName(config, settings, hashing);
871 }
872 
873 struct GeneratorSettings {
874 	NativePath cache;
875 	BuildPlatform platform;
876 	Compiler compiler;
877 	string config;
878 	string recipeName;
879 	string buildType;
880 	BuildSettings buildSettings;
881 	BuildMode buildMode = BuildMode.separate;
882 	int targetExitStatus;
883 	NativePath overrideToolWorkingDirectory;
884 	NativePath destinationDirectory;
885 
886 	bool combined; // compile all in one go instead of each dependency separately
887 	bool filterVersions;
888 
889 	// only used for generator "build"
890 	bool run, force, rdmd, tempBuild, parallelBuild;
891 
892 	/// single file dub package
893 	bool single;
894 
895 	/// build all dependencies for static libraries
896 	bool buildDeep;
897 
898 	string[] runArgs;
899 	void delegate(int status, string output) compileCallback;
900 	void delegate(int status, string output) linkCallback;
901 	void delegate(int status, string output) runCallback;
902 
903 	/// Returns `overrideToolWorkingDirectory` or if that's not set, just the
904 	/// current working directory of the application. This may differ if dub is
905 	/// called with the `--root` parameter or when using DUB as a library.
906 	NativePath toolWorkingDirectory() const
907 	{
908 		return overrideToolWorkingDirectory is NativePath.init
909 			? getWorkingDirectory()
910 			: overrideToolWorkingDirectory;
911 	}
912 }
913 
914 
915 /**
916 	Determines the mode in which the compiler and linker are invoked.
917 */
918 enum BuildMode {
919 	separate,                 /// Compile and link separately
920 	allAtOnce,                /// Perform compile and link with a single compiler invocation
921 	singleFile,               /// Compile each file separately
922 	//multipleObjects,          /// Generate an object file per module
923 	//multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module
924 	//compileOnly               /// Do not invoke the linker (can be done using a post build command)
925 }
926 
927 
928 /**
929 	Creates a project generator of the given type for the specified project.
930 */
931 ProjectGenerator createProjectGenerator(string generator_type, Project project)
932 {
933 	assert(project !is null, "Project instance needed to create a generator.");
934 
935 	generator_type = generator_type.toLower();
936 	switch(generator_type) {
937 		default:
938 			throw new Exception("Unknown project generator: "~generator_type);
939 		case "build":
940 			logDebug("Creating build generator.");
941 			return new BuildGenerator(project);
942 		case "mono-d":
943 			throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead.");
944 		case "visuald":
945 			logDebug("Creating VisualD generator.");
946 			return new VisualDGenerator(project);
947 		case "sublimetext":
948 			logDebug("Creating SublimeText generator.");
949 			return new SublimeTextGenerator(project);
950 		case "cmake":
951 			logDebug("Creating CMake generator.");
952 			return new CMakeGenerator(project);
953 	}
954 }
955 
956 
957 /**
958 	Calls delegates on files and directories in the given path that match any globs.
959 */
960 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile, void delegate(string dir) addDir)
961 {
962 	import std.path : globMatch;
963 
964 	string[] globs;
965 	foreach (f; globList)
966 	{
967 		if (f.canFind("*", "?") ||
968 			(f.canFind("{") && f.balancedParens('{', '}')) ||
969 			(f.canFind("[") && f.balancedParens('[', ']')))
970 		{
971 			globs ~= f;
972 		}
973 		else
974 		{
975 			if (f.isDir)
976 				addDir(f);
977 			else
978 				addFile(f);
979 		}
980 	}
981 	if (globs.length) // Search all files for glob matches
982 		foreach (f; dirEntries(path.toNativeString(), SpanMode.breadth))
983 			foreach (glob; globs)
984 				if (f.name().globMatch(glob))
985 				{
986 					if (f.isDir)
987 						addDir(f);
988 					else
989 						addFile(f);
990 					break;
991 				}
992 }
993 
994 
995 /**
996 	Calls delegates on files in the given path that match any globs.
997 
998 	If a directory matches a glob, the delegate is called on all existing files inside it recursively
999 	in depth-first pre-order.
1000 */
1001 void findFilesMatchingGlobs(in NativePath path, in string[] globList, void delegate(string file) addFile)
1002 {
1003 	void addDir(string dir)
1004 	{
1005 		foreach (f; dirEntries(dir, SpanMode.breadth))
1006 			addFile(f);
1007 	}
1008 
1009 	findFilesMatchingGlobs(path, globList, addFile, &addDir);
1010 }
1011 
1012 
1013 /**
1014 	Runs pre-build commands and performs other required setup before project files are generated.
1015 */
1016 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
1017 	in BuildSettings buildsettings)
1018 {
1019 	if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
1020 		logInfo("Pre-gen", Color.light_green, "Running commands for %s", pack.name);
1021 		runBuildCommands(CommandType.preGenerate, buildsettings.preGenerateCommands, pack, proj, settings, buildsettings);
1022 	}
1023 }
1024 
1025 /**
1026 	Runs post-build commands and copies required files to the binary directory.
1027 */
1028 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
1029 	in BuildSettings buildsettings, NativePath target_path, bool generate_binary)
1030 {
1031 	if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
1032 		logInfo("Post-gen", Color.light_green, "Running commands for %s", pack.name);
1033 		runBuildCommands(CommandType.postGenerate, buildsettings.postGenerateCommands, pack, proj, settings, buildsettings);
1034 	}
1035 
1036 	if (generate_binary) {
1037 		if (!settings.tempBuild)
1038 			ensureDirectory(NativePath(buildsettings.targetPath));
1039 
1040 		if (buildsettings.copyFiles.length) {
1041 			void copyFolderRec(NativePath folder, NativePath dstfolder)
1042 			{
1043 				ensureDirectory(dstfolder);
1044 				foreach (de; iterateDirectory(folder)) {
1045 					if (de.isDirectory) {
1046 						copyFolderRec(folder ~ de.name, dstfolder ~ de.name);
1047 					} else {
1048 						try copyFile(folder ~ de.name, dstfolder ~ de.name, true);
1049 						catch (Exception e) {
1050 							logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg);
1051 						}
1052 					}
1053 				}
1054 			}
1055 
1056 			void tryCopyDir(string file)
1057 			{
1058 				auto src = NativePath(file);
1059 				if (!src.absolute) src = pack.path ~ src;
1060 				auto dst = target_path ~ NativePath(file).head;
1061 				if (src == dst) {
1062 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
1063 					return;
1064 				}
1065 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
1066 				try {
1067 					copyFolderRec(src, dst);
1068 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
1069 			}
1070 
1071 			void tryCopyFile(string file)
1072 			{
1073 				auto src = NativePath(file);
1074 				if (!src.absolute) src = pack.path ~ src;
1075 				auto dst = target_path ~ NativePath(file).head;
1076 				if (src == dst) {
1077 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
1078 					return;
1079 				}
1080 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
1081 				try {
1082 					copyFile(src, dst, true);
1083 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
1084 			}
1085 			logInfo("Copying files for %s...", pack.name);
1086 			findFilesMatchingGlobs(pack.path, buildsettings.copyFiles, &tryCopyFile, &tryCopyDir);
1087 		}
1088 
1089 	}
1090 }
1091 
1092 
1093 /** Runs a list of build commands for a particular package.
1094 
1095 	This function sets all DUB specific environment variables and makes sure
1096 	that recursive dub invocations are detected and don't result in infinite
1097 	command execution loops. The latter could otherwise happen when a command
1098 	runs "dub describe" or similar functionality.
1099 */
1100 void runBuildCommands(CommandType type, in string[] commands, in Package pack, in Project proj,
1101 	in GeneratorSettings settings, in BuildSettings build_settings, in string[string][] extraVars = null)
1102 {
1103 	import dub.internal.utils : runCommands;
1104 
1105 	auto env = makeCommandEnvironmentVariables(type, pack, proj, settings, build_settings, extraVars);
1106 	auto sub_commands = processVars(proj, pack, settings, commands, false, env);
1107 
1108 	auto depNames = proj.dependencies.map!((a) => a.name).array();
1109 	storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames);
1110 
1111 	runCommands(sub_commands, env.collapseEnv, pack.path().toString());
1112 }
1113 
1114 const(string[string])[] makeCommandEnvironmentVariables(CommandType type,
1115 	in Package pack, in Project proj, in GeneratorSettings settings,
1116 	in BuildSettings build_settings, in string[string][] extraVars = null)
1117 {
1118 	import dub.internal.utils : getDUBExePath;
1119 	import std.conv : to, text;
1120 	import std.process : environment, escapeShellFileName;
1121 
1122 	string[string] env;
1123 	// TODO: do more elaborate things here
1124 	// TODO: escape/quote individual items appropriately
1125 	env["VERSIONS"]              = join(build_settings.versions, " ");
1126 	env["LIBS"]                  = join(build_settings.libs, " ");
1127 	env["SOURCE_FILES"]          = join(build_settings.sourceFiles, " ");
1128 	env["IMPORT_PATHS"]          = join(build_settings.importPaths, " ");
1129 	env["C_IMPORT_PATHS"]        = join(build_settings.cImportPaths, " ");
1130 	env["STRING_IMPORT_PATHS"]   = join(build_settings.stringImportPaths, " ");
1131 
1132 	env["DC"]                    = settings.platform.compilerBinary;
1133 	env["DC_BASE"]               = settings.platform.compiler;
1134 	env["D_FRONTEND_VER"]        = to!string(settings.platform.frontendVersion);
1135 
1136 	env["DUB_EXE"]               = getDUBExePath(settings.platform.compilerBinary).toNativeString();
1137 	env["DUB_PLATFORM"]          = join(settings.platform.platform, " ");
1138 	env["DUB_ARCH"]              = join(settings.platform.architecture, " ");
1139 
1140 	env["DUB_TARGET_TYPE"]       = to!string(build_settings.targetType);
1141 	env["DUB_TARGET_PATH"]       = build_settings.targetPath;
1142 	env["DUB_TARGET_NAME"]       = build_settings.targetName;
1143 	env["DUB_TARGET_EXIT_STATUS"] = settings.targetExitStatus.text;
1144 	env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory;
1145 	env["DUB_MAIN_SOURCE_FILE"]  = build_settings.mainSourceFile;
1146 
1147 	env["DUB_CONFIG"]            = settings.config;
1148 	env["DUB_BUILD_TYPE"]        = settings.buildType;
1149 	env["DUB_BUILD_MODE"]        = to!string(settings.buildMode);
1150 	env["DUB_PACKAGE"]           = pack.name;
1151 	env["DUB_PACKAGE_DIR"]       = pack.path.toNativeString();
1152 	env["DUB_ROOT_PACKAGE"]      = proj.rootPackage.name;
1153 	env["DUB_ROOT_PACKAGE_DIR"]  = proj.rootPackage.path.toNativeString();
1154 	env["DUB_PACKAGE_VERSION"]   = pack.version_.toString();
1155 
1156 	env["DUB_COMBINED"]          = settings.combined?      "TRUE" : "";
1157 	env["DUB_RUN"]               = settings.run?           "TRUE" : "";
1158 	env["DUB_FORCE"]             = settings.force?         "TRUE" : "";
1159 	env["DUB_RDMD"]              = settings.rdmd?          "TRUE" : "";
1160 	env["DUB_TEMP_BUILD"]        = settings.tempBuild?     "TRUE" : "";
1161 	env["DUB_PARALLEL_BUILD"]    = settings.parallelBuild? "TRUE" : "";
1162 
1163 	env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" ");
1164 
1165 	auto cfgs = proj.getPackageConfigs(settings.platform, settings.config, true);
1166 	auto rootPackageBuildSettings = proj.rootPackage.getBuildSettings(settings.platform, cfgs[proj.rootPackage.name]);
1167 	env["DUB_ROOT_PACKAGE_TARGET_TYPE"] = to!string(rootPackageBuildSettings.targetType);
1168 	env["DUB_ROOT_PACKAGE_TARGET_PATH"] = rootPackageBuildSettings.targetPath;
1169 	env["DUB_ROOT_PACKAGE_TARGET_NAME"] = rootPackageBuildSettings.targetName;
1170 
1171 	const(string[string])[] typeEnvVars;
1172 	with (build_settings) final switch (type)
1173 	{
1174 		// pre/postGenerate don't have generateEnvironments, but reuse buildEnvironments
1175 		case CommandType.preGenerate: typeEnvVars = [environments, buildEnvironments, preGenerateEnvironments]; break;
1176 		case CommandType.postGenerate: typeEnvVars = [environments, buildEnvironments, postGenerateEnvironments]; break;
1177 		case CommandType.preBuild: typeEnvVars = [environments, buildEnvironments, preBuildEnvironments]; break;
1178 		case CommandType.postBuild: typeEnvVars = [environments, buildEnvironments, postBuildEnvironments]; break;
1179 		case CommandType.preRun: typeEnvVars = [environments, runEnvironments, preRunEnvironments]; break;
1180 		case CommandType.postRun: typeEnvVars = [environments, runEnvironments, postRunEnvironments]; break;
1181 	}
1182 
1183 	return [environment.toAA()] ~ env ~ typeEnvVars ~ extraVars;
1184 }
1185 
1186 string[string] collapseEnv(in string[string][] envs)
1187 {
1188 	string[string] ret;
1189 	foreach (subEnv; envs)
1190 	{
1191 		foreach (k, v; subEnv)
1192 			ret[k] = v;
1193 	}
1194 	return ret;
1195 }
1196 
1197 /// Type to specify where CLI commands that need to be run came from. Needed for
1198 /// proper substitution with support for the different environments.
1199 enum CommandType
1200 {
1201 	/// Defined in the preGenerateCommands setting
1202 	preGenerate,
1203 	/// Defined in the postGenerateCommands setting
1204 	postGenerate,
1205 	/// Defined in the preBuildCommands setting
1206 	preBuild,
1207 	/// Defined in the postBuildCommands setting
1208 	postBuild,
1209 	/// Defined in the preRunCommands setting
1210 	preRun,
1211 	/// Defined in the postRunCommands setting
1212 	postRun
1213 }
1214 
1215 private bool isRecursiveInvocation(string pack)
1216 {
1217 	import std.algorithm : canFind, splitter;
1218 	import std.process : environment;
1219 
1220 	return environment
1221 		.get("DUB_PACKAGES_USED", "")
1222 		.splitter(",")
1223 		.canFind(pack);
1224 }
1225 
1226 private void storeRecursiveInvokations(ref const(string[string])[] env, string[] packs)
1227 {
1228 	import std.algorithm : canFind, splitter;
1229 	import std.range : chain;
1230 	import std.process : environment;
1231 
1232 	env ~= [
1233 		"DUB_PACKAGES_USED": environment
1234 			.get("DUB_PACKAGES_USED", "")
1235 			.splitter(",")
1236 			.chain(packs)
1237 			.join(",")
1238 	];
1239 }
1240 
1241 version(Posix) {
1242     // https://github.com/dlang/dub/issues/2238
1243 	unittest {
1244 		import dub.recipe.io : parsePackageRecipe;
1245 		import dub.compilers.gdc : GDCCompiler;
1246 		import std.algorithm : canFind;
1247 		import std.path : absolutePath;
1248 		import std.file : rmdirRecurse, write;
1249 
1250 		mkdirRecurse("dubtest/preGen/source");
1251 		write("dubtest/preGen/source/foo.d", "");
1252 		scope(exit) rmdirRecurse("dubtest");
1253 
1254 		auto recipe = parsePackageRecipe(
1255 			`{"name":"test", "targetType":"library", "preGenerateCommands":["touch $PACKAGE_DIR/source/bar.d"]}`,
1256 			`dubtest/preGen/dub.json`);
1257 		auto pack = new Package(recipe, NativePath("dubtest/preGen".absolutePath));
1258 		auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false);
1259 		auto prj = new Project(pman, pack);
1260 
1261 		final static class TestCompiler : GDCCompiler {
1262 			override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd) {
1263 				assert(false);
1264 			}
1265 			override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd) {
1266 				assert(false);
1267 			}
1268 		}
1269 
1270 		GeneratorSettings settings;
1271 		settings.compiler = new TestCompiler;
1272 		settings.buildType = "debug";
1273 
1274 		final static class TestGenerator : ProjectGenerator {
1275 			this(Project project) {
1276 			 	super(project);
1277 			}
1278 
1279 			override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) {
1280                 import std.conv : text;
1281 				const sourceFiles = targets["test"].buildSettings.sourceFiles;
1282                 assert(sourceFiles.canFind("dubtest/preGen/source/bar.d".absolutePath), sourceFiles.text);
1283 			}
1284 		}
1285 
1286 		auto gen = new TestGenerator(prj);
1287 		gen.generate(settings);
1288 	}
1289 }