1 /** 2 * Copyright: Copyright (c) 2011 Nick Sabalausky. All rights reserved. 3 * Authors: Nick Sabalausky 4 * Version: Initial created: July 26, 2011 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 6 */ 7 module dvm.commands.Compile; 8 9 import tango.core.Exception; 10 import tango.io.Stdout; 11 import tango.io.device.File; 12 import tango.net.http.HttpGet; 13 import tango.sys.Common; 14 import tango.sys.Environment; 15 import tango.sys.Process; 16 import tango.sys.win32.Types; 17 import tango.text.Util; 18 import tango.util.compress.Zip : extractArchive; 19 20 import mambo.util.Version; 21 22 import dvm.commands.Command; 23 import dvm.commands.DmcInstall; 24 import dvm.commands.Fetch; 25 import dvm.commands.Install; 26 import dvm.commands.Use; 27 import mambo.core._; 28 import dvm.dvm.Wrapper; 29 import dvm.dvm._; 30 import Path = dvm.io.Path; 31 import dvm.sys.Process; 32 import dvm.util.Util; 33 34 class Compile : Fetch 35 { 36 private 37 { 38 static if (Windows) 39 { 40 string origMakefile = "win32.mak"; 41 string secondaryMakefile = "win32.mak"; 42 string patchedDMDMakefile = "win32-fixed.mak"; 43 } 44 45 else 46 { 47 string origMakefile = "posix.mak"; 48 string secondaryMakefile = options.platform ~ ".mak"; 49 string patchedDMDMakefile = "posix.mak"; 50 } 51 52 bool isGitStructure; 53 bool isD1; 54 55 string base; 56 string dmdBase; 57 string dmdPath; 58 string druntimePath; 59 string phobosPath; 60 string toolsPath; 61 62 string installBinName; 63 string installLibName; 64 65 string installPath; 66 string installBin; 67 string installLib; 68 69 string latestDMDPath; 70 string latestDMDBin; 71 string latestDMDLib; 72 73 string phobosLibName; 74 75 string dmdMakefile; 76 string druntimeMakefile; 77 string phobosMakefile; 78 } 79 80 this () 81 { 82 super("compile", "Compiles DMD and standard library."); 83 } 84 85 override void execute () 86 { 87 compile("", options.compileDebug); 88 } 89 90 protected: 91 92 void compile (string directory="", bool compileDebug=false) 93 { 94 if (directory == "") 95 { 96 if (args.any()) 97 directory = args.first; 98 99 else 100 directory = "."; 101 } 102 103 base = Path.normalize(directory); 104 if (base == "") 105 base = "."; 106 107 analyzeStructure; 108 verifyStructure; 109 110 verbose("Project structure: ", isGitStructure? "Git-style" : "Release-style"); 111 112 version (Windows) 113 installDMC(); 114 115 if (isGitStructure) 116 installLatestDMD; // Need this for sc.ini/dmd.conf and packaged static libs 117 118 // Save current dir 119 auto saveCwd = Environment.cwd; 120 scope(exit) Environment.cwd = saveCwd; 121 122 // Save PATH 123 auto savePath = Environment.get("PATH"); 124 scope(exit) Environment.set("PATH", savePath); 125 126 version (Windows) 127 { 128 // Using the dmc.bat wrapper to compile DMD results in a mysterious 129 // "paths with spaces" heisenbug that I can't seem to track down. 130 if (Path.exists(Path.join(options.path.compilers, "dmc"))) 131 { 132 auto dmcPath = Path.join(options.path.compilers, "dmc", "bin"); 133 addEnvPath(dmcPath); 134 } 135 } 136 137 compileDMD(compileDebug); 138 139 // Add the new dmd to PATH 140 addEnvPath(installBin); 141 142 compileDruntime; 143 compilePhobos(compileDebug); 144 compileRDMD(compileDebug); 145 } 146 147 private: 148 149 void analyzeStructure () 150 { 151 auto gitDMDPath = Path.join(base, "dmd", "src"); 152 auto gitDruntimePath = Path.join(base, "druntime"); 153 auto gitPhobosPath = Path.join(base, "phobos"); 154 auto gitToolsPath = Path.join(base, "tools"); 155 156 if (Path.exists(gitDMDPath) && Path.exists(gitPhobosPath)) 157 { 158 isGitStructure = true; 159 dmdBase = Path.join(base, "dmd"); 160 dmdPath = gitDMDPath; 161 druntimePath = gitDruntimePath; 162 phobosPath = gitPhobosPath; 163 toolsPath = gitToolsPath; 164 installPath = dmdBase; 165 } 166 else 167 { 168 isGitStructure = false; 169 dmdBase = base; 170 dmdPath = Path.join(base, "src", "dmd"); 171 druntimePath = Path.join(base, "src", "druntime"); 172 phobosPath = Path.join(base, "src", "phobos"); 173 //toolsPath = ; // N/A: Releases don't include 'rdmd.d' 174 installPath = Path.join(base, options.platform); 175 } 176 177 dmdBase = Environment.toAbsolute(dmdBase.toMutable).assumeUnique; 178 dmdPath = Environment.toAbsolute(dmdPath.toMutable).assumeUnique; 179 druntimePath = Environment.toAbsolute(druntimePath.toMutable).assumeUnique; 180 phobosPath = Environment.toAbsolute(phobosPath.toMutable).assumeUnique; 181 toolsPath = Environment.toAbsolute(toolsPath.toMutable).assumeUnique; 182 installPath = Environment.toAbsolute(installPath.toMutable).assumeUnique; 183 184 installBinName = firstExisting(["bin"[], "bin"~bitsLabel], installPath, null, "bin"~bitsLabel); 185 installLibName = firstExisting(["lib"[], "lib"~bitsLabel], installPath, null, "lib"~bitsLabel); 186 187 installBin = Path.join(installPath, installBinName); 188 installLib = Path.join(installPath, installLibName); 189 190 dmdMakefile = Path.exists(Path.join(dmdPath, origMakefile))? origMakefile : secondaryMakefile; 191 druntimeMakefile = Path.exists(Path.join(druntimePath, origMakefile))? origMakefile : secondaryMakefile; 192 phobosMakefile = Path.exists(Path.join(phobosPath, origMakefile))? origMakefile : secondaryMakefile; 193 194 isD1 = !Path.exists(druntimePath); 195 196 version (Posix) 197 patchedDMDMakefile = dmdMakefile; 198 199 version (Windows) 200 phobosLibName = "phobos"; 201 202 else 203 phobosLibName = isD1? "libphobos" : "libphobos2"; 204 205 phobosLibName = phobosLibName ~ options.path.libExtension; 206 } 207 208 void verifyStructure () 209 { 210 bool valid = true; 211 212 if (!Path.exists(Path.join(dmdPath, dmdMakefile))) 213 valid = false; 214 215 if (!isD1 && !Path.exists(Path.join(druntimePath, druntimeMakefile))) 216 valid = false; 217 218 if (!Path.exists(Path.join(phobosPath, phobosMakefile))) 219 valid = false; 220 221 if (!valid) 222 { 223 throw new DvmException( 224 "Unexpected DMD project structure in '"~base~"'\n\n"~ 225 "Make sure the path you give (or the current path) is either the\n"~ 226 "top-level of an extracted DMD release or a directory containing\n"~ 227 "Git checkouts of dmd, druntime, and phobos.", 228 __FILE__, __LINE__); 229 } 230 } 231 232 version (Windows) 233 void installDMC () 234 { 235 // Only install if not on PATH 236 auto path = Environment.exePath("dmc.exe".toMutable); 237 if(path) 238 return; 239 240 path = Environment.exePath("dmc.bat".toMutable); 241 if(path) 242 return; 243 244 auto dmcInstall = new DmcInstall(); 245 dmcInstall.execute(); 246 } 247 248 void installLatestDMD () 249 { 250 // Only install if missing 251 auto ver = "2." ~ getLatestDMDVersion("2"); 252 latestDMDPath = Path.join(options.path.compilers, "dmd-" ~ ver); 253 254 latestDMDLib = Path.join(latestDMDPath, "lib"); 255 latestDMDBin = Path.join(latestDMDPath, "bin"); 256 257 if (!Path.exists(latestDMDPath)) 258 { 259 auto install = new Install(); 260 install.install(ver); 261 } 262 } 263 264 void compileDMD (bool compileDebug) 265 { 266 Environment.cwd = dmdPath; 267 268 string targetName; 269 version (Windows) 270 targetName = compileDebug? "debdmd" : "release"; 271 272 version (Windows) 273 patchDMDMake; 274 275 // Build dmd executable 276 verbose("Building DMD: ", dmdPath); 277 auto result = system(("make -f" ~ patchedDMDMakefile ~ " " ~ targetName).toMutable); 278 279 if (result.status != 0) 280 throw new DvmException("Error building DMD's executable", __FILE__, __LINE__); 281 282 auto dmdExeName = "dmd" ~ options.path.executableExtension; 283 284 // Copy dmd executable 285 Path.copy(Path.join(dmdPath, dmdExeName), Path.join(installBin, dmdExeName)); 286 287 // Set executable permissions 288 version (Posix) 289 { 290 Path.permission(Path.join(dmdPath, dmdExeName), "+x"); 291 Path.permission(Path.join(installBin, dmdExeName), "+x"); 292 } 293 294 // Copy needed files from lib/bin directories 295 if (isGitStructure) 296 { 297 verbose("Copying lib/bin directories: "); 298 299 auto fileSets = ["lib"[]: Path.children(latestDMDLib), "bin": Path.children(latestDMDBin)]; 300 301 foreach (srcSubDir, fileSet; fileSets) 302 foreach (info; fileSet) 303 if (!info.folder) 304 if (info.name != dmdExeName && info.name != phobosLibName) 305 { 306 auto targetSubDir = (srcSubDir ~ bitsLabel).assumeUnique; 307 308 auto sourcePath = Path.join(latestDMDPath, srcSubDir, info.name); 309 auto targetPath = Path.join(installPath, targetSubDir, info.name); 310 311 if (!Path.exists(targetPath)) 312 Path.copy(sourcePath, targetPath); 313 314 else if (info.name == options.path.confName) 315 { 316 Path.copy(targetPath, targetPath ~ ".dvm-bak"); 317 Path.copy(sourcePath, targetPath); 318 } 319 } 320 321 patchDmdConf(); 322 } 323 } 324 325 void compileDruntime () 326 { 327 if (isD1) 328 return; 329 330 verbose("Building druntime: ", druntimePath); 331 332 Environment.cwd = druntimePath; 333 auto result = system(("make -f" ~ druntimeMakefile).toMutable); 334 335 if (result.status != 0) 336 throw new DvmException("Error building druntime", __FILE__, __LINE__); 337 } 338 339 void compilePhobos (bool compileDebug) 340 { 341 verbose("Building phobos: ", phobosPath); 342 343 // Rebuilding minit.obj should never be necessary, and doing so requires 344 // an assembler not included in DMC, so force it to never be rebuilt. 345 version (Windows) 346 { 347 auto minitSearchPaths = [phobosPath, druntimePath, Path.join(druntimePath, "src", "rt")]; 348 auto minitPath = firstExisting(minitSearchPaths, null, "minit.obj"); 349 touch(Path.join(minitPath, "minit.obj")); 350 } 351 352 string targetName; 353 version (Posix) 354 { 355 if (!isD1) 356 targetName = compileDebug? "debug" : "release"; 357 } 358 359 string dirDef; 360 string dmdDef; 361 version (Windows) 362 { 363 if (isD1) 364 { 365 dirDef = " " ~ quote("DIR=" ~ dmdBase); 366 dmdDef = " " ~ quote("DMD=" ~ installBin ~ `\dmd`); 367 } 368 } 369 370 auto druntimeDef = " " ~ quote("DRUNTIME="~druntimePath); 371 372 Environment.cwd = phobosPath; 373 auto result = system(("make -f" ~ phobosMakefile ~ " " ~ targetName ~ druntimeDef ~ dirDef ~ dmdDef).toMutable); 374 375 if (result.status != 0) 376 throw new DvmException("Error building phobos", __FILE__, __LINE__); 377 378 // Find phobos lib 379 auto generatedDir = Path.join(phobosPath, "generated", options.platform, targetName); 380 auto generatedBitsDir = Path.join(generatedDir, bitsLabel); 381 auto searchPaths = [generatedDir, generatedBitsDir, Path.join(phobosPath, "lib"), Path.join(phobosPath, "lib"~bitsLabel), phobosPath]; 382 auto sourcePath = firstExisting(searchPaths, null, phobosLibName); 383 sourcePath = Path.join(sourcePath, phobosLibName); 384 385 // Copy phobos lib 386 auto targetPath = Path.join(installLib, phobosLibName); 387 Path.copy(sourcePath, targetPath); 388 } 389 390 void compileRDMD (bool compileDebug) 391 { 392 auto rdmdSrc = Path.join(toolsPath, "rdmd.d"); 393 if (!isGitStructure || !Path.exists(rdmdSrc)) 394 return; 395 396 verbose("Building RDMD: ", rdmdSrc); 397 398 Environment.cwd = toolsPath; 399 auto args = compileDebug? "-debug -gc" : "-release -inline -O"; 400 auto result = system("dmd rdmd.d -wi " ~ args); 401 402 if (result.status != 0) 403 throw new DvmException("Error building RDMD", __FILE__, __LINE__); 404 405 auto rdmdExeName = "rdmd" ~ options.path.executableExtension; 406 Path.copy(Path.join(toolsPath, rdmdExeName), Path.join(installBin, rdmdExeName)); 407 } 408 409 void patchDmdConf () 410 { 411 auto patchedFile = Path.join(installBin, options.path.confName); 412 verbose("Patching: ", patchedFile); 413 414 auto content = cast(string) File.get(patchedFile); 415 416 auto newPath = isGitStructure? "%@P%/../.." : "%@P%/../../src"; 417 content = content.slashSafeSubstitute("%@P%/../src", newPath); 418 content = content.slashSafeSubstitute("%@P%/../lib", "%@P%/../"~installLibName); 419 content = content.slashSafeSubstitute("%@P%/../lib3264", "%@P%/../lib64"); 420 content = content.slashSafeSubstitute("%@P%/../lib6464", "%@P%/../lib64"); 421 422 File.set(patchedFile, content); 423 } 424 425 void patchDMDMake () 426 { 427 auto srcPath = Path.join(dmdPath, dmdMakefile); 428 auto destPath = Path.join(dmdPath, patchedDMDMakefile); 429 430 verbose("Patching:"); 431 verbose(options.indentation, "source: ", srcPath); 432 verbose(options.indentation, "destination: ", destPath, '\n'); 433 434 auto content = cast(string) File.get(srcPath); 435 436 content = content.substitute(`CC=\dm\bin\dmc`, `CC=dmc`).assumeUnique; 437 content = content.substitute(dmdMakefile, patchedDMDMakefile).assumeUnique; 438 439 File.set(destPath, content); 440 } 441 442 string bitsLabel () 443 { 444 return options.is64bit? "64" : "32"; 445 } 446 447 string quote (string str) 448 { 449 version (Windows) 450 return format(`"{}"`, str); 451 452 else 453 return format(`'{}'`, str); 454 } 455 456 void addEnvPath (string path) 457 { 458 Environment.set("PATH", path ~ options.path.pathSeparator ~ Environment.get("PATH")); 459 } 460 461 void touch(string filename) 462 { 463 // Merely opening the file for append and closing doesn't appear to work 464 auto data = File.get(filename); 465 File.set(filename, data); 466 } 467 468 // Returns the first element of paths for which 'pre/path/post' exists, 469 // or defaultPath if none. 470 string firstExisting(string[] paths, string pre = null, string post = null, string defaultPath = null) 471 { 472 foreach (string path; paths) 473 { 474 auto testPath = path; 475 testPath = pre is null? testPath : Path.join(pre, testPath); 476 testPath = post is null? testPath : Path.join(testPath, post); 477 478 if (Path.exists(testPath)) 479 return path; 480 } 481 482 return defaultPath; 483 } 484 }