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