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 	}
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), true);
99 			targets[pack.name] = TargetInfo(pack, [pack], config, buildSettings);
100 
101 			prepareGeneration(pack, m_project, settings, buildSettings);
102 		}
103 
104 		string[] mainfiles = configurePackages(m_project.rootPackage, targets, settings);
105 
106 		addBuildTypeSettings(targets, settings);
107 		foreach (ref t; targets.byValue) enforceBuildRequirements(t.buildSettings);
108 		auto bs = &targets[m_project.rootPackage.name].buildSettings;
109 		if (bs.targetType == TargetType.executable) bs.addSourceFiles(mainfiles);
110 
111 		generateTargets(settings, targets);
112 
113 		foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) {
114 			BuildSettings buildsettings;
115 			buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), true);
116 			bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly);
117 			finalizeGeneration(pack, m_project, settings, buildsettings, NativePath(bs.targetPath), generate_binary);
118 		}
119 
120 		performPostGenerateActions(settings, targets);
121 	}
122 
123 	/** Overridden in derived classes to implement the actual generator functionality.
124 
125 		The function should go through all targets recursively. The first target
126 		(which is guaranteed to be there) is
127 		$(D targets[m_project.rootPackage.name]). The recursive descent is then
128 		done using the $(D TargetInfo.linkDependencies) list.
129 
130 		This method is also potentially responsible for running the pre and post
131 		build commands, while pre and post generate commands are already taken
132 		care of by the $(D generate) method.
133 
134 		Params:
135 			settings = The generator settings used for this run
136 			targets = A map from package name to TargetInfo that contains all
137 				binary targets to be built.
138 	*/
139 	protected abstract void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets);
140 
141 	/** Overridable method to be invoked after the generator process has finished.
142 
143 		An examples of functionality placed here is to run the application that
144 		has just been built.
145 	*/
146 	protected void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets) {}
147 
148 	/** Configure `rootPackage` and all of it's dependencies.
149 
150 		1. Merge versions, debugVersions, and inheritable build
151 		settings from dependents to their dependencies.
152 
153 		2. Define version identifiers Have_dependency_xyz for all
154 		direct dependencies of all packages.
155 
156 		3. Merge versions, debugVersions, and inheritable build settings from
157 		dependencies to their dependents, so that importer and importee are ABI
158 		compatible. This also transports all Have_dependency_xyz version
159 		identifiers to `rootPackage`.
160 
161 		Note: The upwards inheritance is done at last so that siblings do not
162 		influence each other, also see https://github.com/dlang/dub/pull/1128.
163 
164 		Note: Targets without output are integrated into their
165 		dependents and removed from `targets`.
166 	 */
167 	private string[] configurePackages(Package rootPackage, TargetInfo[string] targets, GeneratorSettings genSettings)
168 	{
169 		import std.algorithm : remove, sort;
170 		import std.range : repeat;
171 
172 		// 0. do shallow configuration (not including dependencies) of all packages
173 		TargetType determineTargetType(const ref TargetInfo ti)
174 		{
175 			TargetType tt = ti.buildSettings.targetType;
176 			if (ti.pack is rootPackage) {
177 				if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary;
178 			} else {
179 				if (tt == TargetType.autodetect || tt == TargetType.library) tt = genSettings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary;
180 				else if (tt == TargetType.dynamicLibrary) {
181 					logWarn("Dynamic libraries are not yet supported as dependencies - building as static library.");
182 					tt = TargetType.staticLibrary;
183 				}
184 			}
185 			if (tt != TargetType.none && tt != TargetType.sourceLibrary && ti.buildSettings.sourceFiles.empty) {
186 				logWarn(`Configuration '%s' of package %s contains no source files. Please add {"targetType": "none"} to its package description to avoid building it.`,
187 						ti.config, ti.pack.name);
188 				tt = TargetType.none;
189 			}
190 			return tt;
191 		}
192 
193 		string[] mainSourceFiles;
194 		bool[string] hasOutput;
195 
196 		foreach (ref ti; targets.byValue)
197 		{
198 			auto bs = &ti.buildSettings;
199 			// determine the actual target type
200 			bs.targetType = determineTargetType(ti);
201 
202 			switch (bs.targetType)
203 			{
204 			case TargetType.none:
205 				// ignore any build settings for targetType none (only dependencies will be processed)
206 				*bs = BuildSettings.init;
207 				bs.targetType = TargetType.none;
208 				break;
209 
210 			case TargetType.executable:
211 				break;
212 
213 			case TargetType.dynamicLibrary:
214 				// set -fPIC for dynamic library builds
215 				ti.buildSettings.addOptions(BuildOption.pic);
216 				goto default;
217 
218 			default:
219 				// remove any mainSourceFile from non-executable builds
220 				if (bs.mainSourceFile.length) {
221 					bs.sourceFiles = bs.sourceFiles.remove!(f => f == bs.mainSourceFile);
222 					mainSourceFiles ~= bs.mainSourceFile;
223 				}
224 				break;
225 			}
226 			bool generatesBinary = bs.targetType != TargetType.sourceLibrary && bs.targetType != TargetType.none;
227 			hasOutput[ti.pack.name] = generatesBinary || ti.pack is rootPackage;
228 		}
229 
230 		// mark packages as visited (only used during upwards propagation)
231 		void[0][Package] visited;
232 
233 		// collect all dependencies
234 		void collectDependencies(Package pack, ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0)
235 		{
236 			// use `visited` here as pkgs cannot depend on themselves
237 			if (pack in visited)
238 				return;
239 			// transitive dependencies must be visited multiple times, see #1350
240 			immutable transitive = !hasOutput[pack.name];
241 			if (!transitive)
242 				visited[pack] = typeof(visited[pack]).init;
243 
244 			auto bs = &ti.buildSettings;
245 			if (hasOutput[pack.name])
246 				logDebug("%sConfiguring target %s (%s %s %s)", ' '.repeat(2 * level), pack.name, bs.targetType, bs.targetPath, bs.targetName);
247 			else
248 				logDebug("%sConfiguring target without output %s", ' '.repeat(2 * level), pack.name);
249 
250 			// get specified dependencies, e.g. vibe-d ~0.8.1
251 			auto deps = pack.getDependencies(targets[pack.name].config);
252 			logDebug("deps: %s -> %(%s, %)", pack.name, deps.byKey);
253 			foreach (depname; deps.keys.sort())
254 			{
255 				auto depspec = deps[depname];
256 				// get selected package for that dependency, e.g. vibe-d 0.8.2-beta.2
257 				auto deppack = m_project.getDependency(depname, depspec.optional);
258 				if (deppack is null) continue; // optional and not selected
259 
260 				// if dependency has no output
261 				if (!hasOutput[depname]) {
262 					// add itself
263 					ti.packages ~= deppack;
264 					// and it's transitive dependencies to current target
265 					collectDependencies(deppack, ti, targets, level + 1);
266 					continue;
267 				}
268 				auto depti = &targets[depname];
269 				const depbs = &depti.buildSettings;
270 				if (depbs.targetType == TargetType.executable)
271 					continue;
272 				// add to (link) dependencies
273 				ti.dependencies ~= depname;
274 				ti.linkDependencies ~= depname;
275 
276 				// recurse
277 				collectDependencies(deppack, *depti, targets, level + 1);
278 
279 				// also recursively add all link dependencies of static libraries
280 				// preserve topological sorting of dependencies for correct link order
281 				if (depbs.targetType == TargetType.staticLibrary)
282 					ti.linkDependencies = ti.linkDependencies.filter!(d => !depti.linkDependencies.canFind(d)).array ~ depti.linkDependencies;
283 			}
284 		}
285 
286 		collectDependencies(rootPackage, targets[rootPackage.name], targets);
287 		static if (__VERSION__ > 2070)
288 			visited.clear();
289 		else
290 			destroy(visited);
291 
292 		// 1. downwards inherits versions, debugVersions, and inheritable build settings
293 		static void configureDependencies(in ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0)
294 		{
295 			// do not use `visited` here as dependencies must inherit
296 			// configurations from *all* of their parents
297 			logDebug("%sConfigure dependencies of %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies);
298 			foreach (depname; ti.dependencies)
299 			{
300 				auto pti = &targets[depname];
301 				mergeFromDependent(ti.buildSettings, pti.buildSettings);
302 				configureDependencies(*pti, targets, level + 1);
303 			}
304 		}
305 
306 		configureDependencies(targets[rootPackage.name], targets);
307 
308 		// 2. add Have_dependency_xyz for all direct dependencies of a target
309 		// (includes incorporated non-target dependencies and their dependencies)
310 		foreach (ref ti; targets.byValue)
311 		{
312 			import std.range : chain;
313 			import dub.internal.utils : stripDlangSpecialChars;
314 
315 			auto bs = &ti.buildSettings;
316 			auto pkgnames = ti.packages.map!(p => p.name).chain(ti.dependencies);
317 			bs.addVersions(pkgnames.map!(pn => "Have_" ~ stripDlangSpecialChars(pn)).array);
318 		}
319 
320 		// 3. upwards inherit full build configurations (import paths, versions, debugVersions, ...)
321 		void configureDependents(ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0)
322 		{
323 			// use `visited` here as pkgs cannot depend on themselves
324 			if (ti.pack in visited)
325 				return;
326 			visited[ti.pack] = typeof(visited[ti.pack]).init;
327 
328 			logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies);
329 			// embedded non-binary dependencies
330 			foreach (deppack; ti.packages[1 .. $])
331 				ti.buildSettings.add(targets[deppack.name].buildSettings);
332 			// binary dependencies
333 			foreach (depname; ti.dependencies)
334 			{
335 				auto pdepti = &targets[depname];
336 				configureDependents(*pdepti, targets, level + 1);
337 				mergeFromDependency(pdepti.buildSettings, ti.buildSettings);
338 			}
339 		}
340 
341 		configureDependents(targets[rootPackage.name], targets);
342 		static if (__VERSION__ > 2070)
343 			visited.clear();
344 		else
345 			destroy(visited);
346 
347 		// 4. override string import files in dependencies
348 		static void overrideStringImports(ref TargetInfo ti, TargetInfo[string] targets, string[] overrides)
349 		{
350 			// do not use visited here as string imports can be overridden by *any* parent
351 			//
352 			// special support for overriding string imports in parent packages
353 			// this is a candidate for deprecation, once an alternative approach
354 			// has been found
355 			if (ti.buildSettings.stringImportPaths.length) {
356 				// override string import files (used for up to date checking)
357 				foreach (ref f; ti.buildSettings.stringImportFiles)
358 				{
359 					foreach (o; overrides)
360 					{
361 						NativePath op;
362 						if (f != o && NativePath(f).head == (op = NativePath(o)).head) {
363 							logDebug("string import %s overridden by %s", f, o);
364 							f = o;
365 							ti.buildSettings.prependStringImportPaths(op.parentPath.toNativeString);
366 						}
367 					}
368 				}
369 			}
370 			// add to overrides for recursion
371 			overrides ~= ti.buildSettings.stringImportFiles;
372 			// override dependencies
373 			foreach (depname; ti.dependencies)
374 				overrideStringImports(targets[depname], targets, overrides);
375 		}
376 
377 		overrideStringImports(targets[rootPackage.name], targets, null);
378 
379 		// remove targets without output
380 		foreach (name; targets.keys)
381 		{
382 			if (!hasOutput[name])
383 				targets.remove(name);
384 		}
385 
386 		return mainSourceFiles;
387 	}
388 
389 	private static void mergeFromDependent(in ref BuildSettings parent, ref BuildSettings child)
390 	{
391 		child.addVersions(parent.versions);
392 		child.addDebugVersions(parent.debugVersions);
393 		child.addOptions(BuildOptions(cast(BuildOptions)parent.options & inheritedBuildOptions));
394 	}
395 
396 	private static void mergeFromDependency(in ref BuildSettings child, ref BuildSettings parent)
397 	{
398 		import dub.compilers.utils : isLinkerFile;
399 
400 		parent.addDFlags(child.dflags);
401 		parent.addVersions(child.versions);
402 		parent.addDebugVersions(child.debugVersions);
403 		parent.addImportPaths(child.importPaths);
404 		parent.addStringImportPaths(child.stringImportPaths);
405 		// linking of static libraries is done by parent
406 		if (child.targetType == TargetType.staticLibrary) {
407 			parent.addLinkerFiles(child.sourceFiles.filter!isLinkerFile.array);
408 			parent.addLibs(child.libs);
409 			parent.addLFlags(child.lflags);
410 		}
411 	}
412 
413 	// configure targets for build types such as release, or unittest-cov
414 	private void addBuildTypeSettings(TargetInfo[string] targets, GeneratorSettings settings)
415 	{
416 		foreach (ref ti; targets.byValue) {
417 			ti.buildSettings.add(settings.buildSettings);
418 
419 			// add build type settings and convert plain DFLAGS to build options
420 			m_project.addBuildTypeSettings(ti.buildSettings, settings.platform, settings.buildType, ti.pack is m_project.rootPackage);
421 			settings.compiler.extractBuildOptions(ti.buildSettings);
422 
423 			auto tt = ti.buildSettings.targetType;
424 			bool generatesBinary = tt != TargetType.sourceLibrary && tt != TargetType.none;
425 			enforce (generatesBinary || ti.pack !is m_project.rootPackage || (ti.buildSettings.options & BuildOption.syntaxOnly),
426 				format("Main package must have a binary target type, not %s. Cannot build.", tt));
427 		}
428 	}
429 }
430 
431 
432 struct GeneratorSettings {
433 	BuildPlatform platform;
434 	Compiler compiler;
435 	string config;
436 	string buildType;
437 	BuildSettings buildSettings;
438 	BuildMode buildMode = BuildMode.separate;
439 
440 	bool combined; // compile all in one go instead of each dependency separately
441 
442 	// only used for generator "build"
443 	bool run, force, direct, rdmd, tempBuild, parallelBuild;
444 	string[] runArgs;
445 	void delegate(int status, string output) compileCallback;
446 	void delegate(int status, string output) linkCallback;
447 	void delegate(int status, string output) runCallback;
448 }
449 
450 
451 /**
452 	Determines the mode in which the compiler and linker are invoked.
453 */
454 enum BuildMode {
455 	separate,                 /// Compile and link separately
456 	allAtOnce,                /// Perform compile and link with a single compiler invocation
457 	singleFile,               /// Compile each file separately
458 	//multipleObjects,          /// Generate an object file per module
459 	//multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module
460 	//compileOnly               /// Do not invoke the linker (can be done using a post build command)
461 }
462 
463 
464 /**
465 	Creates a project generator of the given type for the specified project.
466 */
467 ProjectGenerator createProjectGenerator(string generator_type, Project project)
468 {
469 	assert(project !is null, "Project instance needed to create a generator.");
470 
471 	generator_type = generator_type.toLower();
472 	switch(generator_type) {
473 		default:
474 			throw new Exception("Unknown project generator: "~generator_type);
475 		case "build":
476 			logDebug("Creating build generator.");
477 			return new BuildGenerator(project);
478 		case "mono-d":
479 			throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead.");
480 		case "visuald":
481 			logDebug("Creating VisualD generator.");
482 			return new VisualDGenerator(project);
483 		case "sublimetext":
484 			logDebug("Creating SublimeText generator.");
485 			return new SublimeTextGenerator(project);
486 		case "cmake":
487 			logDebug("Creating CMake generator.");
488 			return new CMakeGenerator(project);
489 	}
490 }
491 
492 
493 /**
494 	Runs pre-build commands and performs other required setup before project files are generated.
495 */
496 private void prepareGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
497 	in BuildSettings buildsettings)
498 {
499 	if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
500 		logInfo("Running pre-generate commands for %s...", pack.name);
501 		runBuildCommands(buildsettings.preGenerateCommands, pack, proj, settings, buildsettings);
502 	}
503 }
504 
505 /**
506 	Runs post-build commands and copies required files to the binary directory.
507 */
508 private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings,
509 	in BuildSettings buildsettings, NativePath target_path, bool generate_binary)
510 {
511 	import std.path : globMatch;
512 
513 	if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) {
514 		logInfo("Running post-generate commands for %s...", pack.name);
515 		runBuildCommands(buildsettings.postGenerateCommands, pack, proj, settings, buildsettings);
516 	}
517 
518 	if (generate_binary) {
519 		if (!exists(buildsettings.targetPath))
520 			mkdirRecurse(buildsettings.targetPath);
521 
522 		if (buildsettings.copyFiles.length) {
523 			void copyFolderRec(NativePath folder, NativePath dstfolder)
524 			{
525 				mkdirRecurse(dstfolder.toNativeString());
526 				foreach (de; iterateDirectory(folder.toNativeString())) {
527 					if (de.isDirectory) {
528 						copyFolderRec(folder ~ de.name, dstfolder ~ de.name);
529 					} else {
530 						try hardLinkFile(folder ~ de.name, dstfolder ~ de.name, true);
531 						catch (Exception e) {
532 							logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg);
533 						}
534 					}
535 				}
536 			}
537 
538 			void tryCopyDir(string file)
539 			{
540 				auto src = NativePath(file);
541 				if (!src.absolute) src = pack.path ~ src;
542 				auto dst = target_path ~ NativePath(file).head;
543 				if (src == dst) {
544 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
545 					return;
546 				}
547 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
548 				try {
549 					copyFolderRec(src, dst);
550 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
551 			}
552 
553 			void tryCopyFile(string file)
554 			{
555 				auto src = NativePath(file);
556 				if (!src.absolute) src = pack.path ~ src;
557 				auto dst = target_path ~ NativePath(file).head;
558 				if (src == dst) {
559 					logDiagnostic("Skipping copy of %s (same source and destination)", file);
560 					return;
561 				}
562 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
563 				try {
564 					hardLinkFile(src, dst, true);
565 				} catch(Exception e) logWarn("Failed to copy %s to %s: %s", src.toNativeString(), dst.toNativeString(), e.msg);
566 			}
567 			logInfo("Copying files for %s...", pack.name);
568 			string[] globs;
569 			foreach (f; buildsettings.copyFiles)
570 			{
571 				if (f.canFind("*", "?") ||
572 					(f.canFind("{") && f.balancedParens('{', '}')) ||
573 					(f.canFind("[") && f.balancedParens('[', ']')))
574 				{
575 					globs ~= f;
576 				}
577 				else
578 				{
579 					if (f.isDir)
580 						tryCopyDir(f);
581 					else
582 						tryCopyFile(f);
583 				}
584 			}
585 			if (globs.length) // Search all files for glob matches
586 			{
587 				foreach (f; dirEntries(pack.path.toNativeString(), SpanMode.breadth))
588 				{
589 					foreach (glob; globs)
590 					{
591 						if (f.name().globMatch(glob))
592 						{
593 							if (f.isDir)
594 								tryCopyDir(f);
595 							else
596 								tryCopyFile(f);
597 							break;
598 						}
599 					}
600 				}
601 			}
602 		}
603 
604 	}
605 }
606 
607 
608 /** Runs a list of build commands for a particular package.
609 
610 	This function sets all DUB speficic environment variables and makes sure
611 	that recursive dub invocations are detected and don't result in infinite
612 	command execution loops. The latter could otherwise happen when a command
613 	runs "dub describe" or similar functionality.
614 */
615 void runBuildCommands(in string[] commands, in Package pack, in Project proj,
616 	in GeneratorSettings settings, in BuildSettings build_settings)
617 {
618 	import std.conv;
619 	import std.process;
620 	import dub.internal.utils;
621 
622 	string[string] env = environment.toAA();
623 	// TODO: do more elaborate things here
624 	// TODO: escape/quote individual items appropriately
625 	env["DFLAGS"]                = join(cast(string[])build_settings.dflags, " ");
626 	env["LFLAGS"]                = join(cast(string[])build_settings.lflags," ");
627 	env["VERSIONS"]              = join(cast(string[])build_settings.versions," ");
628 	env["LIBS"]                  = join(cast(string[])build_settings.libs," ");
629 	env["IMPORT_PATHS"]          = join(cast(string[])build_settings.importPaths," ");
630 	env["STRING_IMPORT_PATHS"]   = join(cast(string[])build_settings.stringImportPaths," ");
631 
632 	env["DC"]                    = settings.platform.compilerBinary;
633 	env["DC_BASE"]               = settings.platform.compiler;
634 	env["D_FRONTEND_VER"]        = to!string(settings.platform.frontendVersion);
635 
636 	env["DUB_PLATFORM"]          = join(cast(string[])settings.platform.platform," ");
637 	env["DUB_ARCH"]              = join(cast(string[])settings.platform.architecture," ");
638 
639 	env["DUB_TARGET_TYPE"]       = to!string(build_settings.targetType);
640 	env["DUB_TARGET_PATH"]       = build_settings.targetPath;
641 	env["DUB_TARGET_NAME"]       = build_settings.targetName;
642 	env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory;
643 	env["DUB_MAIN_SOURCE_FILE"]  = build_settings.mainSourceFile;
644 
645 	env["DUB_CONFIG"]            = settings.config;
646 	env["DUB_BUILD_TYPE"]        = settings.buildType;
647 	env["DUB_BUILD_MODE"]        = to!string(settings.buildMode);
648 	env["DUB_PACKAGE"]           = pack.name;
649 	env["DUB_PACKAGE_DIR"]       = pack.path.toNativeString();
650 	env["DUB_ROOT_PACKAGE"]      = proj.rootPackage.name;
651 	env["DUB_ROOT_PACKAGE_DIR"]  = proj.rootPackage.path.toNativeString();
652 
653 	env["DUB_COMBINED"]          = settings.combined?      "TRUE" : "";
654 	env["DUB_RUN"]               = settings.run?           "TRUE" : "";
655 	env["DUB_FORCE"]             = settings.force?         "TRUE" : "";
656 	env["DUB_DIRECT"]            = settings.direct?        "TRUE" : "";
657 	env["DUB_RDMD"]              = settings.rdmd?          "TRUE" : "";
658 	env["DUB_TEMP_BUILD"]        = settings.tempBuild?     "TRUE" : "";
659 	env["DUB_PARALLEL_BUILD"]    = settings.parallelBuild? "TRUE" : "";
660 
661 	env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" ");
662 
663 	auto depNames = proj.dependencies.map!((a) => a.name).array();
664 	storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames);
665 	runCommands(commands, env);
666 }
667 
668 private bool isRecursiveInvocation(string pack)
669 {
670 	import std.algorithm : canFind, splitter;
671 	import std.process : environment;
672 
673 	return environment
674         .get("DUB_PACKAGES_USED", "")
675         .splitter(",")
676         .canFind(pack);
677 }
678 
679 private void storeRecursiveInvokations(string[string] env, string[] packs)
680 {
681 	import std.algorithm : canFind, splitter;
682 	import std.range : chain;
683 	import std.process : environment;
684 
685     env["DUB_PACKAGES_USED"] = environment
686         .get("DUB_PACKAGES_USED", "")
687         .splitter(",")
688         .chain(packs)
689         .join(",");
690 }