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