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