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