1 /**
2  * Code to manage a D checkout and its dependencies.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.sys.d.manager;
15 
16 import std.algorithm;
17 import std.array;
18 import std.conv;
19 import std.datetime;
20 import std.exception;
21 import std.file;
22 import std.path;
23 import std.process : spawnProcess, wait, escapeShellCommand;
24 import std.range;
25 import std.regex;
26 import std.string;
27 import std.typecons;
28 
29 import ae.sys.d.cache;
30 import ae.sys.d.repo;
31 import ae.sys.file;
32 import ae.sys.git;
33 import ae.utils.aa;
34 import ae.utils.array;
35 import ae.utils.digest;
36 import ae.utils.json;
37 import ae.utils.regex;
38 
39 alias ensureDirExists = ae.sys.file.ensureDirExists;
40 
41 version (Windows)
42 {
43 	import ae.sys.install.dmc;
44 	import ae.sys.install.msys;
45 	import ae.sys.install.vs;
46 
47 	extern(Windows) void SetErrorMode(int);
48 }
49 
50 import ae.sys.install.dmd;
51 import ae.sys.install.git;
52 import ae.sys.install.kindlegen;
53 
54 static import std.process;
55 
56 /// Class which manages a D checkout and its dependencies.
57 class DManager : ICacheHost
58 {
59 	// **************************** Configuration ****************************
60 
61 	struct Config /// DManager configuration.
62 	{
63 		struct Build /// Build configuration
64 		{
65 			struct Components
66 			{
67 				bool[string] enable;
68 
69 				string[] getEnabledComponentNames()
70 				{
71 					foreach (componentName; enable.byKey)
72 						enforce(allComponents.canFind(componentName), "Unknown component: " ~ componentName);
73 					return allComponents
74 						.filter!(componentName =>
75 							enable.get(componentName, defaultComponents.canFind(componentName)))
76 						.array
77 						.dup;
78 				}
79 
80 				Component.CommonConfig common;
81 				DMD.Config dmd;
82 				Website.Config website;
83 			}
84 			Components components;
85 
86 			/// Additional environment variables.
87 			/// Supports %VAR% expansion - see applyEnv.
88 			string[string] environment;
89 		}
90 		Build build; /// ditto
91 
92 		/// Machine-local configuration
93 		/// These settings should not affect the build output.
94 		struct Local
95 		{
96 			/// URL of D git repository hosting D components.
97 			/// Defaults to (and must have the layout of) D.git:
98 			/// https://github.com/CyberShadow/D-dot-git
99 			string repoUrl = "https://bitbucket.org/cybershadow/d.git";
100 
101 			/// Location for the checkout, temporary files, etc.
102 			string workDir;
103 
104 			/// If present, passed to GNU make via -j parameter.
105 			/// Can also be "auto" or "unlimited".
106 			string makeJobs;
107 
108 			/// Don't get latest updates from GitHub.
109 			bool offline;
110 
111 			/// How to cache built files.
112 			string cache;
113 
114 			/// Maximum execution time, in seconds, of any single
115 			/// command.
116 			int timeout;
117 		}
118 		Local local; /// ditto
119 	}
120 	Config config; /// ditto
121 
122 	// Behavior options that generally depend on the host program.
123 
124 	/// Automatically re-clone the repository in case
125 	/// "git reset --hard" fails.
126 	bool autoClean;
127 
128 	/// Whether to verify working tree state
129 	/// to make sure we don't clobber user changes
130 	bool verifyWorkTree;
131 
132 	/// Whether we should cache failed builds.
133 	bool cacheFailures = true;
134 
135 	/// Current build environment.
136 	struct Environment
137 	{
138 		struct Deps /// Configuration for software dependencies
139 		{
140 			string dmcDir;   /// Where dmc.zip is unpacked.
141 			string vsDir;    /// Where Visual Studio is installed
142 			string sdkDir;   /// Where the Windows SDK is installed
143 			string hostDC;   /// Host D compiler (for DDMD bootstrapping)
144 		}
145 		Deps deps; /// ditto
146 
147 		/// Calculated local environment, incl. dependencies
148 		string[string] vars;
149 	}
150 
151 	/// Get a specific subdirectory of the work directory.
152 	@property string subDir(string name)() { return buildPath(config.local.workDir, name); }
153 
154 	alias repoDir    = subDir!"repo";        /// The git repository location.
155 	alias buildDir   = subDir!"build";       /// The build directory.
156 	alias dlDir      = subDir!"dl";          /// The directory for downloaded software.
157 	alias tmpDir     = subDir!"tmp";         /// Directory for $TMPDIR etc.
158 	alias homeDir    = subDir!"home";        /// Directory for $HOME.
159 	alias binDir     = subDir!"bin" ;        /// For wrapper scripts.
160 
161 	/// This number increases with each incompatible change to cached data.
162 	enum cacheVersion = 3;
163 
164 	string cacheEngineDir(string engineName)
165 	{
166 		// Keep compatibility with old cache paths
167 		string engineDirName =
168 			engineName.isOneOf("directory", "true") ? "cache"      :
169 			engineName.isOneOf("", "none", "false") ? "temp-cache" :
170 			"cache-" ~ engineName;
171 		return buildPath(
172 			config.local.workDir,
173 			engineDirName,
174 			"v%d".format(cacheVersion),
175 		);
176 	}
177 
178 	version (Windows)
179 	{
180 		enum string binExt = ".exe";
181 		enum configFileName = "sc.ini";
182 	}
183 	else
184 	{
185 		enum string binExt = "";
186 		enum configFileName = "dmd.conf";
187 	}
188 
189 	static bool needConfSwitch() { return exists(std.process.environment.get("HOME", null).buildPath(configFileName)); }
190 
191 	// **************************** Repositories *****************************
192 
193 	class DManagerRepository : ManagedRepository
194 	{
195 		this()
196 		{
197 			this.offline = config.local.offline;
198 			this.verify = this.outer.verifyWorkTree;
199 		}
200 
201 		override void log(string s) { return this.outer.log(s); }
202 	}
203 
204 	class MetaRepository : DManagerRepository
205 	{
206 		override void needRepo()
207 		{
208 			needGit();
209 
210 			if (!repoDir.exists)
211 			{
212 				log("Cloning initial repository...");
213 				atomic!performClone(config.local.repoUrl, repoDir);
214 			}
215 
216 			if (!git.path)
217 				git = Repository(repoDir);
218 		}
219 
220 		static void performClone(string url, string target)
221 		{
222 			import ae.sys.cmd;
223 			run(["git", "clone", url, target]);
224 		}
225 
226 		override void performCheckout(string hash)
227 		{
228 			super.performCheckout(hash);
229 			submodules = null;
230 		}
231 
232 		string[string][string] submoduleCache;
233 
234 		string[string] getSubmoduleCommits(string head)
235 		{
236 			auto pcacheEntry = head in submoduleCache;
237 			if (pcacheEntry)
238 				return (*pcacheEntry).dup;
239 
240 			string[string] result;
241 			needRepo();
242 			foreach (line; git.query("ls-tree", head).splitLines())
243 			{
244 				auto parts = line.split();
245 				if (parts.length == 4 && parts[1] == "commit")
246 					result[parts[3]] = parts[2];
247 			}
248 			assert(result.length, "No submodules found");
249 			submoduleCache[head] = result;
250 			return result.dup;
251 		}
252 
253 		/// Get the submodule state for all commits in the history.
254 		/// Returns: result[commitHash][submoduleName] == submoduleCommitHash
255 		string[string][string] getSubmoduleHistory(string[] refs)
256 		{
257 			auto marksFile = buildPath(config.local.workDir, "temp", "marks.txt");
258 			ensurePathExists(marksFile);
259 			scope(exit) if (marksFile.exists) marksFile.remove();
260 			log("Running fast-export...");
261 			auto fastExportData = git.query([
262 				"fast-export",
263 				"--full-tree",
264 				"--no-data",
265 				"--export-marks=" ~ marksFile.absolutePath,
266 				] ~ refs
267 			);
268 
269 			log("Parsing fast-export marks...");
270 
271 			auto markLines = marksFile.readText.strip.splitLines;
272 			auto marks = new string[markLines.length];
273 			foreach (line; markLines)
274 			{
275 				auto parts = line.split(' ');
276 				auto markIndex = parts[0][1..$].to!int-1;
277 				marks[markIndex] = parts[1];
278 			}
279 
280 			log("Parsing fast-export data...");
281 
282 			string[string][string] result;
283 			foreach (i, commitData; fastExportData.split("deleteall\n")[1..$])
284 				result[marks[i]] = commitData
285 					.matchAll(re!(`^M 160000 ([0-9a-f]{40}) (\S+)$`, "m"))
286 					.map!(m => tuple(m.captures[2], m.captures[1]))
287 					.assocArray
288 				;
289 			return result;
290 		}
291 	}
292 
293 	class SubmoduleRepository : DManagerRepository
294 	{
295 		string dir;
296 
297 		override void needRepo()
298 		{
299 			getMetaRepo().needRepo();
300 
301 			if (!git.path)
302 				git = Repository(dir);
303 		}
304 
305 		override void needHead(string hash)
306 		{
307 			if (!autoClean)
308 				super.needHead(hash);
309 			else
310 			try
311 				super.needHead(hash);
312 			catch (RepositoryCleanException e)
313 			{
314 				log("Error during repository cleanup.");
315 
316 				log("Nuking %s...".format(dir));
317 				rmdirRecurse(dir);
318 
319 				auto name = baseName(dir);
320 				auto gitDir = buildPath(dirName(dir), ".git", "modules", name);
321 				log("Nuking %s...".format(gitDir));
322 				rmdirRecurse(gitDir);
323 
324 				log("Updating submodule...");
325 				getMetaRepo().git.run(["submodule", "update", name]);
326 
327 				reset();
328 
329 				log("Trying again...");
330 				super.needHead(hash);
331 			}
332 		}
333 	}
334 
335 	/// The meta-repository, which contains the sub-project submodules.
336 	private MetaRepository metaRepo;
337 
338 	MetaRepository getMetaRepo() /// ditto
339 	{
340 		if (!metaRepo)
341 			metaRepo = new MetaRepository;
342 		return metaRepo;
343 	}
344 
345 	/// Sub-project repositories.
346 	private SubmoduleRepository[string] submodules;
347 
348 	ManagedRepository getSubmodule(string name) /// ditto
349 	{
350 		assert(name, "This component is not associated with a submodule");
351 		if (name !in submodules)
352 		{
353 			getMetaRepo().needRepo();
354 			enforce(name in getMetaRepo().getSubmoduleCommits(getMetaRepo().getRef("origin/master")),
355 				"Unknown submodule: " ~ name);
356 
357 			auto path = buildPath(metaRepo.git.path, name);
358 			auto gitPath = buildPath(path, ".git");
359 
360 			if (!gitPath.exists)
361 			{
362 				log("Initializing and updating submodule %s...".format(name));
363 				getMetaRepo().git.run(["submodule", "update", "--init", name]);
364 			}
365 
366 			submodules[name] = new SubmoduleRepository();
367 			submodules[name].dir = path;
368 		}
369 
370 		return submodules[name];
371 	}
372 
373 	// ***************************** Components ******************************
374 
375 	/// Base class for a D component.
376 	class Component
377 	{
378 		/// Name of this component, as registered in DManager.components AA.
379 		string name;
380 
381 		/// Corresponding subproject repository name.
382 		@property abstract string submoduleName();
383 		@property ManagedRepository submodule() { return getSubmodule(submoduleName); }
384 
385 		/// Configuration applicable to multiple (not all) components.
386 		// Note: don't serialize this structure whole!
387 		// Only serialize used fields.
388 		struct CommonConfig
389 		{
390 			version (Windows)
391 				enum defaultModel = "32";
392 			else
393 			version (D_LP64)
394 				enum defaultModel = "64";
395 			else
396 				enum defaultModel = "32";
397 
398 			/// Target comma-separated models ("32", "64", and on Windows, "32mscoff").
399 			/// Controls the models of the built Phobos and Druntime libraries.
400 			string model = defaultModel;
401 
402 			@property string[] models() { return model.split(","); }
403 			@property void models(string[] value) { this.model = value.join(","); }
404 
405 			string[] makeArgs; /// Additional make parameters,
406 			                   /// e.g. "HOST_CC=g++48"
407 		}
408 
409 		/// A string description of this component's configuration.
410 		abstract @property string configString();
411 
412 		/// Commit in the component's repo from which to build this component.
413 		@property string commit() { return incrementalBuild ? "incremental" : getComponentCommit(name); }
414 
415 		/// The components the source code of which this component depends on.
416 		/// Used for calculating the cache key.
417 		@property abstract string[] sourceDependencies();
418 
419 		/// The components the state and configuration of which this component depends on.
420 		/// Used for calculating the cache key.
421 		@property abstract string[] dependencies();
422 
423 		/// This metadata is saved to a .json file,
424 		/// and is also used to calculate the cache key.
425 		struct Metadata
426 		{
427 			int cacheVersion;
428 			string name;
429 			string commit;
430 			string configString;
431 			string[] sourceDepCommits;
432 			Metadata[] dependencyMetadata;
433 		}
434 
435 		Metadata getMetadata() /// ditto
436 		{
437 			return Metadata(
438 				cacheVersion,
439 				name,
440 				commit,
441 				configString,
442 				sourceDependencies.map!(
443 					dependency => getComponent(dependency).commit
444 				).array(),
445 				dependencies.map!(
446 					dependency => getComponent(dependency).getMetadata()
447 				).array(),
448 			);
449 		}
450 
451 		void saveMetaData(string target)
452 		{
453 			std.file.write(buildPath(target, "digger-metadata.json"), getMetadata().toJson());
454 			// Use a separate file to avoid double-encoding JSON
455 			std.file.write(buildPath(target, "digger-config.json"), configString);
456 		}
457 
458 		/// Calculates the cache key, which should be unique and immutable
459 		/// for the same source, build parameters, and build algorithm.
460 		string getBuildID()
461 		{
462 			auto configBlob = getMetadata().toJson() ~ configString;
463 			return "%s-%s-%s".format(
464 				name,
465 				commit,
466 				configBlob.getDigestString!MD5().toLower(),
467 			);
468 		}
469 
470 		@property string sourceDir() { submodule.needRepo(); return submodule.git.path; }
471 
472 		/// Directory to which built files are copied to.
473 		/// This will then be atomically added to the cache.
474 		protected string stageDir;
475 
476 		/// Prepare the source checkout for this component.
477 		/// Usually needed by other components.
478 		void needSource()
479 		{
480 			tempError++; scope(success) tempError--;
481 
482 			if (incrementalBuild)
483 				return;
484 			if (!submoduleName)
485 				return;
486 			foreach (component; getSubmoduleComponents(submoduleName))
487 				component.haveBuild = false;
488 
489 			submodule.needHead(commit);
490 			submodule.clean = false;
491 		}
492 
493 		private bool haveBuild;
494 
495 		/// Build the component in-place, as needed,
496 		/// without moving the built files anywhere.
497 		void needBuild()
498 		{
499 			if (haveBuild) return;
500 			scope(success) haveBuild = true;
501 
502 			log("needBuild: " ~ getBuildID());
503 
504 			needSource();
505 
506 			prepareEnv();
507 
508 			log("Building " ~ getBuildID());
509 			performBuild();
510 			log(getBuildID() ~ " built OK!");
511 		}
512 
513 		/// Set up / clean the build environment.
514 		private void prepareEnv()
515 		{
516 			// Nuke any additional directories cloned by makefiles
517 			if (!incrementalBuild)
518 			{
519 				getMetaRepo().git.run(["clean", "-ffdx"]);
520 
521 				foreach (dir; [tmpDir, homeDir])
522 				{
523 					if (dir.exists && !dir.dirEntries(SpanMode.shallow).empty)
524 						log("Clearing %s ...".format(dir));
525 					dir.recreateEmptyDirectory();
526 				}
527 			}
528 
529 			// Set up compiler wrappers.
530 			recreateEmptyDirectory(binDir);
531 			version (linux)
532 			{
533 				foreach (cc; ["cc", "gcc", "c++", "g++"])
534 				{
535 					auto fileName = binDir.buildPath(cc);
536 					write(fileName, q"EOF
537 #!/bin/sh
538 set -eu
539 
540 tool=$(basename "$0")
541 next=/usr/bin/$tool
542 flagfile=$TMP/nopie-flag-$tool
543 
544 if [ ! -e "$flagfile" ]
545 then
546 	echo 'Testing for -no-pie...' 1>&2
547 	testfile=$TMP/test-$$.c
548 	echo 'int main(){return 0;}' > $testfile
549 	if $next -no-pie -c -o$testfile.o $testfile
550 	then
551 		printf "%s" "-no-pie" > "$flagfile".$$.tmp
552 		mv "$flagfile".$$.tmp "$flagfile"
553 	else
554 		touch "$flagfile"
555 	fi
556 	rm -f "$testfile" "$testfile.o"
557 fi
558 
559 exec "$next" $(cat "$flagfile") "$@"
560 EOF");
561 					setAttributes(fileName, octal!755);
562 				}
563 			}
564 		}
565 
566 		private bool haveInstalled;
567 
568 		/// Build and "install" the component to buildDir as necessary.
569 		void needInstalled()
570 		{
571 			if (haveInstalled) return;
572 			scope(success) haveInstalled = true;
573 
574 			auto buildID = getBuildID();
575 			log("needInstalled: " ~ buildID);
576 
577 			needCacheEngine();
578 			if (cacheEngine.haveEntry(buildID))
579 			{
580 				log("Cache hit!");
581 				if (cacheEngine.listFiles(buildID).canFind(unbuildableMarker))
582 					throw new Exception(buildID ~ " was cached as unbuildable");
583 			}
584 			else
585 			{
586 				log("Cache miss.");
587 
588 				auto tempDir = buildPath(config.local.workDir, "temp");
589 				if (tempDir.exists)
590 					tempDir.removeRecurse();
591 				stageDir = buildPath(tempDir, buildID);
592 				stageDir.mkdirRecurse();
593 
594 				bool failed = false;
595 				tempError = 0;
596 
597 				// Save the results to cache, failed or not
598 				void saveToCache()
599 				{
600 					// Use a separate function to work around
601 					// "cannot put scope(success) statement inside scope(exit)"
602 
603 					int currentTempError = tempError;
604 
605 					// Treat cache errors an environmental errors
606 					// (for when needInstalled is invoked to build a dependency)
607 					tempError++; scope(success) tempError--;
608 
609 					// tempDir might be removed by a dependency's build failure.
610 					if (!tempDir.exists)
611 						log("Not caching %s dependency build failure.".format(name));
612 					else
613 					// Don't cache failed build results due to temporary/environment problems
614 					if (failed && currentTempError > 0)
615 					{
616 						log("Not caching %s build failure due to temporary/environment error.".format(name));
617 						rmdirRecurse(tempDir);
618 					}
619 					else
620 					// Don't cache failed build results during delve
621 					if (failed && !cacheFailures)
622 					{
623 						log("Not caching failed %s build.".format(name));
624 						rmdirRecurse(tempDir);
625 					}
626 					else
627 					if (cacheEngine.haveEntry(buildID))
628 					{
629 						// Can happen due to force==true
630 						log("Already in cache.");
631 						rmdirRecurse(tempDir);
632 					}
633 					else
634 					{
635 						log("Saving to cache.");
636 						saveMetaData(stageDir);
637 						cacheEngine.add(buildID, stageDir);
638 						rmdirRecurse(tempDir);
639 					}
640 				}
641 
642 				scope (exit)
643 					saveToCache();
644 
645 				// An incomplete build is useless, nuke the directory
646 				// and create a new one just for the "unbuildable" marker.
647 				scope (failure)
648 				{
649 					failed = true;
650 					if (stageDir.exists)
651 					{
652 						rmdirRecurse(stageDir);
653 						mkdir(stageDir);
654 						buildPath(stageDir, unbuildableMarker).touch();
655 					}
656 				}
657 
658 				needBuild();
659 
660 				performStage();
661 			}
662 
663 			install();
664 		}
665 
666 		/// Build the component in-place, without moving the built files anywhere.
667 		void performBuild() {}
668 
669 		/// Place resulting files to stageDir
670 		void performStage() {}
671 
672 		/// Update the environment post-install, to allow
673 		/// building components that depend on this one.
674 		void updateEnv(ref Environment env) {}
675 
676 		/// Copy build results from cacheDir to buildDir
677 		void install()
678 		{
679 			log("Installing " ~ getBuildID());
680 			needCacheEngine().extract(getBuildID(), buildDir, de => !de.baseName.startsWith("digger-"));
681 		}
682 
683 		/// Prepare the dependencies then run the component's tests.
684 		void test()
685 		{
686 			log("Testing " ~ getBuildID());
687 
688 			needSource();
689 
690 			submodule.clean = false;
691 			performTest();
692 			log(getBuildID() ~ " tests OK!");
693 		}
694 
695 		/// Run the component's tests.
696 		void performTest() {}
697 
698 	protected final:
699 		// Utility declarations for component implementations
700 
701 		string modelSuffix(string model) { return model == "32" ? "" : model; }
702 		version (Windows)
703 		{
704 			enum string makeFileName = "win32.mak";
705 			string makeFileNameModel(string model)
706 			{
707 				if (model == "32mscoff")
708 					model = "64";
709 				return "win"~model~".mak";
710 			}
711 			enum string binExt = ".exe";
712 		}
713 		else
714 		{
715 			enum string makeFileName = "posix.mak";
716 			string makeFileNameModel(string model) { return "posix.mak"; }
717 			enum string binExt = "";
718 		}
719 
720 		version (Windows)
721 			enum platform = "windows";
722 		else
723 		version (linux)
724 			enum platform = "linux";
725 		else
726 		version (OSX)
727 			enum platform = "osx";
728 		else
729 		version (FreeBSD)
730 			enum platform = "freebsd";
731 		else
732 			static assert(false);
733 
734 		/// Returns the command for the make utility.
735 		string[] getMake(in ref Environment env)
736 		{
737 			return [env.vars.get("MAKE", "make")];
738 		}
739 
740 		/// Returns the path to the built dmd executable.
741 		@property string dmd() { return buildPath(buildDir, "bin", "dmd" ~ binExt).absolutePath(); }
742 
743 		/// Escape a path for d_do_test's very "special" criteria.
744 		/// Spaces must be escaped, but there must be no double-quote at the end.
745 		private static string dDoTestEscape(string str)
746 		{
747 			return str.replaceAll(re!`\\([^\\ ]*? [^\\]*)(?=\\)`, `\"$1"`);
748 		}
749 
750 		unittest
751 		{
752 			assert(dDoTestEscape(`C:\Foo boo bar\baz quuz\derp.exe`) == `C:\"Foo boo bar"\"baz quuz"\derp.exe`);
753 		}
754 
755 		string[] getPlatformMakeVars(in ref Environment env, string model)
756 		{
757 			string[] args;
758 
759 			args ~= "MODEL=" ~ model;
760 
761 			version (Windows)
762 				if (model != "32")
763 				{
764 					args ~= "VCDIR="  ~ env.deps.vsDir.buildPath("VC").absolutePath();
765 					args ~= "SDKDIR=" ~ env.deps.sdkDir.absolutePath();
766 					args ~= "CC=" ~ '"' ~ env.deps.vsDir.buildPath("VC", "bin", msvcModelDir(model), "cl.exe").absolutePath() ~ '"';
767 					args ~= "LD=" ~ '"' ~ env.deps.vsDir.buildPath("VC", "bin", msvcModelDir(model), "link.exe").absolutePath() ~ '"';
768 					args ~= "AR=" ~ '"' ~ env.deps.vsDir.buildPath("VC", "bin", msvcModelDir(model), "lib.exe").absolutePath() ~ '"';
769 				}
770 
771 			return args;
772 		}
773 
774 		@property string[] gnuMakeArgs()
775 		{
776 			string[] args;
777 			if (config.local.makeJobs)
778 			{
779 				if (config.local.makeJobs == "auto")
780 				{
781 					import std.parallelism, std.conv;
782 					args ~= "-j" ~ text(totalCPUs);
783 				}
784 				else
785 				if (config.local.makeJobs == "unlimited")
786 					args ~= "-j";
787 				else
788 					args ~= "-j" ~ config.local.makeJobs;
789 			}
790 			return args;
791 		}
792 
793 		@property string[] dMakeArgs()
794 		{
795 			version (Windows)
796 				return null; // On Windows, DigitalMars make is used for all makefiles except the dmd test suite
797 			else
798 				return gnuMakeArgs;
799 		}
800 
801 		/// Older versions did not use the posix.mak/win32.mak convention.
802 		static string findMakeFile(string dir, string fn)
803 		{
804 			version (OSX)
805 				if (!dir.buildPath(fn).exists && dir.buildPath("osx.mak").exists)
806 					return "osx.mak";
807 			version (Posix)
808 				if (!dir.buildPath(fn).exists && dir.buildPath("linux.mak").exists)
809 					return "linux.mak";
810 			return fn;
811 		}
812 
813 		void needCC(ref Environment env, string model, string dmcVer = null)
814 		{
815 			version (Windows)
816 			{
817 				needDMC(env, dmcVer); // We need DMC even for 64-bit builds (for DM make)
818 				if (model != "32")
819 					needVC(env, model);
820 			}
821 		}
822 
823 		void run(const(string)[] args, in string[string] newEnv, string dir)
824 		{
825 			// Apply user environment
826 			auto env = applyEnv(newEnv, config.build.environment);
827 
828 			// Temporarily apply PATH from newEnv to our process,
829 			// so process creation lookup can use it.
830 			string oldPath = std.process.environment["PATH"];
831 			scope (exit) std.process.environment["PATH"] = oldPath;
832 			std.process.environment["PATH"] = env["PATH"];
833 
834 			// Apply timeout setting
835 			if (config.local.timeout)
836 				args = ["timeout", config.local.timeout.text] ~ args;
837 
838 			foreach (name, value; env)
839 				log("Environment: " ~ name ~ "=" ~ value);
840 			log("Working directory: " ~ dir);
841 			log("Running: " ~ escapeShellCommand(args));
842 
843 			auto status = spawnProcess(args, env, std.process.Config.newEnv, dir).wait();
844 			enforce(status == 0, "Command %s failed with status %d".format(args, status));
845 		}
846 	}
847 
848 	/// The dmd executable
849 	final class DMD : Component
850 	{
851 		@property override string submoduleName  () { return "dmd"; }
852 		@property override string[] sourceDependencies() { return []; }
853 		@property override string[] dependencies() { return []; }
854 
855 		struct Config
856 		{
857 			/// Whether to build a debug DMD.
858 			/// Debug builds are faster to build,
859 			/// but run slower.
860 			@JSONOptional bool debugDMD = false;
861 
862 			/// Whether to build a release DMD.
863 			/// Mutually exclusive with debugDMD.
864 			@JSONOptional bool releaseDMD = false;
865 
866 			/// Model for building DMD itself (on Windows).
867 			/// Can be used to build a 64-bit DMD, to avoid 4GB limit.
868 			@JSONOptional string dmdModel = CommonConfig.defaultModel;
869 
870 			/// How to build DMD versions written in D.
871 			/// We can either download a pre-built binary DMD
872 			/// package, or build an  earlier version from source
873 			/// (e.g. starting with the last C++-only version.)
874 			struct Bootstrap
875 			{
876 				/// Whether to download a pre-built D version,
877 				/// or build one from source. If set, then build
878 				/// from source according to the value of ver,
879 				@JSONOptional bool fromSource = false;
880 
881 				/// Version specification.
882 				/// When building from source, syntax can be defined
883 				/// by outer application (see parseSpec method);
884 				/// When the bootstrapping compiler is not built from source,
885 				/// it is understood as a version number, such as "v2.070.2",
886 				/// which also doubles as a tag name.
887 				/// By default (when set to null), an appropriate version
888 				/// is selected automatically.
889 				@JSONOptional string ver = null;
890 
891 				/// Build configuration for the compiler used for bootstrapping.
892 				/// If not set, then use the default build configuration.
893 				/// Used when fromSource is set.
894 				@JSONOptional DManager.Config.Build* build;
895 			}
896 			@JSONOptional Bootstrap bootstrap; /// ditto
897 
898 			/// Use Visual C++ to build DMD instead of DMC.
899 			/// Currently, this is a hack, as msbuild will consult the system
900 			/// registry and use the system-wide installation of Visual Studio.
901 			/// Only relevant for older versions, as newer versions are written in D.
902 			@JSONOptional bool useVC;
903 		}
904 
905 		@property override string configString()
906 		{
907 			static struct FullConfig
908 			{
909 				Config config;
910 				string[] makeArgs;
911 
912 				// Include the common models as well as the DMD model (from config).
913 				// Necessary to ensure the correct sc.ini is generated on Windows
914 				// (we don't want to pull in MSVC unless either DMD or Phobos are
915 				// built as 64-bit, but also we can't reuse a DMD build with 32-bit
916 				// DMD and Phobos for a 64-bit Phobos build because it won't have
917 				// the VC vars set up in its sc.ini).
918 				// Possibly refactor the compiler configuration to a separate
919 				// component in the future to avoid the inefficiency of rebuilding
920 				// DMD just to generate a different sc.ini.
921 				@JSONOptional string commonModel = Component.CommonConfig.defaultModel;
922 			}
923 
924 			return FullConfig(
925 				config.build.components.dmd,
926 				config.build.components.common.makeArgs,
927 				config.build.components.common.model,
928 			).toJson();
929 		}
930 
931 		@property string vsConfiguration() { return config.build.components.dmd.debugDMD ? "Debug" : "Release"; }
932 		@property string vsPlatform     () { return config.build.components.dmd.dmdModel == "64" ? "x64" : "Win32"; }
933 
934 		override void performBuild()
935 		{
936 			// We need an older DMC for older DMD versions
937 			string dmcVer = null;
938 			auto idgen = buildPath(sourceDir, "src", "idgen.c");
939 			if (idgen.exists && idgen.readText().indexOf(`{ "alignof" },`) >= 0)
940 				dmcVer = "850";
941 
942 			auto env = baseEnvironment;
943 			needCC(env, config.build.components.dmd.dmdModel, dmcVer); // Need VC too for VSINSTALLDIR
944 
945 			if (buildPath(sourceDir, "src", "idgen.d").exists ||
946 			    buildPath(sourceDir, "src", "ddmd", "idgen.d").exists ||
947 			    buildPath(sourceDir, "src", "ddmd", "mars.d").exists ||
948 			    buildPath(sourceDir, "src", "dmd", "mars.d").exists)
949 			{
950 				// Required for bootstrapping.
951 				needDMD(env);
952 				// Go back to our commit.
953 				needSource();
954 				submodule.clean = false;
955 			}
956 
957 			auto srcDir = buildPath(sourceDir, "src");
958 
959 			if (config.build.components.dmd.useVC) // Mostly obsolete, see useVC ddoc
960 			{
961 				version (Windows)
962 				{
963 					needVC(env, config.build.components.dmd.dmdModel);
964 
965 					env.vars["PATH"] = env.vars["PATH"] ~ pathSeparator ~ env.deps.hostDC.dirName;
966 
967 					auto solutionFile = `dmd_msc_vs10.sln`;
968 					if (!exists(srcDir.buildPath(solutionFile)))
969 						solutionFile = `vcbuild\dmd.sln`;
970 					if (!exists(srcDir.buildPath(solutionFile)))
971 						throw new Exception("Can't find Visual Studio solution file");
972 
973 					return run(["msbuild", "/p:Configuration=" ~ vsConfiguration, "/p:Platform=" ~ vsPlatform, solutionFile], env.vars, srcDir);
974 				}
975 				else
976 					throw new Exception("Can only use Visual Studio on Windows");
977 			}
978 
979 			version (Windows)
980 				auto scRoot = env.deps.dmcDir.absolutePath();
981 
982 			string dmdMakeFileName = findMakeFile(srcDir, makeFileName);
983 			string dmdMakeFullName = srcDir.buildPath(dmdMakeFileName);
984 
985 			string modelFlag = config.build.components.dmd.dmdModel;
986 			if (dmdMakeFullName.readText().canFind("MODEL=-m32"))
987 				modelFlag = "-m" ~ modelFlag;
988 
989 			version (Windows)
990 			{
991 				// A make argument is insufficient,
992 				// because of recursive make invocations
993 				auto m = dmdMakeFullName.readText();
994 				m = m
995 					.replace(`CC=\dm\bin\dmc`, `CC=dmc`)
996 					.replace(`SCROOT=$D\dm`, `SCROOT=` ~ scRoot)
997 				;
998 				dmdMakeFullName.write(m);
999 			}
1000 			else
1001 			{
1002 				auto m = dmdMakeFullName.readText();
1003 				m = m
1004 					// Fix hard-coded reference to gcc as linker
1005 					.replace(`gcc -m32 -lstdc++`, `g++ -m32 -lstdc++`)
1006 					.replace(`gcc $(MODEL) -lstdc++`, `g++ $(MODEL) -lstdc++`)
1007 					// Fix compilation of older versions of go.c with GCC 6
1008 					.replace(`-Wno-deprecated`, `-Wno-deprecated -Wno-narrowing`)
1009 				;
1010 				// Fix pthread linker error
1011 				version (linux)
1012 					m = m.replace(`-lpthread`, `-pthread`);
1013 				dmdMakeFullName.write(m);
1014 			}
1015 
1016 			submodule.saveFileState("src/" ~ dmdMakeFileName);
1017 
1018 			// Fix compilation error of older DMDs with glibc >= 2.25
1019 			version (linux)
1020 			{{
1021 				auto fn = srcDir.buildPath("root", "port.c");
1022 				if (fn.exists)
1023 				{
1024 					fn.write(fn.readText
1025 						.replace(`#include <bits/mathdef.h>`, `#include <complex.h>`)
1026 					);
1027 					submodule.saveFileState(fn.relativePath(sourceDir));
1028 				}
1029 			}}
1030 
1031 			// Fix alignment issue in older DMDs with GCC >= 7
1032 			// See https://issues.dlang.org/show_bug.cgi?id=17726
1033 			version (Posix)
1034 			{
1035 				foreach (fn; [srcDir.buildPath("tk", "mem.c"), srcDir.buildPath("ddmd", "tk", "mem.c")])
1036 					if (fn.exists)
1037 					{
1038 						fn.write(fn.readText.replace(
1039 								// `#if defined(__llvm__) && (defined(__GNUC__) || defined(__clang__))`,
1040 								// `#if defined(__GNUC__) || defined(__clang__)`,
1041 								`numbytes = (numbytes + 3) & ~3;`,
1042 								`numbytes = (numbytes + 0xF) & ~0xF;`
1043 						));
1044 						submodule.saveFileState(fn.relativePath(sourceDir));
1045 					}
1046 			}
1047 
1048 			string[] extraArgs, targets;
1049 			version (Posix)
1050 			{
1051 				if (config.build.components.dmd.debugDMD)
1052 					extraArgs ~= "DEBUG=1";
1053 				if (config.build.components.dmd.releaseDMD)
1054 					extraArgs ~= "ENABLE_RELEASE=1";
1055 			}
1056 			else
1057 			{
1058 				if (config.build.components.dmd.debugDMD)
1059 					targets ~= [];
1060 				else
1061 				if (config.build.components.dmd.releaseDMD && dmdMakeFullName.readText().canFind("reldmd"))
1062 					targets ~= ["reldmd"];
1063 				else
1064 					targets ~= ["dmd"];
1065 			}
1066 
1067 			version (Windows)
1068 			{
1069 				if (config.build.components.dmd.dmdModel != CommonConfig.defaultModel)
1070 				{
1071 					dmdMakeFileName = "win64.mak";
1072 					dmdMakeFullName = srcDir.buildPath(dmdMakeFileName);
1073 					enforce(dmdMakeFullName.exists, "dmdModel not supported for this DMD version");
1074 					extraArgs ~= "DMODEL=-m" ~ config.build.components.dmd.dmdModel;
1075 					if (config.build.components.dmd.dmdModel == "32mscoff")
1076 					{
1077 						auto objFiles = dmdMakeFullName.readText().splitLines().filter!(line => line.startsWith("OBJ_MSVC="));
1078 						enforce(!objFiles.empty, "Can't find OBJ_MSVC in win64.mak");
1079 						extraArgs ~= "OBJ_MSVC=" ~ objFiles.front.findSplit("=")[2].split().filter!(obj => obj != "ldfpu.obj").join(" ");
1080 					}
1081 				}
1082 			}
1083 
1084 			// Avoid HOST_DC reading ~/dmd.conf
1085 			string hostDC = env.deps.hostDC;
1086 			version (Posix)
1087 			if (hostDC && needConfSwitch())
1088 			{
1089 				auto dcProxy = buildPath(config.local.workDir, "host-dc-proxy.sh");
1090 				std.file.write(dcProxy, escapeShellCommand(["exec", hostDC, "-conf=" ~ buildPath(dirName(hostDC), configFileName)]) ~ ` "$@"`);
1091 				setAttributes(dcProxy, octal!755);
1092 				hostDC = dcProxy;
1093 			}
1094 
1095 			run(getMake(env) ~ [
1096 					"-f", dmdMakeFileName,
1097 					"MODEL=" ~ modelFlag,
1098 					"HOST_DC=" ~ hostDC,
1099 				] ~ config.build.components.common.makeArgs ~ dMakeArgs ~ extraArgs ~ targets,
1100 				env.vars, srcDir
1101 			);
1102 		}
1103 
1104 		override void performStage()
1105 		{
1106 			if (config.build.components.dmd.useVC)
1107 			{
1108 				foreach (ext; [".exe", ".pdb"])
1109 					cp(
1110 						buildPath(sourceDir, "src", "vcbuild", vsPlatform, vsConfiguration, "dmd_msc" ~ ext),
1111 						buildPath(stageDir , "bin", "dmd" ~ ext),
1112 					);
1113 			}
1114 			else
1115 			{
1116 				string dmdPath = buildPath(sourceDir, "generated", platform, "release", config.build.components.dmd.dmdModel, "dmd" ~ binExt);
1117 				if (!dmdPath.exists)
1118 					dmdPath = buildPath(sourceDir, "src", "dmd" ~ binExt); // legacy
1119 				enforce(dmdPath.exists && dmdPath.isFile, "Can't find built DMD executable");
1120 
1121 				cp(
1122 					dmdPath,
1123 					buildPath(stageDir , "bin", "dmd" ~ binExt),
1124 				);
1125 			}
1126 
1127 			version (Windows)
1128 			{
1129 				auto env = baseEnvironment;
1130 				needCC(env, config.build.components.dmd.dmdModel);
1131 				foreach (model; config.build.components.common.models)
1132 					needCC(env, model);
1133 
1134 				auto ini = q"EOS
1135 [Environment]
1136 LIB="%@P%\..\lib"
1137 DFLAGS="-I%@P%\..\import"
1138 DMC=__DMC__
1139 LINKCMD=%DMC%\link.exe
1140 EOS"
1141 				.replace("__DMC__", env.deps.dmcDir.buildPath(`bin`).absolutePath())
1142 			;
1143 
1144 				if (env.deps.vsDir && env.deps.sdkDir)
1145 				{
1146 					ini ~= q"EOS
1147 
1148 [Environment64]
1149 LIB="%@P%\..\lib"
1150 DFLAGS=%DFLAGS% -L/OPT:NOICF
1151 VSINSTALLDIR=__VS__\
1152 VCINSTALLDIR=%VSINSTALLDIR%VC\
1153 PATH=%PATH%;%VCINSTALLDIR%\bin\__MODELDIR__;%VCINSTALLDIR%\bin
1154 WindowsSdkDir=__SDK__
1155 LINKCMD=%VCINSTALLDIR%\bin\__MODELDIR__\link.exe
1156 LIB=%LIB%;"%VCINSTALLDIR%\lib\amd64"
1157 LIB=%LIB%;"%WindowsSdkDir%\Lib\x64"
1158 
1159 [Environment32mscoff]
1160 LIB="%@P%\..\lib"
1161 DFLAGS=%DFLAGS% -L/OPT:NOICF
1162 VSINSTALLDIR=__VS__\
1163 VCINSTALLDIR=%VSINSTALLDIR%VC\
1164 PATH=%PATH%;%VCINSTALLDIR%\bin
1165 WindowsSdkDir=__SDK__
1166 LINKCMD=%VCINSTALLDIR%\bin\link.exe
1167 LIB=%LIB%;"%VCINSTALLDIR%\lib"
1168 LIB=%LIB%;"%WindowsSdkDir%\Lib"
1169 EOS"
1170 						.replace("__VS__"      , env.deps.vsDir .absolutePath())
1171 						.replace("__SDK__"     , env.deps.sdkDir.absolutePath())
1172 						.replace("__MODELDIR__", msvcModelDir("64"))
1173 					;
1174 				}
1175 
1176 				buildPath(stageDir, "bin", configFileName).write(ini);
1177 			}
1178 			else version (OSX)
1179 			{
1180 				auto ini = q"EOS
1181 [Environment]
1182 DFLAGS="-I%@P%/../import" "-L-L%@P%/../lib"
1183 EOS";
1184 				buildPath(stageDir, "bin", configFileName).write(ini);
1185 			}
1186 			else version (linux)
1187 			{
1188 				auto ini = q"EOS
1189 [Environment32]
1190 DFLAGS="-I%@P%/../import" "-L-L%@P%/../lib" -L--export-dynamic
1191 
1192 [Environment64]
1193 DFLAGS="-I%@P%/../import" "-L-L%@P%/../lib" -L--export-dynamic -fPIC
1194 EOS";
1195 				buildPath(stageDir, "bin", configFileName).write(ini);
1196 			}
1197 			else
1198 			{
1199 				auto ini = q"EOS
1200 [Environment]
1201 DFLAGS="-I%@P%/../import" "-L-L%@P%/../lib" -L--export-dynamic
1202 EOS";
1203 				buildPath(stageDir, "bin", configFileName).write(ini);
1204 			}
1205 		}
1206 
1207 		override void updateEnv(ref Environment env)
1208 		{
1209 			// Add the DMD we built for Phobos/Druntime/Tools
1210 			env.vars["PATH"] = buildPath(buildDir, "bin").absolutePath() ~ pathSeparator ~ env.vars["PATH"];
1211 		}
1212 
1213 		override void performTest()
1214 		{
1215 			foreach (dep; ["dmd", "druntime", "phobos"])
1216 				getComponent(dep).needBuild();
1217 
1218 			foreach (model; config.build.components.common.models)
1219 			{
1220 				auto env = baseEnvironment;
1221 				version (Windows)
1222 				{
1223 					// In this order so it uses the MSYS make
1224 					needCC(env, model);
1225 					needMSYS(env);
1226 
1227 					disableCrashDialog();
1228 				}
1229 
1230 				auto makeArgs = getMake(env) ~ config.build.components.common.makeArgs ~ getPlatformMakeVars(env, model) ~ gnuMakeArgs;
1231 				version (Windows)
1232 				{
1233 					makeArgs ~= ["OS=win" ~ model[0..2], "SHELL=bash"];
1234 					if (model == "32")
1235 					{
1236 						auto extrasDir = needExtras();
1237 						// The autotester seems to pass this via environment. Why does that work there???
1238 						makeArgs ~= "LIB=" ~ extrasDir.buildPath("localextras-windows", "dmd2", "windows", "lib") ~ `;..\..\phobos`;
1239 					}
1240 					else
1241 					{
1242 						// Fix path for d_do_test and its special escaping (default is the system VS2010 install)
1243 						// We can't use the same syntax in getPlatformMakeVars because win64.mak uses "CC=\$(CC32)"\""
1244 						auto cl = env.deps.vsDir.buildPath("VC", "bin", msvcModelDir(model), "cl.exe");
1245 						foreach (ref arg; makeArgs)
1246 							if (arg.startsWith("CC="))
1247 								arg = "CC=" ~ dDoTestEscape(cl);
1248 					}
1249 				}
1250 
1251 				version (test)
1252 				{
1253 					// Only try a few tests during CI runs, to check for
1254 					// platform integration and correct invocation.
1255 					// For this purpose, the C++ ABI tests will do nicely.
1256 					makeArgs ~= [
1257 					//	"test_results/runnable/cppa.d.out", // https://github.com/dlang/dmd/pull/5686
1258 						"test_results/runnable/cpp_abi_tests.d.out",
1259 						"test_results/runnable/cabi1.d.out",
1260 					];
1261 				}
1262 
1263 				run(makeArgs, env.vars, sourceDir.buildPath("test"));
1264 			}
1265 		}
1266 	}
1267 
1268 	/// Phobos import files.
1269 	/// In older versions of D, Druntime depended on Phobos modules.
1270 	final class PhobosIncludes : Component
1271 	{
1272 		@property override string submoduleName() { return "phobos"; }
1273 		@property override string[] sourceDependencies() { return []; }
1274 		@property override string[] dependencies() { return []; }
1275 		@property override string configString() { return null; }
1276 
1277 		override void performStage()
1278 		{
1279 			foreach (f; ["std", "etc", "crc32.d"])
1280 				if (buildPath(sourceDir, f).exists)
1281 					cp(
1282 						buildPath(sourceDir, f),
1283 						buildPath(stageDir , "import", f),
1284 					);
1285 		}
1286 	}
1287 
1288 	/// Druntime. Installs only import files, but builds the library too.
1289 	final class Druntime : Component
1290 	{
1291 		@property override string submoduleName    () { return "druntime"; }
1292 		@property override string[] sourceDependencies() { return ["phobos", "phobos-includes"]; }
1293 		@property override string[] dependencies() { return ["dmd"]; }
1294 
1295 		@property override string configString()
1296 		{
1297 			static struct FullConfig
1298 			{
1299 				string model;
1300 				string[] makeArgs;
1301 			}
1302 
1303 			return FullConfig(
1304 				config.build.components.common.model,
1305 				config.build.components.common.makeArgs,
1306 			).toJson();
1307 		}
1308 
1309 		override void performBuild()
1310 		{
1311 			getComponent("phobos").needSource();
1312 			getComponent("dmd").needSource();
1313 			getComponent("dmd").needInstalled();
1314 			getComponent("phobos-includes").needInstalled();
1315 
1316 			foreach (model; config.build.components.common.models)
1317 			{
1318 				auto env = baseEnvironment;
1319 				needCC(env, model);
1320 
1321 				mkdirRecurse(sourceDir.buildPath("import"));
1322 				mkdirRecurse(sourceDir.buildPath("lib"));
1323 
1324 				setTimes(sourceDir.buildPath("src", "rt", "minit.obj"), Clock.currTime(), Clock.currTime()); // Don't rebuild
1325 				submodule.saveFileState("src/rt/minit.obj");
1326 
1327 				run(getMake(env) ~ ["-f", makeFileNameModel(model), "import", "DMD=" ~ dmd] ~ config.build.components.common.makeArgs ~ getPlatformMakeVars(env, model) ~ dMakeArgs, env.vars, sourceDir);
1328 				run(getMake(env) ~ ["-f", makeFileNameModel(model)          , "DMD=" ~ dmd] ~ config.build.components.common.makeArgs ~ getPlatformMakeVars(env, model) ~ dMakeArgs, env.vars, sourceDir);
1329 			}
1330 		}
1331 
1332 		override void performStage()
1333 		{
1334 			cp(
1335 				buildPath(sourceDir, "import"),
1336 				buildPath(stageDir , "import"),
1337 			);
1338 		}
1339 
1340 		override void performTest()
1341 		{
1342 			getComponent("druntime").needBuild();
1343 			getComponent("dmd").needInstalled();
1344 
1345 			foreach (model; config.build.components.common.models)
1346 			{
1347 				auto env = baseEnvironment;
1348 				needCC(env, model);
1349 				run(getMake(env) ~ ["-f", makeFileNameModel(model), "unittest", "DMD=" ~ dmd] ~ config.build.components.common.makeArgs ~ getPlatformMakeVars(env, model) ~ dMakeArgs, env.vars, sourceDir);
1350 			}
1351 		}
1352 	}
1353 
1354 	/// Phobos library and imports.
1355 	final class Phobos : Component
1356 	{
1357 		@property override string submoduleName    () { return "phobos"; }
1358 		@property override string[] sourceDependencies() { return []; }
1359 		@property override string[] dependencies() { return ["druntime", "dmd"]; }
1360 
1361 		@property override string configString()
1362 		{
1363 			static struct FullConfig
1364 			{
1365 				string model;
1366 				string[] makeArgs;
1367 			}
1368 
1369 			return FullConfig(
1370 				config.build.components.common.model,
1371 				config.build.components.common.makeArgs,
1372 			).toJson();
1373 		}
1374 
1375 		string[] targets;
1376 
1377 		override void performBuild()
1378 		{
1379 			getComponent("dmd").needSource();
1380 			getComponent("dmd").needInstalled();
1381 			getComponent("druntime").needBuild();
1382 
1383 			targets = null;
1384 
1385 			foreach (model; config.build.components.common.models)
1386 			{
1387 				// Clean up old object files with mismatching model.
1388 				// Necessary for a consecutive 32/64 build.
1389 				version (Windows)
1390 				{
1391 					foreach (de; dirEntries(sourceDir.buildPath("etc", "c", "zlib"), "*.obj", SpanMode.shallow))
1392 					{
1393 						auto data = cast(ubyte[])read(de.name);
1394 
1395 						string fileModel;
1396 						if (data.length < 4)
1397 							fileModel = "invalid";
1398 						else
1399 						if (data[0] == 0x80)
1400 							fileModel = "32"; // OMF
1401 						else
1402 						if (data[0] == 0x01 && data[0] == 0x4C)
1403 							fileModel = "32mscoff"; // COFF - IMAGE_FILE_MACHINE_I386
1404 						else
1405 						if (data[0] == 0x86 && data[0] == 0x64)
1406 							fileModel = "64"; // COFF - IMAGE_FILE_MACHINE_AMD64
1407 						else
1408 							fileModel = "unknown";
1409 
1410 						if (fileModel != model)
1411 						{
1412 							log("Cleaning up object file '%s' with mismatching model (file is %s, building %s)".format(de.name, fileModel, model));
1413 							remove(de.name);
1414 						}
1415 					}
1416 				}
1417 
1418 				auto env = baseEnvironment;
1419 				needCC(env, model);
1420 
1421 				string phobosMakeFileName = findMakeFile(sourceDir, makeFileNameModel(model));
1422 				string phobosMakeFullName = sourceDir.buildPath(phobosMakeFileName);
1423 
1424 				auto makeArgs = getMake(env) ~ ["-f", phobosMakeFileName, "DMD=" ~ dmd] ~ config.build.components.common.makeArgs ~ getPlatformMakeVars(env, model) ~ dMakeArgs;
1425 
1426 				version (Windows)
1427 				{
1428 					auto lib = "phobos%s.lib".format(modelSuffix(model));
1429 					run(makeArgs ~ lib, env.vars, sourceDir);
1430 					enforce(sourceDir.buildPath(lib).exists);
1431 					targets ~= ["phobos%s.lib".format(modelSuffix(model))];
1432 				}
1433 				else
1434 				{
1435 					if (phobosMakeFullName.readText().canFind("DRUNTIME = $(DRUNTIME_PATH)/lib/libdruntime-$(OS)$(MODEL).a") &&
1436 						getComponent("druntime").sourceDir.buildPath("lib").dirEntries(SpanMode.shallow).walkLength == 0 &&
1437 						exists(getComponent("druntime").sourceDir.buildPath("generated")))
1438 					{
1439 						auto dir = getComponent("druntime").sourceDir.buildPath("generated");
1440 						auto aFile  = dir.dirEntries("libdruntime.a", SpanMode.depth);
1441 						if (!aFile .empty) makeArgs ~= ["DRUNTIME="   ~ aFile .front];
1442 						auto soFile = dir.dirEntries("libdruntime.so.a", SpanMode.depth);
1443 						if (!soFile.empty) makeArgs ~= ["DRUNTIMESO=" ~ soFile.front];
1444 					}
1445 					run(makeArgs, env.vars, sourceDir);
1446 					targets ~= sourceDir
1447 						.buildPath("generated")
1448 						.dirEntries(SpanMode.depth)
1449 						.filter!(de => de.name.endsWith(".a") || de.name.endsWith(".so"))
1450 						.map!(de => de.name.relativePath(sourceDir))
1451 						.array()
1452 					;
1453 				}
1454 			}
1455 		}
1456 
1457 		override void performStage()
1458 		{
1459 			assert(targets.length, "Druntime stage without build");
1460 			foreach (lib; targets)
1461 				cp(
1462 					buildPath(sourceDir, lib),
1463 					buildPath(stageDir , "lib", lib.baseName()),
1464 				);
1465 		}
1466 
1467 		override void performTest()
1468 		{
1469 			getComponent("druntime").needBuild();
1470 			getComponent("phobos").needBuild();
1471 			getComponent("dmd").needInstalled();
1472 
1473 			foreach (model; config.build.components.common.models)
1474 			{
1475 				auto env = baseEnvironment;
1476 				needCC(env, model);
1477 				version (Windows)
1478 				{
1479 					getComponent("curl").needInstalled();
1480 					getComponent("curl").updateEnv(env);
1481 
1482 					// Patch out std.datetime unittest to work around Digger test
1483 					// suite failure on AppVeyor due to Windows time zone changes
1484 					auto stdDateTime = buildPath(sourceDir, "std", "datetime.d");
1485 					if (stdDateTime.exists && !stdDateTime.readText().canFind("Altai Standard Time"))
1486 					{
1487 						auto m = stdDateTime.readText();
1488 						m = m
1489 							.replace(`assert(tzName !is null, format("TZName which is missing: %s", winName));`, ``)
1490 							.replace(`assert(tzDatabaseNameToWindowsTZName(tzName) !is null, format("TZName which failed: %s", tzName));`, `{}`)
1491 							.replace(`assert(windowsTZNameToTZDatabaseName(tzName) !is null, format("TZName which failed: %s", tzName));`, `{}`)
1492 						;
1493 						stdDateTime.write(m);
1494 						submodule.saveFileState("std/datetime.d");
1495 					}
1496 
1497 					if (model == "32")
1498 						getComponent("extras").needInstalled();
1499 				}
1500 				run(getMake(env) ~ ["-f", makeFileNameModel(model), "unittest", "DMD=" ~ dmd] ~ config.build.components.common.makeArgs ~ getPlatformMakeVars(env, model) ~ dMakeArgs, env.vars, sourceDir);
1501 			}
1502 		}
1503 	}
1504 
1505 	/// The rdmd build tool by itself.
1506 	/// It predates the tools package.
1507 	final class RDMD : Component
1508 	{
1509 		@property override string submoduleName() { return "tools"; }
1510 		@property override string[] sourceDependencies() { return []; }
1511 		@property override string[] dependencies() { return ["dmd", "druntime", "phobos"]; }
1512 
1513 		@property string model() { return config.build.components.common.models.get(0); }
1514 
1515 		@property override string configString()
1516 		{
1517 			static struct FullConfig
1518 			{
1519 				string model;
1520 			}
1521 
1522 			return FullConfig(
1523 				this.model,
1524 			).toJson();
1525 		}
1526 
1527 		override void performBuild()
1528 		{
1529 			foreach (dep; ["dmd", "druntime", "phobos", "phobos-includes"])
1530 				getComponent(dep).needInstalled();
1531 
1532 			auto env = baseEnvironment;
1533 			needCC(env, this.model);
1534 
1535 			// Just build rdmd
1536 			bool needModel; // Need -mXX switch?
1537 
1538 			if (sourceDir.buildPath("posix.mak").exists)
1539 				needModel = true; // Known to be needed for recent versions
1540 
1541 			string[] args;
1542 			if (needConfSwitch())
1543 				args ~= ["-conf=" ~ buildPath(buildDir , "bin", configFileName)];
1544 			args ~= ["rdmd"];
1545 
1546 			if (!needModel)
1547 				try
1548 					run([dmd] ~ args, env.vars, sourceDir);
1549 				catch (Exception e)
1550 					needModel = true;
1551 
1552 			if (needModel)
1553 				run([dmd, "-m" ~ this.model] ~ args, env.vars, sourceDir);
1554 		}
1555 
1556 		override void performStage()
1557 		{
1558 			cp(
1559 				buildPath(sourceDir, "rdmd" ~ binExt),
1560 				buildPath(stageDir , "bin", "rdmd" ~ binExt),
1561 			);
1562 		}
1563 
1564 		override void performTest()
1565 		{
1566 			version (Windows)
1567 				if (this.model != "32")
1568 				{
1569 					// Can't test rdmd on non-32-bit Windows until compiler model matches Phobos model.
1570 					// rdmd_test does not use -m when building rdmd, thus linking will fail
1571 					// (because of model mismatch with the phobos we built).
1572 					log("Can't test rdmd with model " ~ this.model ~ ", skipping");
1573 					return;
1574 				}
1575 
1576 			foreach (dep; ["dmd", "druntime", "phobos", "phobos-includes"])
1577 				getComponent(dep).needInstalled();
1578 
1579 			auto env = baseEnvironment;
1580 			getComponent("dmd").updateEnv(env);
1581 			run(["dmd", "-run", "rdmd_test.d"], env.vars, sourceDir);
1582 		}
1583 	}
1584 
1585 	/// Tools package with all its components, including rdmd.
1586 	final class Tools : Component
1587 	{
1588 		@property override string submoduleName() { return "tools"; }
1589 		@property override string[] sourceDependencies() { return []; }
1590 		@property override string[] dependencies() { return ["dmd", "druntime", "phobos"]; }
1591 
1592 		@property string model() { return config.build.components.common.models.get(0); }
1593 
1594 		@property override string configString()
1595 		{
1596 			static struct FullConfig
1597 			{
1598 				string model;
1599 				string[] makeArgs;
1600 			}
1601 
1602 			return FullConfig(
1603 				this.model,
1604 				config.build.components.common.makeArgs,
1605 			).toJson();
1606 		}
1607 
1608 		override void performBuild()
1609 		{
1610 			getComponent("dmd").needSource();
1611 			foreach (dep; ["dmd", "druntime", "phobos"])
1612 				getComponent(dep).needInstalled();
1613 
1614 			auto env = baseEnvironment;
1615 			needCC(env, this.model);
1616 
1617 			run(getMake(env) ~ ["-f", makeFileName, "DMD=" ~ dmd] ~ config.build.components.common.makeArgs ~ getPlatformMakeVars(env, this.model) ~ dMakeArgs, env.vars, sourceDir);
1618 		}
1619 
1620 		override void performStage()
1621 		{
1622 			foreach (os; buildPath(sourceDir, "generated").dirEntries(SpanMode.shallow))
1623 			{
1624 				auto dir = os.buildPath(this.model);
1625 				cp(dir, buildPath(stageDir , "bin"));
1626 			}
1627 		}
1628 	}
1629 
1630 	/// Website (dlang.org). Only buildable on POSIX.
1631 	final class Website : Component
1632 	{
1633 		@property override string submoduleName() { return "dlang.org"; }
1634 		@property override string[] sourceDependencies() { return ["druntime", "phobos"]; }
1635 		@property override string[] dependencies() { return ["dmd", "druntime", "phobos", "rdmd"]; }
1636 
1637 		struct Config
1638 		{
1639 			/// Do not include timestamps, line numbers, or other
1640 			/// volatile dynamic content in generated .ddoc files.
1641 			/// Improves cache efficiency and allows meaningful diffs.
1642 			bool diffable = false;
1643 
1644 			deprecated alias noDateTime = diffable;
1645 		}
1646 
1647 		@property override string configString()
1648 		{
1649 			static struct FullConfig
1650 			{
1651 				Config config;
1652 			}
1653 
1654 			return FullConfig(
1655 				config.build.components.website,
1656 			).toJson();
1657 		}
1658 
1659 		/// Get the latest version of DMD at the time.
1660 		/// Needed for the makefile's "LATEST" parameter.
1661 		string getLatest()
1662 		{
1663 			auto dmd = getComponent("dmd").submodule;
1664 			dmd.needRepo();
1665 
1666 			auto t = dmd.git.query(["log", "--pretty=format:%ct"]).splitLines.map!(to!int).filter!(n => n > 0).front;
1667 
1668 			foreach (line; dmd.git.query(["log", "--decorate=full", "--tags", "--pretty=format:%ct%d"]).splitLines())
1669 				if (line.length > 10 && line[0..10].to!int < t)
1670 					if (line[10..$].startsWith(" (") && line.endsWith(")"))
1671 					{
1672 						foreach (r; line[12..$-1].split(", "))
1673 							if (r.skipOver("tag: refs/tags/"))
1674 								if (r.match(re!`^v2\.\d\d\d(\.\d)?$`))
1675 									return r[1..$];
1676 					}
1677 			throw new Exception("Can't find any DMD version tags at this point!");
1678 		}
1679 
1680 		private enum Target { build, test }
1681 
1682 		private void make(Target target)
1683 		{
1684 			auto env = baseEnvironment;
1685 
1686 			version (Windows)
1687 				throw new Exception("The dlang.org website is only buildable on POSIX platforms.");
1688 			else
1689 			{
1690 				getComponent("dmd").updateEnv(env);
1691 
1692 				needKindleGen(env);
1693 
1694 				foreach (dep; dependencies)
1695 					getComponent(dep).submodule.clean = false;
1696 
1697 				auto makeFullName = sourceDir.buildPath(makeFileName);
1698 				auto makeSrc = makeFullName.readText();
1699 				makeSrc
1700 					// https://github.com/D-Programming-Language/dlang.org/pull/1011
1701 					.replace(": modlist.d", ": modlist.d $(DMD)")
1702 					// https://github.com/D-Programming-Language/dlang.org/pull/1017
1703 					.replace("dpl-docs: ${DUB} ${STABLE_DMD}\n\tDFLAGS=", "dpl-docs: ${DUB} ${STABLE_DMD}\n\t${DUB} upgrade --missing-only --root=${DPL_DOCS_PATH}\n\tDFLAGS=")
1704 					.toFile(makeFullName)
1705 				;
1706 				submodule.saveFileState(makeFileName);
1707 
1708 				// Retroactive OpenSSL 1.1.0 fix
1709 				// See https://github.com/dlang/dlang.org/pull/1654
1710 				auto dubJson = sourceDir.buildPath("dpl-docs/dub.json");
1711 				dubJson
1712 					.readText()
1713 					.replace(`"versions": ["VibeCustomMain"]`, `"versions": ["VibeCustomMain", "VibeNoSSL"]`)
1714 					.toFile(dubJson);
1715 				submodule.saveFileState("dpl-docs/dub.json");
1716 				scope(exit) submodule.saveFileState("dpl-docs/dub.selections.json");
1717 
1718 				string latest = null;
1719 				if (!sourceDir.buildPath("VERSION").exists)
1720 				{
1721 					latest = getLatest();
1722 					log("LATEST=" ~ latest);
1723 				}
1724 				else
1725 					log("VERSION file found, not passing LATEST parameter");
1726 
1727 				string[] diffable = null;
1728 
1729 				string[] targets =
1730 					[
1731 						config.build.components.website.diffable
1732 						? makeSrc.indexOf("dautotest") >= 0
1733 							? ["dautotest"]
1734 							: ["all", "verbatim", "pdf"] ~ (
1735 								makeSrc.indexOf("diffable-intermediaries") >= 0
1736 								? ["diffable-intermediaries"]
1737 								: ["dlangspec.html"])
1738 						: ["all", "verbatim", "pdf", "kindle"],
1739 						["test"]
1740 					][target];
1741 
1742 				if (config.build.components.website.diffable)
1743 				{
1744 					if (makeSrc.indexOf("DIFFABLE") >= 0)
1745 						diffable = ["DIFFABLE=1"];
1746 					else
1747 						diffable = ["NODATETIME=nodatetime.ddoc"];
1748 
1749 					env.vars["SOURCE_DATE_EPOCH"] = "0";
1750 				}
1751 
1752 				auto args =
1753 					getMake(env) ~
1754 					[ "-f", makeFileName ] ~
1755 					diffable ~
1756 					(latest ? ["LATEST=" ~ latest] : []) ~
1757 					targets ~
1758 					gnuMakeArgs;
1759 				run(args, env.vars, sourceDir);
1760 			}
1761 		}
1762 
1763 		override void performBuild()
1764 		{
1765 			foreach (dep; ["dmd", "druntime", "phobos"])
1766 			{
1767 				auto c = getComponent(dep);
1768 				c.needInstalled();
1769 
1770 				// Need DMD source because https://github.com/dlang/phobos/pull/4613#issuecomment-266462596
1771 				// Need Druntime/Phobos source because we are building its documentation from there.
1772 				c.needSource();
1773 			}
1774 			getComponent("tools").needSource(); // for changed.d
1775 
1776 			make(Target.build);
1777 		}
1778 
1779 		override void performTest()
1780 		{
1781 			make(Target.test);
1782 		}
1783 
1784 		override void performStage()
1785 		{
1786 			foreach (item; ["web", "dlangspec.tex", "dlangspec.html"])
1787 			{
1788 				auto src = buildPath(sourceDir, item);
1789 				auto dst = buildPath(stageDir , item);
1790 				if (src.exists)
1791 					cp(src, dst);
1792 			}
1793 		}
1794 	}
1795 
1796 	/// Extras not built from source (DigitalMars and third-party tools and libraries)
1797 	final class Extras : Component
1798 	{
1799 		@property override string submoduleName() { return null; }
1800 		@property override string[] sourceDependencies() { return []; }
1801 		@property override string[] dependencies() { return []; }
1802 		@property override string configString() { return null; }
1803 
1804 		override void performBuild()
1805 		{
1806 			needExtras();
1807 		}
1808 
1809 		override void performStage()
1810 		{
1811 			auto extrasDir = needExtras();
1812 
1813 			void copyDir(string source, string target)
1814 			{
1815 				source = buildPath(extrasDir, "localextras-" ~ platform, "dmd2", platform, source);
1816 				target = buildPath(stageDir, target);
1817 				if (source.exists)
1818 					cp(source, target);
1819 			}
1820 
1821 			copyDir("bin", "bin");
1822 			foreach (model; config.build.components.common.models)
1823 				copyDir("bin" ~ model, "bin");
1824 			copyDir("lib", "lib");
1825 
1826 			version (Windows)
1827 				foreach (model; config.build.components.common.models)
1828 					if (model == "32")
1829 					{
1830 						// The version of snn.lib bundled with DMC will be newer.
1831 						Environment env;
1832 						needDMC(env);
1833 						cp(buildPath(env.deps.dmcDir, "lib", "snn.lib"), buildPath(stageDir, "lib", "snn.lib"));
1834 					}
1835 		}
1836 	}
1837 
1838 	/// libcurl DLL and import library for Windows.
1839 	final class Curl : Component
1840 	{
1841 		@property override string submoduleName() { return null; }
1842 		@property override string[] sourceDependencies() { return []; }
1843 		@property override string[] dependencies() { return []; }
1844 		@property override string configString() { return null; }
1845 
1846 		override void performBuild()
1847 		{
1848 			version (Windows)
1849 				needCurl();
1850 			else
1851 				log("Not on Windows, skipping libcurl download");
1852 		}
1853 
1854 		override void performStage()
1855 		{
1856 			version (Windows)
1857 			{
1858 				auto curlDir = needCurl();
1859 
1860 				void copyDir(string source, string target)
1861 				{
1862 					source = buildPath(curlDir, "dmd2", "windows", source);
1863 					target = buildPath(stageDir, target);
1864 					if (source.exists)
1865 						cp(source, target);
1866 				}
1867 
1868 				foreach (model; config.build.components.common.models)
1869 				{
1870 					auto suffix = model == "64" ? "64" : "";
1871 					copyDir("bin" ~ suffix, "bin");
1872 					copyDir("lib" ~ suffix, "lib");
1873 				}
1874 			}
1875 			else
1876 				log("Not on Windows, skipping libcurl install");
1877 		}
1878 
1879 		override void updateEnv(ref Environment env)
1880 		{
1881 			env.vars["PATH"] = buildPath(buildDir, "bin").absolutePath() ~ pathSeparator ~ env.vars["PATH"];
1882 		}
1883 	}
1884 
1885 	private int tempError;
1886 
1887 	private Component[string] components;
1888 
1889 	Component getComponent(string name)
1890 	{
1891 		if (name !in components)
1892 		{
1893 			Component c;
1894 
1895 			switch (name)
1896 			{
1897 				case "dmd":
1898 					c = new DMD();
1899 					break;
1900 				case "phobos-includes":
1901 					c = new PhobosIncludes();
1902 					break;
1903 				case "druntime":
1904 					c = new Druntime();
1905 					break;
1906 				case "phobos":
1907 					c = new Phobos();
1908 					break;
1909 				case "rdmd":
1910 					c = new RDMD();
1911 					break;
1912 				case "tools":
1913 					c = new Tools();
1914 					break;
1915 				case "website":
1916 					c = new Website();
1917 					break;
1918 				case "extras":
1919 					c = new Extras();
1920 					break;
1921 				case "curl":
1922 					c = new Curl();
1923 					break;
1924 				default:
1925 					throw new Exception("Unknown component: " ~ name);
1926 			}
1927 
1928 			c.name = name;
1929 			return components[name] = c;
1930 		}
1931 
1932 		return components[name];
1933 	}
1934 
1935 	Component[] getSubmoduleComponents(string submoduleName)
1936 	{
1937 		return components
1938 			.byValue
1939 			.filter!(component => component.submoduleName == submoduleName)
1940 			.array();
1941 	}
1942 
1943 	// **************************** Customization ****************************
1944 
1945 	/// Fetch latest D history.
1946 	void update()
1947 	{
1948 		getMetaRepo().update();
1949 	}
1950 
1951 	struct SubmoduleState
1952 	{
1953 		string[string] submoduleCommits;
1954 	}
1955 
1956 	/// Begin customization, starting at the specified commit.
1957 	SubmoduleState begin(string commit)
1958 	{
1959 		log("Starting at meta repository commit " ~ commit);
1960 		return SubmoduleState(getMetaRepo().getSubmoduleCommits(commit));
1961 	}
1962 
1963 	/// Applies a merge onto the given SubmoduleState.
1964 	void merge(ref SubmoduleState submoduleState, string submoduleName, string branch)
1965 	{
1966 		log("Merging %s commit %s".format(submoduleName, branch));
1967 		enforce(submoduleName in submoduleState.submoduleCommits, "Unknown submodule: " ~ submoduleName);
1968 		auto submodule = getSubmodule(submoduleName);
1969 		auto head = submoduleState.submoduleCommits[submoduleName];
1970 		auto result = submodule.getMerge(head, branch);
1971 		submoduleState.submoduleCommits[submoduleName] = result;
1972 	}
1973 
1974 	/// Removes a merge from the given SubmoduleState.
1975 	void unmerge(ref SubmoduleState submoduleState, string submoduleName, string branch)
1976 	{
1977 		log("Unmerging %s commit %s".format(submoduleName, branch));
1978 		enforce(submoduleName in submoduleState.submoduleCommits, "Unknown submodule: " ~ submoduleName);
1979 		auto submodule = getSubmodule(submoduleName);
1980 		auto head = submoduleState.submoduleCommits[submoduleName];
1981 		auto result = submodule.getUnMerge(head, branch);
1982 		submoduleState.submoduleCommits[submoduleName] = result;
1983 	}
1984 
1985 	/// Reverts a commit from the given SubmoduleState.
1986 	/// parent is the 1-based mainline index (as per `man git-revert`),
1987 	/// or 0 if commit is not a merge commit.
1988 	void revert(ref SubmoduleState submoduleState, string submoduleName, string commit, int parent)
1989 	{
1990 		log("Reverting %s commit %s".format(submoduleName, commit));
1991 		enforce(submoduleName in submoduleState.submoduleCommits, "Unknown submodule: " ~ submoduleName);
1992 		auto submodule = getSubmodule(submoduleName);
1993 		auto head = submoduleState.submoduleCommits[submoduleName];
1994 		auto result = submodule.getRevert(head, commit, parent);
1995 		submoduleState.submoduleCommits[submoduleName] = result;
1996 	}
1997 
1998 	/// Returns the commit hash for the given pull request #.
1999 	/// The result can then be used with addMerge/removeMerge.
2000 	string getPull(string submoduleName, int pullNumber)
2001 	{
2002 		return getSubmodule(submoduleName).getPull(pullNumber);
2003 	}
2004 
2005 	/// Returns the commit hash for the given GitHub fork.
2006 	/// The result can then be used with addMerge/removeMerge.
2007 	string getFork(string submoduleName, string user, string branch)
2008 	{
2009 		return getSubmodule(submoduleName).getFork(user, branch);
2010 	}
2011 
2012 	/// Find the child of a commit (starting with the current submodule state),
2013 	/// and, if the commit was a merge, the mainline index of said commit for the child.
2014 	void getChild(ref SubmoduleState submoduleState, string submoduleName, string commit, out string child, out int mainline)
2015 	{
2016 		enforce(submoduleName in submoduleState.submoduleCommits, "Unknown submodule: " ~ submoduleName);
2017 		auto head = submoduleState.submoduleCommits[submoduleName];
2018 		return getSubmodule(submoduleName).getChild(head, commit, child, mainline);
2019 	}
2020 
2021 	// ****************************** Building *******************************
2022 
2023 	private SubmoduleState submoduleState;
2024 	private bool incrementalBuild;
2025 
2026 	@property string cacheEngineName()
2027 	{
2028 		if (incrementalBuild)
2029 			return "none";
2030 		else
2031 			return config.local.cache;
2032 	}
2033 
2034 	private string getComponentCommit(string componentName)
2035 	{
2036 		auto submoduleName = getComponent(componentName).submoduleName;
2037 		auto commit = submoduleState.submoduleCommits.get(submoduleName, null);
2038 		enforce(commit, "Unknown commit to build for component %s (submodule %s)"
2039 			.format(componentName, submoduleName));
2040 		return commit;
2041 	}
2042 
2043 	static const string[] defaultComponents = ["dmd", "druntime", "phobos-includes", "phobos", "rdmd"];
2044 	static const string[] additionalComponents = ["tools", "website", "extras", "curl"];
2045 	static const string[] allComponents = defaultComponents ~ additionalComponents;
2046 
2047 	/// Build the specified components according to the specified configuration.
2048 	void build(SubmoduleState submoduleState, bool incremental = false)
2049 	{
2050 		auto componentNames = config.build.components.getEnabledComponentNames();
2051 		log("Building components %-(%s, %)".format(componentNames));
2052 
2053 		this.components = null;
2054 		this.submoduleState = submoduleState;
2055 		this.incrementalBuild = incremental;
2056 
2057 		if (buildDir.exists)
2058 			buildDir.removeRecurse();
2059 		enforce(!buildDir.exists);
2060 
2061 		scope(exit) if (cacheEngine) cacheEngine.finalize();
2062 
2063 		foreach (componentName; componentNames)
2064 			getComponent(componentName).needInstalled();
2065 	}
2066 
2067 	/// Shortcut for begin + build
2068 	void buildRev(string rev)
2069 	{
2070 		auto submoduleState = begin(rev);
2071 		build(submoduleState);
2072 	}
2073 
2074 	/// Simply check out the source code for the given submodules.
2075 	void checkout(SubmoduleState submoduleState)
2076 	{
2077 		auto componentNames = config.build.components.getEnabledComponentNames();
2078 		log("Checking out components %-(%s, %)".format(componentNames));
2079 
2080 		this.components = null;
2081 		this.submoduleState = submoduleState;
2082 		this.incrementalBuild = false;
2083 
2084 		foreach (componentName; componentNames)
2085 			getComponent(componentName).needSource();
2086 	}
2087 
2088 	/// Rerun build without cleaning up any files.
2089 	void rebuild()
2090 	{
2091 		build(SubmoduleState(null), true);
2092 	}
2093 
2094 	/// Run all tests for the current checkout (like rebuild).
2095 	void test()
2096 	{
2097 		auto componentNames = config.build.components.getEnabledComponentNames();
2098 		log("Testing components %-(%s, %)".format(componentNames));
2099 
2100 		this.components = null;
2101 		this.submoduleState = SubmoduleState(null);
2102 		this.incrementalBuild = true;
2103 
2104 		foreach (componentName; componentNames)
2105 			getComponent(componentName).test();
2106 	}
2107 
2108 	bool isCached(SubmoduleState submoduleState)
2109 	{
2110 		this.components = null;
2111 		this.submoduleState = submoduleState;
2112 
2113 		needCacheEngine();
2114 		foreach (componentName; config.build.components.getEnabledComponentNames())
2115 			if (!cacheEngine.haveEntry(getComponent(componentName).getBuildID()))
2116 				return false;
2117 		return true;
2118 	}
2119 
2120 	/// Returns the isCached state for all commits in the history of the given ref.
2121 	bool[string] getCacheState(string[string][string] history)
2122 	{
2123 		log("Enumerating cache entries...");
2124 		auto cacheEntries = needCacheEngine().getEntries().toSet();
2125 
2126 		this.components = null;
2127 		auto componentNames = config.build.components.getEnabledComponentNames();
2128 		auto components = componentNames.map!(componentName => getComponent(componentName)).array;
2129 		auto requiredSubmodules = components
2130 			.map!(component => chain(component.name.only, component.sourceDependencies, component.dependencies))
2131 			.joiner
2132 			.map!(componentName => getComponent(componentName).submoduleName)
2133 			.array.sort().uniq().array
2134 		;
2135 
2136 		log("Collating cache state...");
2137 		bool[string] result;
2138 		foreach (commit, submoduleCommits; history)
2139 		{
2140 			this.submoduleState.submoduleCommits = submoduleCommits;
2141 
2142 			result[commit] =
2143 				requiredSubmodules.all!(submoduleName => submoduleName in submoduleCommits) &&
2144 				componentNames.all!(componentName =>
2145 					getComponent(componentName).I!(component =>
2146 						component.getBuildID() in cacheEntries
2147 					)
2148 				);
2149 		}
2150 		return result;
2151 	}
2152 
2153 	/// ditto
2154 	bool[string] getCacheState(string[] refs)
2155 	{
2156 		auto history = getMetaRepo().getSubmoduleHistory(refs);
2157 		return getCacheState(history);
2158 	}
2159 
2160 	// **************************** Dependencies *****************************
2161 
2162 	private void needInstaller()
2163 	{
2164 		Installer.logger = &log;
2165 		Installer.installationDirectory = dlDir;
2166 	}
2167 
2168 	/// Pull in a built DMD as configured.
2169 	/// Note that this function invalidates the current repository state.
2170 	void needDMD(ref Environment env)
2171 	{
2172 		tempError++; scope(success) tempError--;
2173 
2174 		auto dmdVer = config.build.components.dmd.bootstrap.ver;
2175 		if (!dmdVer)
2176 		{
2177 			dmdVer = "v2.067.1";
2178 			version (Windows)
2179 				if (config.build.components.dmd.dmdModel != Component.CommonConfig.defaultModel)
2180 					dmdVer = "v2.070.2"; // dmd/src/builtin.d needs core.stdc.math.fabsl. 2.068.2 generates a dmd which crashes on building Phobos
2181 		}
2182 
2183 		if (config.build.components.dmd.bootstrap.fromSource)
2184 		{
2185 			log("Bootstrapping DMD " ~ dmdVer);
2186 
2187 			auto bootstrapBuildConfig = config.build.components.dmd.bootstrap.build;
2188 
2189 			// Back up and clear component state
2190 			enum backupTemplate = q{
2191 				auto VARBackup = this.VAR;
2192 				this.VAR = typeof(VAR).init;
2193 				scope(exit) this.VAR = VARBackup;
2194 			};
2195 			mixin(backupTemplate.replace(q{VAR}, q{components}));
2196 			mixin(backupTemplate.replace(q{VAR}, q{config}));
2197 			mixin(backupTemplate.replace(q{VAR}, q{submoduleState}));
2198 
2199 			config.local = configBackup.local;
2200 			if (bootstrapBuildConfig)
2201 				config.build = *bootstrapBuildConfig;
2202 
2203 			// Disable building rdmd in the bootstrap compiler by default
2204 			if ("rdmd" !in config.build.components.enable)
2205 				config.build.components.enable["rdmd"] = false;
2206 
2207 			build(parseSpec(dmdVer));
2208 
2209 			log("Built bootstrap DMD " ~ dmdVer ~ " successfully.");
2210 
2211 			auto bootstrapDir = buildPath(config.local.workDir, "bootstrap");
2212 			if (bootstrapDir.exists)
2213 				bootstrapDir.removeRecurse();
2214 			ensurePathExists(bootstrapDir);
2215 			rename(buildDir, bootstrapDir);
2216 
2217 			env.deps.hostDC = buildPath(bootstrapDir, "bin", "dmd" ~ binExt);
2218 		}
2219 		else
2220 		{
2221 			import std.ascii;
2222 			log("Preparing DMD " ~ dmdVer);
2223 			enforce(dmdVer.startsWith("v"), "Invalid DMD version spec for binary bootstrap. Did you forget to " ~
2224 				((dmdVer.length && dmdVer[0].isDigit && dmdVer.contains('.')) ? "add a leading 'v'" : "enable fromSource") ~ "?");
2225 			needInstaller();
2226 			auto dmdInstaller = new DMDInstaller(dmdVer[1..$]);
2227 			dmdInstaller.requireLocal(false);
2228 			env.deps.hostDC = dmdInstaller.exePath("dmd").absolutePath();
2229 		}
2230 
2231 		log("hostDC=" ~ env.deps.hostDC);
2232 	}
2233 
2234 	void needKindleGen(ref Environment env)
2235 	{
2236 		needInstaller();
2237 		kindleGenInstaller.requireLocal(false);
2238 		env.vars["PATH"] = kindleGenInstaller.directory ~ pathSeparator ~ env.vars["PATH"];
2239 	}
2240 
2241 	version (Windows)
2242 	void needMSYS(ref Environment env)
2243 	{
2244 		needInstaller();
2245 		MSYS.msysCORE.requireLocal(false);
2246 		MSYS.libintl.requireLocal(false);
2247 		MSYS.libiconv.requireLocal(false);
2248 		MSYS.libtermcap.requireLocal(false);
2249 		MSYS.libregex.requireLocal(false);
2250 		MSYS.coreutils.requireLocal(false);
2251 		MSYS.bash.requireLocal(false);
2252 		MSYS.make.requireLocal(false);
2253 		MSYS.grep.requireLocal(false);
2254 		MSYS.sed.requireLocal(false);
2255 		MSYS.diffutils.requireLocal(false);
2256 		env.vars["PATH"] = MSYS.bash.directory.buildPath("bin") ~ pathSeparator ~ env.vars["PATH"];
2257 	}
2258 
2259 	/// Get DMD unbuildable extras
2260 	/// (proprietary DigitalMars utilities, 32-bit import libraries)
2261 	string needExtras()
2262 	{
2263 		import ae.utils.meta : I, singleton;
2264 
2265 		static class DExtrasInstaller : Installer
2266 		{
2267 			@property override string name() { return "dmd-localextras"; }
2268 			string url = "http://semitwist.com/download/app/dmd-localextras.7z";
2269 
2270 			override void installImpl(string target)
2271 			{
2272 				url
2273 					.I!save()
2274 					.I!unpackTo(target);
2275 			}
2276 
2277 			static this()
2278 			{
2279 				urlDigests["http://semitwist.com/download/app/dmd-localextras.7z"] = "ef367c2d25d4f19f45ade56ab6991c726b07d3d9";
2280 			}
2281 		}
2282 
2283 		alias extrasInstaller = singleton!DExtrasInstaller;
2284 
2285 		needInstaller();
2286 		extrasInstaller.requireLocal(false);
2287 		return extrasInstaller.directory;
2288 	}
2289 
2290 	/// Get libcurl for Windows (DLL and import libraries)
2291 	version (Windows)
2292 	string needCurl()
2293 	{
2294 		import ae.utils.meta : I, singleton;
2295 
2296 		static class DCurlInstaller : Installer
2297 		{
2298 			@property override string name() { return "libcurl-" ~ curlVersion; }
2299 			string curlVersion = "7.47.1";
2300 			@property string url() { return "http://downloads.dlang.org/other/libcurl-" ~ curlVersion ~ "-WinSSL-zlib-x86-x64.zip"; }
2301 
2302 			override void installImpl(string target)
2303 			{
2304 				url
2305 					.I!save()
2306 					.I!unpackTo(target);
2307 			}
2308 
2309 			static this()
2310 			{
2311 				urlDigests["http://downloads.dlang.org/other/libcurl-7.47.1-WinSSL-zlib-x86-x64.zip"] = "4b8a7bb237efab25a96588093ae51994c821e097";
2312 			}
2313 		}
2314 
2315 		alias curlInstaller = singleton!DCurlInstaller;
2316 
2317 		needInstaller();
2318 		curlInstaller.requireLocal(false);
2319 		return curlInstaller.directory;
2320 	}
2321 
2322 	version (Windows)
2323 	void needDMC(ref Environment env, string ver = null)
2324 	{
2325 		tempError++; scope(success) tempError--;
2326 
2327 		needInstaller();
2328 
2329 		auto dmc = ver ? new LegacyDMCInstaller(ver) : dmcInstaller;
2330 		if (!dmc.installedLocally)
2331 			log("Preparing DigitalMars C++ " ~ ver);
2332 		dmc.requireLocal(false);
2333 		env.deps.dmcDir = dmc.directory;
2334 
2335 		auto binPath = buildPath(env.deps.dmcDir, `bin`).absolutePath();
2336 		log("DMC=" ~ binPath);
2337 		env.vars["DMC"] = binPath;
2338 		env.vars["PATH"] = binPath ~ pathSeparator ~ env.vars.get("PATH", null);
2339 	}
2340 
2341 	version (Windows)
2342 	auto getVSInstaller()
2343 	{
2344 		needInstaller();
2345 		return vs2013community;
2346 	}
2347 
2348 	version (Windows)
2349 	static string msvcModelStr(string model, string str32, string str64)
2350 	{
2351 		switch (model)
2352 		{
2353 			case "32":
2354 				throw new Exception("Shouldn't need VC for 32-bit builds");
2355 			case "64":
2356 				return str64;
2357 			case "32mscoff":
2358 				return str32;
2359 			default:
2360 				throw new Exception("Unknown model: " ~ model);
2361 		}
2362 	}
2363 
2364 	version (Windows)
2365 	static string msvcModelDir(string model, string dir64 = "x86_amd64")
2366 	{
2367 		return msvcModelStr(model, null, dir64);
2368 	}
2369 
2370 	version (Windows)
2371 	void needVC(ref Environment env, string model)
2372 	{
2373 		tempError++; scope(success) tempError--;
2374 
2375 		auto vs = getVSInstaller();
2376 
2377 		// At minimum, we want the C compiler (cl.exe) and linker (link.exe).
2378 		vs["vc_compilercore86"].requireLocal(false); // Contains both x86 and x86_amd64 cl.exe
2379 		vs["vc_compilercore86res"].requireLocal(false); // Contains clui.dll needed by cl.exe
2380 
2381 		// Include files. Needed when using VS to build either DMD or Druntime.
2382 		vs["vc_librarycore86"].requireLocal(false); // Contains include files, e.g. errno.h needed by Druntime
2383 
2384 		// C runtime. Needed for all programs built with VC.
2385 		vs[msvcModelStr(model, "vc_libraryDesktop_x86", "vc_libraryDesktop_x64")].requireLocal(false); // libcmt.lib
2386 
2387 		// XP-compatible import libraries.
2388 		vs["win_xpsupport"].requireLocal(false); // shell32.lib
2389 
2390 		// MSBuild, for the useVC option
2391 		if (config.build.components.dmd.useVC)
2392 			vs["Msi_BuildTools_MSBuild_x86"].requireLocal(false); // msbuild.exe
2393 
2394 		env.deps.vsDir  = vs.directory.buildPath("Program Files (x86)", "Microsoft Visual Studio 12.0").absolutePath();
2395 		env.deps.sdkDir = vs.directory.buildPath("Program Files", "Microsoft SDKs", "Windows", "v7.1A").absolutePath();
2396 
2397 		env.vars["PATH"] ~= pathSeparator ~ vs.modelBinPaths(msvcModelDir(model)).map!(path => vs.directory.buildPath(path).absolutePath()).join(pathSeparator);
2398 		env.vars["VCINSTALLDIR"] = env.deps.vsDir.buildPath("VC") ~ dirSeparator;
2399 		env.vars["INCLUDE"] = env.deps.vsDir.buildPath("VC", "include") ~ ";" ~ env.deps.sdkDir.buildPath("Include");
2400 		env.vars["LIB"] = env.deps.vsDir.buildPath("VC", "lib", msvcModelDir(model, "amd64")) ~ ";" ~ env.deps.sdkDir.buildPath("Lib", msvcModelDir(model, "x64"));
2401 		env.vars["WindowsSdkDir"] = env.deps.sdkDir ~ dirSeparator;
2402 		env.vars["Platform"] = "x64";
2403 		env.vars["LINKCMD64"] = env.deps.vsDir.buildPath("VC", "bin", msvcModelDir(model), "link.exe"); // Used by dmd
2404 		env.vars["MSVC_CC"] = env.deps.vsDir.buildPath("VC", "bin", msvcModelDir(model), "cl.exe"); // For the msvc-dmc wrapper
2405 		env.vars["MSVC_AR"] = env.deps.vsDir.buildPath("VC", "bin", msvcModelDir(model), "lib.exe"); // For the msvc-lib wrapper
2406 		env.vars["CL"] = "-D_USING_V110_SDK71_"; // Work around __userHeader macro redifinition VS bug
2407 	}
2408 
2409 	private void needGit()
2410 	{
2411 		tempError++; scope(success) tempError--;
2412 
2413 		needInstaller();
2414 		gitInstaller.require();
2415 	}
2416 
2417 	/// Disable the "<program> has stopped working"
2418 	/// standard Windows dialog.
2419 	version (Windows)
2420 	static void disableCrashDialog()
2421 	{
2422 		enum : uint { SEM_FAILCRITICALERRORS = 1, SEM_NOGPFAULTERRORBOX = 2 }
2423 		SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
2424 	}
2425 
2426 	/// Create a build environment base.
2427 	protected @property Environment baseEnvironment()
2428 	{
2429 		Environment env;
2430 
2431 		// Build a new environment from scratch, to avoid tainting the build with the current environment.
2432 		string[] newPaths;
2433 
2434 		version (Windows)
2435 		{
2436 			import std.utf;
2437 			import ae.sys.windows.imports;
2438 			mixin(importWin32!q{winbase});
2439 			mixin(importWin32!q{winnt});
2440 
2441 			TCHAR[1024] buf;
2442 			// Needed for DLLs
2443 			auto winDir = buf[0..GetWindowsDirectory(buf.ptr, buf.length)].toUTF8();
2444 			auto sysDir = buf[0..GetSystemDirectory (buf.ptr, buf.length)].toUTF8();
2445 			newPaths ~= [sysDir, winDir];
2446 		}
2447 		else
2448 		{
2449 			// Needed for coreutils, make, gcc, git etc.
2450 			newPaths = ["/bin", "/usr/bin"];
2451 
2452 			version (linux)
2453 			{
2454 				// GCC wrappers
2455 				ensureDirExists(binDir);
2456 				newPaths = binDir ~ newPaths;
2457 			}
2458 		}
2459 
2460 		env.vars["PATH"] = newPaths.join(pathSeparator);
2461 
2462 		ensureDirExists(tmpDir);
2463 		env.vars["TMPDIR"] = env.vars["TEMP"] = env.vars["TMP"] = tmpDir;
2464 
2465 		version (Windows)
2466 		{
2467 			env.vars["SystemDrive"] = winDir.driveName;
2468 			env.vars["SystemRoot"] = winDir;
2469 		}
2470 
2471 		ensureDirExists(homeDir);
2472 		env.vars["HOME"] = homeDir;
2473 
2474 		return env;
2475 	}
2476 
2477 	/// Apply user modifications onto an environment.
2478 	/// Supports Windows-style %VAR% expansions.
2479 	static string[string] applyEnv(in string[string] target, in string[string] source)
2480 	{
2481 		// The source of variable expansions is variables in the target environment,
2482 		// if they exist, and the host environment otherwise, so e.g.
2483 		// `PATH=C:\...;%PATH%` and `MAKE=%MAKE%` work as expected.
2484 		auto oldEnv = std.process.environment.toAA();
2485 		foreach (name, value; target)
2486 			oldEnv[name] = value;
2487 
2488 		string[string] result;
2489 		foreach (name, value; target)
2490 			result[name] = value;
2491 		foreach (name, value; source)
2492 		{
2493 			string newValue = value;
2494 			foreach (oldName, oldValue; oldEnv)
2495 				newValue = newValue.replace("%" ~ oldName ~ "%", oldValue);
2496 			result[name] = oldEnv[name] = newValue;
2497 		}
2498 		return result;
2499 	}
2500 
2501 	// ******************************** Cache ********************************
2502 
2503 	enum unbuildableMarker = "unbuildable";
2504 
2505 	DCache cacheEngine;
2506 
2507 	DCache needCacheEngine()
2508 	{
2509 		if (!cacheEngine)
2510 		{
2511 			if (cacheEngineName == "git")
2512 				needGit();
2513 			cacheEngine = createCache(cacheEngineName, cacheEngineDir(cacheEngineName), this);
2514 		}
2515 		return cacheEngine;
2516 	}
2517 
2518 	void cp(string src, string dst)
2519 	{
2520 		needCacheEngine().cp(src, dst);
2521 	}
2522 
2523 	private string[] getComponentKeyOrder(string componentName)
2524 	{
2525 		auto submodule = getComponent(componentName).submodule;
2526 		submodule.needRepo();
2527 		return submodule
2528 			.git.query("log", "--pretty=format:%H", "--all", "--topo-order")
2529 			.splitLines()
2530 			.map!(commit => componentName ~ "-" ~ commit ~ "-")
2531 			.array
2532 		;
2533 	}
2534 
2535 	string componentNameFromKey(string key)
2536 	{
2537 		auto parts = key.split("-");
2538 		return parts[0..$-2].join("-");
2539 	}
2540 
2541 	string[][] getKeyOrder(string key)
2542 	{
2543 		if (key !is null)
2544 			return [getComponentKeyOrder(componentNameFromKey(key))];
2545 		else
2546 			return allComponents.map!(componentName => getComponentKeyOrder(componentName)).array;
2547 	}
2548 
2549 	/// Optimize entire cache.
2550 	void optimizeCache()
2551 	{
2552 		needCacheEngine().optimize();
2553 	}
2554 
2555 	bool shouldPurge(string key)
2556 	{
2557 		auto files = cacheEngine.listFiles(key);
2558 		if (files.canFind(unbuildableMarker))
2559 			return true;
2560 
2561 		if (componentNameFromKey(key) == "druntime")
2562 		{
2563 			if (!files.canFind("import/core/memory.d")
2564 			 && !files.canFind("import/core/memory.di"))
2565 				return true;
2566 		}
2567 
2568 		return false;
2569 	}
2570 
2571 	/// Delete cached "unbuildable" build results.
2572 	void purgeUnbuildable()
2573 	{
2574 		needCacheEngine()
2575 			.getEntries
2576 			.filter!(key => shouldPurge(key))
2577 			.each!((key)
2578 			{
2579 				log("Deleting: " ~ key);
2580 				cacheEngine.remove(key);
2581 			})
2582 		;
2583 	}
2584 
2585 	/// Move cached files from one cache engine to another.
2586 	void migrateCache(string sourceEngineName, string targetEngineName)
2587 	{
2588 		auto sourceEngine = createCache(sourceEngineName, cacheEngineDir(sourceEngineName), this);
2589 		auto targetEngine = createCache(targetEngineName, cacheEngineDir(targetEngineName), this);
2590 		auto tempDir = buildPath(config.local.workDir, "temp");
2591 		if (tempDir.exists)
2592 			tempDir.removeRecurse();
2593 		log("Enumerating source entries...");
2594 		auto sourceEntries = sourceEngine.getEntries();
2595 		log("Enumerating target entries...");
2596 		auto targetEntries = targetEngine.getEntries().sort();
2597 		foreach (key; sourceEntries)
2598 			if (!targetEntries.canFind(key))
2599 			{
2600 				log(key);
2601 				sourceEngine.extract(key, tempDir, fn => true);
2602 				targetEngine.add(key, tempDir);
2603 				if (tempDir.exists)
2604 					tempDir.removeRecurse();
2605 			}
2606 		targetEngine.optimize();
2607 	}
2608 
2609 	// **************************** Miscellaneous ****************************
2610 
2611 	struct LogEntry
2612 	{
2613 		string hash;
2614 		string[] message;
2615 		SysTime time;
2616 	}
2617 
2618 	/// Gets the D merge log (newest first).
2619 	LogEntry[] getLog(string refName = "refs/remotes/origin/master")
2620 	{
2621 		getMetaRepo().needRepo();
2622 		auto history = getMetaRepo().git.getHistory();
2623 		LogEntry[] logs;
2624 		auto master = history.commits[history.refs[refName]];
2625 		for (auto c = master; c; c = c.parents.length ? c.parents[0] : null)
2626 		{
2627 			auto time = SysTime(c.time.unixTimeToStdTime);
2628 			logs ~= LogEntry(c.hash.toString(), c.message, time);
2629 		}
2630 		return logs;
2631 	}
2632 
2633 	// ***************************** Integration *****************************
2634 
2635 	/// Override to add logging.
2636 	void log(string line)
2637 	{
2638 	}
2639 
2640 	/// Bootstrap description resolution.
2641 	/// See DMD.Config.Bootstrap.spec.
2642 	/// This is essentially a hack to allow the entire
2643 	/// Config structure to be parsed from an .ini file.
2644 	SubmoduleState parseSpec(string spec)
2645 	{
2646 		getMetaRepo().needRepo();
2647 		auto rev = getMetaRepo().getRef("refs/tags/" ~ spec);
2648 		log("Resolved " ~ spec ~ " to " ~ rev);
2649 		return begin(rev);
2650 	}
2651 
2652 	/// Override this method with one which returns a command,
2653 	/// which will invoke the unmergeRebaseEdit function below,
2654 	/// passing to it any additional parameters.
2655 	/// Note: Currently unused. Was previously used
2656 	/// for unmerging things using interactive rebase.
2657 	abstract string getCallbackCommand();
2658 
2659 	void callback(string[] args) { assert(false); }
2660 }