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