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