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