1 /** 2 * Copyright: Copyright (c) 2010-2011 Jacob Carlborg. All rights reserved. 3 * Authors: Jacob Carlborg 4 * Version: Initial created: Nov 8, 2010 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 6 */ 7 module dvm.commands.Install; 8 9 import std.format : format; 10 import std.range : empty; 11 import std.stdio : writeln; 12 13 import tango.core.Exception; 14 import tango.io.Stdout; 15 import tango.io.device.File; 16 import tango.net.http.HttpGet; 17 import tango.sys.Common; 18 import tango.sys.Environment; 19 import tango.sys.Process; 20 import tango.sys.win32.Types; 21 import tango.util.compress.Zip : extractArchive; 22 23 import dvm.commands.Command; 24 import dvm.commands.DmcInstall; 25 import dvm.commands.DvmInstall; 26 import dvm.commands.Fetch; 27 import dvm.commands.Use; 28 import dvm.dvm.Wrapper; 29 import dvm.dvm._; 30 import Path = dvm.io.Path; 31 import dvm.util.Util; 32 33 class Install : Fetch 34 { 35 private 36 { 37 string archivePath; 38 string tmpCompilerPath; 39 string installPath_; 40 string binDestination_; 41 Wrapper wrapper; 42 string ver; 43 } 44 45 this () 46 { 47 super("install", "Install one or many D versions."); 48 } 49 50 override void execute () 51 { 52 // special case for the installation of dvm itself or dmc 53 if (args.any() && (args.first == "dvm" || args.first == "dmc")) 54 { 55 if (args.first == "dvm") 56 (new DvmInstall).invoke(args); 57 58 else 59 (new DmcInstall).invoke(args); 60 61 return; 62 } 63 64 install; 65 } 66 67 void install (string ver = "") 68 { 69 if(ver == "") 70 ver = getDMDVersion(); 71 72 this.ver = ver; 73 74 auto filename = buildFilename(ver); 75 auto url = buildUrl(ver, filename); 76 77 archivePath = Path.join(options.path.archives, filename); 78 79 fetch(url, archivePath); 80 writeln("Installing: dmd-", ver); 81 82 unpack; 83 moveFiles; 84 installWrapper; 85 86 version (Posix) 87 setPermissions; 88 89 installEnvironment(createEnvironment); 90 91 if (options.tango) 92 installTango; 93 } 94 95 private: 96 97 void unpack () 98 { 99 tmpCompilerPath = Path.join(options.path.tmp, "dmd-" ~ ver); 100 verbose("Unpacking:"); 101 verbose(options.indentation, "source: ", archivePath); 102 verbose(options.indentation, "destination: ", tmpCompilerPath, '\n'); 103 extractArchive(archivePath, tmpCompilerPath); 104 } 105 106 void moveFiles () 107 { 108 auto dmd = ver.length > 0 && ver[0] == '2' ? "dmd2" : "dmd"; 109 auto root = Path.join(tmpCompilerPath, dmd); 110 auto platformRoot = Path.join(root, Options.platform); 111 112 if (!Path.exists(platformRoot)) 113 throw new DvmException(format(`The platform "%s" is not compatible with the compiler dmd %s`, Options.platform, ver), __FILE__, __LINE__); 114 115 auto binSource = getBinSource(platformRoot); 116 117 auto srcSource = Path.join(root, options.path.src); 118 auto srcDest = Path.join(installPath, options.path.src); 119 120 verbose("Moving:"); 121 122 foreach (path ; getLibSources(platformRoot)) 123 { 124 auto dest = Path.join(installPath, options.platform, path.destination); 125 Path.move(path.source, dest); 126 } 127 128 Path.move(binSource, binDestination); 129 Path.move(srcSource, srcDest); 130 } 131 132 void installWrapper () 133 { 134 wrapper.target = Path.join(binDestination, "dmd" ~ options.path.executableExtension); 135 wrapper.path = Path.join(options.path.dvm, options.path.bin, "dmd-") ~ ver; 136 137 version (Windows) 138 wrapper.path ~= ".bat"; 139 140 verbose("Installing wrapper: " ~ wrapper.path); 141 wrapper.write; 142 } 143 144 void setPermissions () 145 { 146 verbose("Setting permissions:"); 147 148 setExecutableIfExists(Path.join(binDestination, "ddemangle")); 149 setExecutableIfExists(Path.join(binDestination, "dman")); 150 setExecutableIfExists(Path.join(binDestination, "dmd")); 151 setExecutableIfExists(Path.join(binDestination, "dub")); 152 setExecutableIfExists(Path.join(binDestination, "dumpobj")); 153 setExecutableIfExists(Path.join(binDestination, "dustmite")); 154 setExecutableIfExists(Path.join(binDestination, "obj2asm")); 155 setExecutableIfExists(Path.join(binDestination, "rdmd")); 156 setExecutableIfExists(Path.join(binDestination, "shell")); 157 158 setExecutableIfExists(wrapper.path); 159 } 160 161 void installEnvironment (ShellScript sh) 162 { 163 sh.path = options.path.env; 164 Path.createPath(sh.path); 165 sh.path = Path.join(sh.path, "dmd-" ~ ver ~ options.path.scriptExtension); 166 167 verbose("Installing environment: ", sh.path); 168 sh.write; 169 } 170 171 ShellScript createEnvironment () 172 { 173 auto sh = new ShellScript; 174 sh.echoOff; 175 176 auto envPath = binDestination; 177 auto binPath = Path.join(options.path.dvm, options.path.bin); 178 179 version (Posix) 180 sh.exportPath("PATH", envPath, binPath, Sh.variable("PATH", false)); 181 182 version (Windows) 183 { 184 Path.native(envPath); 185 Path.native(binPath); 186 sh.exportPath("DVM", envPath, binPath).nl; 187 sh.exportPath("PATH", envPath, Sh.variable("PATH", false)); 188 } 189 190 return sh; 191 } 192 193 void installTango () 194 { 195 verbose("Installing Tango"); 196 197 fetchTango; 198 unpackTango; 199 setupTangoEnvironment; 200 buildTango; 201 moveTangoFiles; 202 patchDmdConfForTango; 203 } 204 205 void fetchTango () 206 { 207 enum tangoUrl = "http://dsource.org/projects/tango/changeset/head/trunk?old_path=%2F&format=zip"; 208 fetch(tangoUrl, options.path.tangoZip); 209 } 210 211 void unpackTango () 212 { 213 verbose("Unpacking:"); 214 verbose(options.indentation, "source: ", options.path.tangoZip); 215 verbose(options.indentation, "destination: ", options.path.tangoTmp, '\n'); 216 extractArchive(options.path.tangoZip, options.path.tangoUnarchived); 217 } 218 219 void setupTangoEnvironment () 220 { 221 verbose(format(`Installing "%s" as the temporary D compiler`, ver)); 222 auto path = Environment.get("PATH"); 223 path = binDestination ~ options.path.pathSeparator ~ path; 224 Environment.set("PATH", path); 225 } 226 227 void buildTango () 228 { 229 version (Posix) 230 { 231 verbose("Setting permission:"); 232 permission(options.path.tangoBob, "+x"); 233 } 234 235 verbose("Building Tango..."); 236 237 string[] tangoBuildOptions = ["-r=dmd"[], "-c=dmd", "-u", "-q", "-l=" ~ options.path.tangoLibName]; 238 239 version (Posix) 240 tangoBuildOptions ~= options.is64bit ? "-m=64" : "-m=32"; 241 242 auto process = new Process(true, options.path.tangoBob ~ tangoBuildOptions ~ "."[]); 243 process.workDir = options.path.tangoTmp; 244 process.execute; 245 246 auto result = process.wait; 247 248 if (options.verbose || result.reason != Process.Result.Exit) 249 { 250 writeln("Output of the Tango build:", "\n"); 251 Stdout.copy(process.stdout).flush; 252 writeln(); 253 writeln("Process ", process.programName, '(', process.pid, ')', " exited with:"); 254 writeln(options.indentation, "reason: ", result); 255 writeln(options.indentation, "status: ", result.status, "\n"); 256 } 257 } 258 259 void moveTangoFiles () 260 { 261 verbose("Moving:"); 262 263 auto importDest = Path.join(installPath, options.path.import_); 264 265 auto tangoSource = options.path.tangoSrc; 266 auto tangoDest = Path.join(importDest, "tango"); 267 268 269 auto objectSrc = options.path.tangoObject; 270 auto objectDest = Path.join(importDest, options.path.object_di); 271 272 auto vendorSrc = options.path.tangoVendor; 273 auto vendorDest = Path.join(importDest, options.path.std); 274 275 auto libPath = options.is64bit ? options.path.lib64 : options.path.lib32; 276 277 Path.move(options.path.tangoLib, Path.join(installPath, options.platform, libPath, options.path.tangoLibName ~ options.path.libExtension)); 278 Path.move(vendorSrc, vendorDest); 279 Path.move(tangoSource, tangoDest); 280 Path.move(objectSrc, objectDest); 281 } 282 283 void patchDmdConfForTango () 284 { 285 auto dmdConfPath = Path.join(binDestination, options.path.confName); 286 287 verbose("Patching: ", dmdConfPath); 288 289 string newInclude = "-I%@P%/../../import"; 290 string newArgs = " -defaultlib=tango -debuglib=tango -version=Tango"; 291 string content = cast(string) File.get(dmdConfPath); 292 293 string oldInclude1 = "-I%@P%/../../src/phobos"; 294 string oldInclude2 = "-I%@P%/../../src/druntime/import"; 295 version (Windows) 296 { 297 oldInclude1 = '"' ~ oldInclude1 ~ '"'; 298 oldInclude2 = '"' ~ oldInclude2 ~ '"'; 299 newInclude = '"' ~ newInclude ~ '"'; 300 } 301 302 auto src = newInclude ~ newArgs; 303 304 content = content.slashSafeSubstitute(oldInclude1, src); 305 content = content.slashSafeSubstitute(oldInclude2, ""); 306 307 File.set(dmdConfPath, content); 308 } 309 310 string installPath () 311 { 312 if (installPath_.length > 0) 313 return installPath_; 314 315 return installPath_ = Path.join(options.path.compilers, "dmd-" ~ ver); 316 } 317 318 void permission (string path, string mode) 319 { 320 version (Posix) 321 { 322 verbose(options.indentation, "mode: " ~ mode); 323 verbose(options.indentation, "file: " ~ path, '\n'); 324 325 Path.permission(path, mode); 326 } 327 } 328 329 SourceDestination[] getLibSources (string platformRoot) 330 { 331 SourceDestination[] paths; 332 333 if (auto path = getLibSource(platformRoot, options.path.lib)) 334 paths ~= path; 335 336 if (auto path = getLibSource(platformRoot, options.path.lib32)) 337 paths ~= path; 338 339 if (auto path = getLibSource(platformRoot, options.path.lib64)) 340 paths ~= path; 341 342 if (paths.empty) 343 throw new DvmException("Could not find any library paths", __FILE__, __LINE__); 344 345 return paths; 346 } 347 348 SourceDestination getLibSource (string platformRoot, string libPath) 349 { 350 auto path = Path.join(platformRoot, libPath); 351 352 if (Path.exists(path)) 353 return SourceDestination(path, libPath); 354 355 else 356 return SourceDestination.invalid; 357 } 358 359 string getBinSource (string platformRoot) 360 { 361 string binPath = Path.join(platformRoot, options.path.bin); 362 363 if (Path.exists(binPath)) 364 return binPath; 365 366 if (options.is64bit) 367 { 368 binPath = Path.join(platformRoot, options.path.bin64); 369 370 if (Path.exists(binPath)) 371 return binPath; 372 373 else 374 throw new DvmException("There is no 64bit compiler available on this platform", __FILE__, __LINE__); 375 } 376 377 binPath = Path.join(platformRoot, options.path.bin32); 378 379 if (Path.exists(binPath)) 380 return binPath; 381 382 throw new DvmException("Could not find the binrary path: " ~ binPath, __FILE__, __LINE__); 383 } 384 385 void setExecutableIfExists (string path) 386 { 387 if (Path.exists(path)) 388 permission(path, "+x"); 389 } 390 391 void validateArguments (string errorMessage = null) 392 { 393 if (errorMessage.empty) 394 errorMessage = "Cannot install a compiler without specifying a version"; 395 396 super.validateArguments(errorMessage); 397 } 398 399 struct SourceDestination 400 { 401 string source; 402 string destination; 403 404 bool isValid () 405 { 406 return source.length > 0 && destination.length > 0; 407 } 408 409 bool opCast (T : bool) () 410 { 411 return isValid; 412 } 413 414 static SourceDestination invalid () 415 { 416 return SourceDestination(null, null); 417 } 418 } 419 420 string binDestination () 421 { 422 return binDestination_ = binDestination_.length > 0 ? binDestination_ : Path.join(installPath, options.platform, options.path.bin); 423 } 424 }