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