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