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