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