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