1 /** 2 * Code to build DMD/Phobos/Druntime. 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.builder; 15 16 import std.algorithm; 17 import std.array; 18 import std.datetime; 19 import std.exception; 20 import std.file; 21 import std.path; 22 import std.process; 23 import std.string; 24 25 import ae.sys.file; 26 27 /// Class which builds D from source. 28 class DBuilder 29 { 30 /// DBuilder configuration. 31 struct Config 32 { 33 /// Build configuration. 34 struct Build 35 { 36 version (Windows) 37 enum defaultModel = "32"; 38 else 39 version (D_LP64) 40 enum defaultModel = "64"; 41 else 42 enum defaultModel = "32"; 43 44 string model = defaultModel; /// Target model ("32" or "64"). 45 bool debugDMD = false; /// Whether to build a debug DMD. 46 /// Debug builds are faster to build, 47 /// but run slower. Windows only. 48 49 /// Returns a string representation of this build configuration 50 /// usable for a cache directory name. Must reflect all fields. 51 string toString() const 52 { 53 import std.conv : text; 54 string buildID = text(model); 55 if (debugDMD) 56 buildID ~= "-debug"; 57 return buildID; 58 } 59 } 60 Build build; /// ditto 61 62 /// Local configuration. 63 struct Local 64 { 65 string repoDir; /// D source location (as checked out from GitHub). 66 string buildDir; /// Build target directory. 67 string dmcDir; /// Where dmc.zip is unpacked. 68 string vsDir; /// Where Visual Studio is installed 69 string sdkDir; /// Where the Windows SDK is installed 70 71 /// D build environment. 72 string[string] env; 73 } 74 Local local; /// ditto 75 } 76 Config config; 77 78 /// Build everything. 79 void build() 80 { 81 buildDMD(); 82 buildPhobosIncludes(); 83 buildDruntime(); 84 buildPhobos(); 85 buildTools(); 86 } 87 88 void buildDMD() 89 { 90 { 91 auto owd = pushd(buildPath(config.local.repoDir, "dmd", "src")); 92 string[] targets = config.build.debugDMD ? [] : ["dmd"]; 93 run(["make", "-f", makeFileName, "MODEL=" ~ config.build.model] ~ targets); 94 } 95 96 install( 97 buildPath(config.local.repoDir, "dmd", "src", "dmd" ~ binExt), 98 buildPath(config.local.buildDir, "bin", "dmd" ~ binExt), 99 ); 100 101 version (Windows) 102 { 103 // TODO: either properly detect where VS and the SDK are installed, 104 // or obtain and create a portable install with only the necessary components, 105 // as done here: https://github.com/CyberShadow/FarCI 106 auto ini = q"EOS 107 [Environment] 108 LIB="%@P%\..\lib" 109 DFLAGS="-I%@P%\..\import" 110 DMC=__DMC__ 111 LINKCMD=%DMC%\link.exe 112 [Environment64] 113 LIB="%@P%\..\lib" 114 DFLAGS=%DFLAGS% -L/OPT:NOICF 115 VSINSTALLDIR=__VS__\ 116 VCINSTALLDIR=%VSINSTALLDIR%VC\ 117 PATH=%PATH%;%VCINSTALLDIR%\bin\amd64 118 WindowsSdkDir=__SDK__ 119 LINKCMD=%VCINSTALLDIR%\bin\amd64\link.exe 120 LIB=%LIB%;"%VCINSTALLDIR%\lib\amd64" 121 LIB=%LIB%;"%WindowsSdkDir%\Lib\winv6.3\um\x64" 122 LIB=%LIB%;"%WindowsSdkDir%\Lib\win8\um\x64" 123 LIB=%LIB%;"%WindowsSdkDir%\Lib\x64" 124 EOS"; 125 ini = ini.replace("__DMC__", config.local.dmcDir.buildPath(`bin`).absolutePath()); 126 ini = ini.replace("__VS__" , config.local.vsDir .absolutePath()); 127 ini = ini.replace("__SDK__", config.local.sdkDir.absolutePath()); 128 129 buildPath(config.local.buildDir, "bin", "sc.ini").write(ini); 130 } 131 else 132 { 133 auto ini = q"EOS 134 [Environment] 135 DFLAGS="-I%@P%/../import" "-L-L%@P%/../lib" 136 EOS"; 137 buildPath(config.local.buildDir, "bin", "dmd.conf").write(ini); 138 } 139 140 log("DMD OK!"); 141 } 142 143 @property string[] platformMakeVars() 144 { 145 string[] args; 146 147 args ~= "MODEL=" ~ config.build.model; 148 149 version (Windows) 150 if (config.build.model == "64") 151 { 152 args ~= "VCDIR=" ~ config.local.vsDir .absolutePath() ~ `\VC`; 153 args ~= "SDKDIR=" ~ config.local.sdkDir.absolutePath(); 154 } 155 156 return args; 157 } 158 159 void buildDruntime() 160 { 161 { 162 auto owd = pushd(buildPath(config.local.repoDir, "druntime")); 163 164 mkdirRecurse("import"); 165 mkdirRecurse("lib"); 166 167 setTimes(buildPath("src", "rt", "minit.obj"), Clock.currTime(), Clock.currTime()); 168 169 run(["make", "-f", makeFileNameModel] ~ platformMakeVars); 170 } 171 172 install( 173 buildPath(config.local.repoDir, "druntime", "import"), 174 buildPath(config.local.buildDir, "import"), 175 ); 176 177 178 log("Druntime OK!"); 179 } 180 181 void buildPhobosIncludes() 182 { 183 // In older versions of D, Druntime depended on Phobos modules. 184 foreach (f; ["std", "etc", "crc32.d"]) 185 if (buildPath(config.local.repoDir, "phobos", f).exists) 186 install( 187 buildPath(config.local.repoDir, "phobos", f), 188 buildPath(config.local.buildDir, "import", f), 189 ); 190 } 191 192 void buildPhobos() 193 { 194 string[] targets; 195 196 { 197 auto owd = pushd(buildPath(config.local.repoDir, "phobos")); 198 version (Windows) 199 { 200 auto lib = "phobos%s.lib".format(modelSuffix); 201 run(["make", "-f", makeFileNameModel, lib] ~ platformMakeVars); 202 enforce(lib.exists); 203 targets = [lib]; 204 } 205 else 206 { 207 run(["make", "-f", makeFileNameModel] ~ platformMakeVars); 208 targets = "generated".dirEntries(SpanMode.depth).filter!(de => de.name.endsWith(".a")).map!(de => de.name).array(); 209 } 210 } 211 212 foreach (lib; targets) 213 install( 214 buildPath(config.local.repoDir, "phobos", lib), 215 buildPath(config.local.buildDir, "lib", lib.baseName()), 216 ); 217 218 log("Phobos OK!"); 219 } 220 221 void buildTools() 222 { 223 // Just build rdmd 224 { 225 auto owd = pushd(buildPath(config.local.repoDir, "tools")); 226 run(["dmd", "-m" ~ config.build.model, "rdmd"]); 227 } 228 install( 229 buildPath(config.local.repoDir, "tools", "rdmd" ~ binExt), 230 buildPath(config.local.buildDir, "bin", "rdmd" ~ binExt), 231 ); 232 233 log("Tools OK!"); 234 } 235 236 protected: 237 @property string modelSuffix() { return config.build.model == config.init.build.model ? "" : config.build.model; } 238 version (Windows) 239 { 240 enum string makeFileName = "win32.mak"; 241 @property string makeFileNameModel() { return "win"~config.build.model~".mak"; } 242 enum string binExt = ".exe"; 243 } 244 else 245 { 246 enum string makeFileName = "posix.mak"; 247 enum string makeFileNameModel = "posix.mak"; 248 enum string binExt = ""; 249 } 250 251 void run(string[] args, ref string[string] newEnv) 252 { 253 if (newEnv is null) newEnv = environment.toAA(); 254 string oldPath = environment["PATH"]; 255 scope(exit) environment["PATH"] = oldPath; 256 environment["PATH"] = newEnv["PATH"]; 257 log("PATH=" ~ newEnv["PATH"]); 258 259 auto status = spawnProcess(args, newEnv, .Config.newEnv).wait(); 260 enforce(status == 0, "Command %s failed with status %d".format(args, status)); 261 } 262 263 void run(string[] args...) 264 { 265 run(args, config.local.env); 266 } 267 268 void install(string src, string dst) 269 { 270 ensurePathExists(dst); 271 if (src.isDir) 272 { 273 if (!dst.exists) 274 dst.mkdirRecurse(); 275 foreach (de; src.dirEntries(SpanMode.shallow)) 276 install(de.name, dst.buildPath(de.name.baseName)); 277 } 278 else 279 { 280 debug log(src ~ " -> " ~ dst); 281 try 282 hardLink(src, dst); 283 catch (FileException e) 284 copy(src, dst); 285 } 286 } 287 288 /// Override to add logging. 289 void log(string line) 290 { 291 } 292 }