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