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