root/branches/bud/sss/net.d

Revision 195, 19.7 kB (checked in by Gregor, 2 years ago)

sss/*: Support for --prefix and --use with dsss net

Line 
1 /**
2  * DSSS command "net"
3  *
4  * Authors:
5  *  Gregor Richards
6  *
7  * License:
8  *  Copyright (c) 2006  Gregor Richards
9  * 
10  *  Permission is hereby granted, free of charge, to any person obtaining a
11  *  copy of this software and associated documentation files (the "Software"),
12  *  to deal in the Software without restriction, including without limitation
13  *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  *  and/or sell copies of the Software, and to permit persons to whom the
15  *  Software is furnished to do so, subject to the following conditions:
16  * 
17  *  The above copyright notice and this permission notice shall be included in
18  *  all copies or substantial portions of the Software.
19  * 
20  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  *  DEALINGS IN THE SOFTWARE.
27  */
28
29 module sss.net;
30
31 import std.cstream;
32 import std.stdio;
33 import std.string;
34 alias std.string.split split;
35 import std.file;
36 alias std.file.write write;
37 import std.path;
38 import std.random;
39 import std.regexp;
40
41 import sss.build;
42 import sss.conf;
43 import sss.install;
44 import sss.uninstall;
45
46 import hcf.path;
47 import hcf.process;
48
49 /*import mango.http.client.HttpClient;
50 import mango.http.client.HttpGet;*/
51
52 /** Entry to the "net" command */
53 int net(char[][] args)
54 {
55     // cannot be used from the source dir
56     if (inSourceDir) {
57         writefln("The 'net' subcommand cannot be used with DSSS running from the source");
58         writefln("directory. You must install DSSS.");
59         return 1;
60     }
61    
62     // make sure our sources list is up to date
63     static bool srcListUpdated = false;
64     if (!srcListUpdated) {
65         srcListUpdated = true;
66        
67         // check for cruft from pre-0.3 DSSS
68         if (exists(srcListPrefix ~ std.path.sep ~ ".svn")) {
69             rmRecursive(srcListPrefix);
70         }
71        
72         writefln("Synchronizing...");
73        
74         if (!exists(srcListPrefix ~ std.path.sep ~ "mirror")) {
75             // select a source list mirror
76             char[][] mirrorList = std.string.split(
77                 std.string.replace(
78                     cast(char[]) std.file.read(etcPrefix ~ std.path.sep ~
79                                                "dsss" ~ std.path.sep ~
80                                                "list.list"),
81                     "\r", ""),
82                 "\n");
83             while (mirrorList[$-1] == "") mirrorList = mirrorList[0..$-1];
84            
85             int sel = -1;
86            
87             if (mirrorList.length == 1) {
88                 // easy choice :)
89                 sel = 0;
90             } else {
91                 writefln("Please choose a mirror for the source list:");
92                 writefln("(Note that you may choose another mirror at any time by removing the directory");
93                 writefln("%s)", srcListPrefix);
94                 writefln("");
95                
96                 foreach (i, mirror; mirrorList) {
97                     writefln("%d) %s", i + 1, mirror);
98                 }
99                
100                 // choose
101                 char[] csel;
102                 while (sel < 0 || sel >= mirrorList.length) {
103                     csel = din.readLine();
104                     sel = atoi(csel) - 1;
105                 }
106             }
107            
108             // get it
109             mkdirP(srcListPrefix);
110             std.file.write(srcListPrefix ~ std.path.sep ~ "mirror",
111                            mirrorList[sel]);
112             char[] mirror = cast(char[]) std.file.read(
113                 srcListPrefix ~ std.path.sep ~ "mirror");
114             sayAndSystem("curl -s -S -k " ~ mirror ~ "/source.list "
115                         "-o " ~ srcListPrefix ~ std.path.sep ~ "source.list");
116             sayAndSystem("curl -s -S -k " ~ mirror ~ "/pkgs.list "
117                         "-o " ~ srcListPrefix ~ std.path.sep ~ "pkgs.list");
118             sayAndSystem("curl -s -S -k " ~ mirror ~ "/mirrors.list "
119                         "-o " ~ srcListPrefix ~ std.path.sep ~ "mirrors.list");
120         } else {
121             char[] mirror = cast(char[]) std.file.read(
122                 srcListPrefix ~ std.path.sep ~ "mirror");
123             char[] srcList = srcListPrefix ~ std.path.sep ~ "source.list";
124             char[] pkgsList = srcListPrefix ~ std.path.sep ~ "pkgs.list";
125             char[] mirrorsList = srcListPrefix ~ std.path.sep ~ "mirrors.list";
126            
127             sayAndSystem("curl -s -S -k " ~ mirror ~ "/source.list "
128                         "-o " ~ srcList ~
129                         " -z " ~ srcList);
130             sayAndSystem("curl -s -S -k " ~ mirror ~ "/pkgs.list "
131                         "-o " ~ pkgsList ~
132                         " -z " ~ pkgsList);
133             sayAndSystem("curl -s -S -k " ~ mirror ~ "/mirrors.list "
134                         "-o " ~ mirrorsList ~
135                         " -z " ~ mirrorsList);
136         }
137        
138         writefln("");
139     }
140    
141     // load it
142     NetConfig conf = ReadNetConfig();
143    
144     // now switch on the command
145     if (args.length < 1) {
146         writefln("The net command requires a second command as a parameter.");
147         return 1;
148     }
149     switch (args[0]) {
150         case "deps":
151         {
152             // install dependencies
153             DSSSConf dconf = readConfig(null);
154             char[][] deps = sourceToDeps(conf, dconf);
155             foreach (dep; deps) {
156                 if (dep == "" || dep == dconf.settings[""]["name"]) continue;
157                
158                 char[][] netcommand;
159                 netcommand ~= "assert";
160                 netcommand ~= dep;
161                
162                 writefln("\n\nInstalling %s\n", dep);
163                 int netret = net(netcommand);
164                 if (netret) return netret;
165             }
166            
167             return 0;
168         }
169        
170         case "assert":
171         {
172             // make sure that the tool is installed, install it if not
173            
174             // check for manifest files in every usedir
175             bool found = false;
176             char[] manifestFile = manifestPrefix ~ std.path.sep ~ args[1] ~ ".manifest";
177             if (exists(manifestFile)) {
178                 found = true;
179             } else {
180                
181                 foreach (dir; useDirs) {
182                     manifestFile = dir ~ std.path.sep ~
183                         "share" ~ std.path.sep ~
184                         "dsss" ~ std.path.sep ~
185                         "manifest" ~ std.path.sep ~
186                         args[1] ~ ".manifest";
187                     if (exists(manifestFile)) {
188                         found = true;
189                         break;
190                     }
191                 }
192             }
193            
194             if (found) {
195                 writefln("%s is already installed.\n", args[1]);
196                 return 0;
197             }
198            
199             // fall through
200         }
201        
202         case "fetch":
203         case "install":
204         {
205             // download and install the specified package and its dependencies
206             if (args.length < 2) {
207                 writefln("No package name specified.");
208                 return 1;
209             }
210            
211             // 0) sanity
212             if (!(args[1] in conf.vers)) {
213                 writefln("That package does not appear to exist!");
214                 return 1;
215             }
216            
217             // 1) make the source directory
218             char[] srcDir = scratchPrefix ~ std.path.sep ~ "DSSS_" ~ args[1];
219             char[] tmpDir = srcDir;
220             mkdirP(srcDir);
221             writefln("Working in %s", srcDir);
222            
223             // 2) chdir
224             char[] origcwd = getcwd();
225             chdir(srcDir);
226            
227             // make sure the directory gets removed
228             scope(exit) {
229                 chdir(origcwd);
230                 rmRecursive(tmpDir);
231             }
232            
233             // 3) get sources
234             if (!getSources(args[1], conf)) return 1;
235             srcDir = getcwd();
236            
237             // if we're just fetching, make the archive
238             if (args[0] == "fetch") {
239                 char[] archname = args[1] ~ ".tar.gz";
240                
241                 // compress
242                 version (Windows) {
243                     system("bsdtar -zcf " ~ archname ~ " " ~ std.string.join(
244                         listdir(".", RegExp(r"^[^\.]")),
245                         " "));
246                 } else {
247                     system("tar -cf - * | gzip -c > " ~ archname);
248                 }
249                
250                 // move into place
251                 std.file.rename(archname,
252                                 origcwd ~ std.path.sep ~ archname);
253                
254                 writefln("Archive %s created.", archname);
255                 return 0;
256             } else {
257                 // 4) make sure it's not installed
258                 uninstall(args[1..2]);
259            
260                 // 5) install prerequisites
261                 char[][] netcmd;
262                 netcmd ~= "deps";
263                 int netret = net(netcmd);
264                 if (netret) return netret;
265                 chdir(srcDir);
266            
267                 // 6) build
268                 DSSSConf dconf = readConfig(null);
269                 int buildret = build(args[2..$], dconf);
270                 if (buildret) return buildret;
271            
272                 // 7) install
273                 return install(args[2..$]);
274             }
275         }
276        
277         case "list":
278         {
279             // Just list installable packages
280             foreach (pkg; conf.srcURL.keys.sort) {
281                 writefln("%s", pkg);
282             }
283             return 0;
284         }
285        
286         case "search":
287         {
288             // List matching packages
289             if (args.length < 2) {
290                 writefln("Search for what?");
291                 return 1;
292             }
293            
294             foreach (pkg; conf.srcURL.keys.sort) {
295                 if (std.regexp.find(pkg, args[1]) != -1) {
296                     writefln("%s", pkg);
297                 }
298             }
299            
300             return 0;
301         }
302        
303         default:
304             writefln("Unrecognized command: %s", args[0]);
305             return 1;
306     }
307 }
308
309 /** Net config object */
310 class NetConfig {
311     /** The mirror in use */
312     char[] mirror;
313    
314     /** Versions of packages */
315     char[][char[]] vers;
316    
317     /** Dependencies of packages */
318     char[][][char[]] deps;
319    
320     /** Source formats of packages */
321     char[][char[]] srcFormat;
322    
323     /** Source URL of packages */
324     char[][char[]] srcURL;
325    
326     /** Patches */
327     char[][][char[]] srcPatches;
328 }
329
330 /** Read the net configuration info */
331 NetConfig ReadNetConfig()
332 {
333     NetConfig conf = new NetConfig();
334    
335     // read in the mirror
336     conf.mirror = cast(char[]) std.file.read(srcListPrefix ~ std.path.sep ~ "mirror");
337    
338     // read in the main tool/dep/version list
339     char[] pkgslist = std.string.replace(
340         cast(char[]) std.file.read(srcListPrefix ~ std.path.sep ~ "pkgs.list"),
341         "\r", "");
342     foreach (pkg; std.string.split(pkgslist, "\n")) {
343         if (pkg.length == 0 || pkg[0] == '#') continue;
344        
345         char[][] pkinfo = std.string.split(pkg, " ");
346        
347         // format: pkg ver deps
348         if (pkinfo.length < 2) continue;
349         conf.vers[pkinfo[0]] = pkinfo[1];
350         conf.deps[pkinfo[0]] = pkinfo[2..$];
351     }
352    
353     // then read in the source list
354     char[] srclist = cast(char[]) std.file.read(srcListPrefix ~ std.path.sep ~ "source.list");
355     foreach (pkg; std.string.split(srclist, "\n")) {
356         if (pkg.length == 0 || pkg[0] == '#') continue;
357        
358         char[][] pkinfo = std.string.split(pkg, " ");
359        
360         //format: pkg protocol/format URL [patches]
361         if (pkinfo.length < 3) continue;
362         conf.srcFormat[pkinfo[0]] = pkinfo[1];
363         conf.srcURL[pkinfo[0]] = pkinfo[2];
364         conf.srcPatches[pkinfo[0]] = pkinfo[3..$];
365     }
366    
367     return conf;
368 }
369
370 /** Generate a list of dependencies for the current source */
371 char[][] sourceToDeps(NetConfig nconf = null, DSSSConf conf = null)
372 {
373     if (nconf is null) {
374         nconf = ReadNetConfig();
375     }
376     if (conf is null) {
377         conf = readConfig(null);
378     }
379    
380     // start with the requires setting
381     char[][] deps;
382     if ("requires" in conf.settings[""]) {
383         deps ~= std.string.split(conf.settings[""]["requires"]);
384     }
385    
386     // then trace uses
387     foreach (section; conf.sections) {
388         char[][] files;
389         char[] type = conf.settings[section]["type"];
390         if (type == "binary") {
391             files ~= section;
392         } else if (type == "library") {
393             files ~= targetToFiles(section, conf);
394         } else if (type == "subdir") {
395             // recurse
396             char[] origcwd = getcwd();
397             chdir(section);
398             deps ~= sourceToDeps(nconf);
399             chdir(origcwd);
400             continue;
401         } else {
402             // ignore
403             continue;
404         }
405        
406         // make a uses file
407         char[] usesLine = dsss_build ~ " -test -uses=temp.uses " ~
408             std.string.join(files, " ");
409         saySystemDie(usesLine);
410        
411         // then read the uses
412         char[] uses = cast(char[]) std.file.read("temp.uses");
413         foreach (use; std.string.split(uses, "\n")) {
414             if (use.length == 0) continue;
415             if (use[$-1] == '\r') use = use[0 .. $-1];
416             if (use.length == 0) continue;
417            
418             if (use == "[USEDBY]") break;
419             if (use[0] == '[') continue;
420            
421             // OK, we're definitely reading a use - split by " <> "
422             char[][] useinfo = std.string.split(use, " <> ");
423             if (useinfo.length < 2) continue;
424            
425             // add the dep
426             deps ~= canonicalSource(useinfo[1], nconf);
427         }
428        
429         // delete the uses file
430         std.file.remove("temp.uses");
431     }
432    
433     return deps;
434 }
435
436 /** Canonicalize a dependency (.d -> source) */
437 char[] canonicalSource(char[] origsrc, NetConfig nconf)
438 {
439     char[] src = origsrc.dup;
440    
441     if ((src.length > 2 &&
442          std.string.tolower(src[$-2 .. $]) == ".d") ||
443         (src.length > 3 &&
444          std.string.tolower(src[$-3 .. $]) == ".di")) {
445         // convert to a proper source
446         if (src in nconf.deps &&
447             nconf.deps[src].length == 1) {
448             src = nconf.deps[src][0].dup;
449         } else {
450             src = "";
451         }
452     }
453    
454     return src;
455 }
456
457 /** Get the source for a given package
458  * Returns true on success, false on failure
459  * NOTE: Your chdir can change! */
460 bool getSources(char[] pkg, NetConfig conf)
461 {
462     /// get sources from upstream, return false on failure
463     bool getUpstream() {
464         // 1) get source
465         char[] srcFormat = conf.srcFormat[pkg];
466         int res;
467         switch (srcFormat) {
468             case "svn":
469                 // Subversion, check it out
470                 res = sayAndSystem("svn co " ~ conf.srcURL[pkg]);
471                 break;
472                
473             default:
474             {
475                 /* download ...
476                 HttpGet dlhttp = new HttpGet(conf.srcURL[pkg]);
477                 
478                 // save it to a source file
479                 write("src." ~ srcFormat, dlhttp.read());*/
480                
481                 // mango doesn't work properly for me :(
482                 res = sayAndSystem("curl -k " ~ conf.srcURL[pkg] ~ " -o src." ~ srcFormat);
483                 if (res != 0) return false;
484                
485                 // extract it
486                 switch (srcFormat) {
487                     case "tar.gz":
488                     case "tgz":
489                         version (Windows) {
490                             // assume BsdTar
491                             sayAndSystem("bsdtar -xf src." ~ srcFormat);
492                             res = 0;
493                         } else {
494                             res = sayAndSystem("gunzip -c src." ~ srcFormat ~ " | tar -xf -");
495                         }
496                         break;
497                        
498                     case "tar.bz2":
499                         version (Windows) {
500                             // assume BsdTar
501                             sayAndSystem("bsdtar -xf src.tar.bz2");
502                             res = 0;
503                         } else {
504                             res = sayAndSystem("bunzip2 -c src.tar.bz2 | tar -xf -");
505                         }
506                         break;
507                        
508                     case "zip":
509                         version (Windows) {
510                             // assume BsdTar
511                             sayAndSystem("bsdtar -xf src.zip");
512                             res = 0;
513                         } else {
514                             // assume InfoZip
515                             res = sayAndSystem("unzip src.zip");
516                         }
517                         break;
518                        
519                     default:
520                         writefln("Unrecognized source format: %s", srcFormat);
521                         return false;
522                 }
523             }
524         }
525        
526         if (res != 0) return false;
527        
528         // 2) apply patches
529         char[] srcDir = getcwd();
530         foreach (patch; conf.srcPatches[pkg]) {
531             char[][] pinfo = split(patch, ":");
532             char[] dir;
533             char[] pfile;
534            
535             // split into dir:file or just file
536             if (pinfo.length < 2) {
537                 dir = srcDir;
538                 pfile = pinfo[0];
539             } else {
540                 dir = pinfo[0];
541                 pfile = pinfo[1];
542             }
543            
544             chdir(dir);
545            
546             // download the patch file
547             saySystemDie("curl -k " ~ conf.mirror ~ "/" ~ pfile ~
548                          " -o " ~ pfile);
549            
550             // convert it to DOS line endings if necessary
551             version (Windows) {
552                 saySystemDie("unix2dos " ~ pfile);
553             }
554            
555             // install the patch
556             system("patch -p0 -i " ~ pfile);
557            
558             chdir(srcDir);
559         }
560        
561         return true;
562     }
563    
564     if (!getUpstream()) {
565         // failed to get from upstream, try a mirror
566         char[][] mirrorsList = std.string.split(
567             cast(char[]) std.file.read(
568                 srcListPrefix ~ std.path.sep ~ "mirrors.list"
569                 ),
570             "\n");
571<