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