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