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 }