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