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