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