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