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