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