1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2009 Tango. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Oct 2009: Initial release
8 
9         author:         larsivi, sleets, kris
10 
11 *******************************************************************************/
12 module bob;
13 
14 private import tango.text.Util;
15 private import tango.io.Stdout;
16 private import tango.sys.Process;
17 private import tango.io.FilePath;
18 private import Path = tango.io.Path;
19 private import tango.io.device.Array;
20 private import tango.io.device.File;
21 private import tango.text.Arguments;
22 private import tango.sys.Environment;
23 
24 /*******************************************************************************
25       
26 *******************************************************************************/
27 
28 void main (immutable(char)[][] arg)
29 {
30         Args args;
31 
32         if (args.populate (arg[1..$]))
33            {
34            try {
35                Path.remove (args.lib);
36                }catch (Throwable o){}
37            new Linux (args);
38            new MacOSX (args);
39            new FreeBSD (args);
40            new Solaris (args);
41            new Windows (args);
42            Stdout.formatln ("{} files", FileFilter.builder(args.os, args.compiler)());
43            }
44 }
45 
46 /*******************************************************************************
47       
48 *******************************************************************************/
49 
50 class Windows : FileFilter
51 {
52         this (ref Args args)
53         {
54                 super (args);
55                 exclude ("tango/stdc/posix");
56                 include ("tango/sys/win32");
57                 register ("windows", "dmd", &dmd);
58                 register ("windows", "ldc2", &ldc2);
59         }
60 
61         int dmd ()
62         {
63                 void compile (const(char)[] cmd, FilePath file)
64                 {
65                         auto temp = objname (file);
66                         if (args.quick is false || isOverdue (file, temp))
67                             exec (cmd~temp~" "~file.toString());
68                         addToLib (temp);
69                 }
70 
71                 auto dmd = "dmd -c -I"~args.root~" "~args.flags~" -of";
72                 libs ("-c -n -p256\n"~args.lib~"\n");
73 
74                 foreach (file; scan(".d"))
75                          compile (dmd, file);
76 
77                 foreach (file; scan(".c"))
78                          compile ("dmc -c -mn -6 -r -o", file);
79 
80                 File.set("tango.lsp", libs.slice());
81                 exec ("lib @tango.lsp");
82 
83                 // retain obj files only when -q is specified
84                 if (args.quick)
85                     exec ("cmd /q /c del tango.lsp");
86                 else
87                     exec ("cmd /q /c del tango.lsp *.obj");
88                 return count;
89         }
90 
91         int ldc2 ()
92         {
93                 char[] compile (FilePath file, const(char)[] cmd)
94                 {
95                         auto temp = objname (file, ".o");
96                         if (args.quick is false || isOverdue (file, temp))
97                             exec (cmd~temp~" "~file.toString());
98                         return temp;
99                 }
100 
101                 auto gcc = "gcc -c -o";
102                 auto ldc2 = "ldc2 -c -I"~args.root~" "~args.flags~" -of";
103                 foreach (file; scan(".d")) {
104                          auto obj = compile (file, ldc2);
105                          addToLib(obj);
106                 }
107 
108                 File.set("tango.lsp", libs.slice());
109                 exec ("ar -r "~args.lib~" @tango.lsp");
110                 exec ("cmd /q /c del tango.lsp");
111 
112                 // retain object files only when -q is specified
113                 if (!args.quick)
114                     exec ("cmd /q /c del *.o");
115 
116                 return count;
117         }
118 }
119 
120 /*******************************************************************************
121       
122 *******************************************************************************/
123 
124 class Linux : FileFilter
125 {
126         this (ref Args args)
127         {
128                 super (args);
129                 include ("tango/sys/linux");
130                 register ("linux", "dmd", &dmd);
131                 register ("linux", "ldc2", &ldc2);
132                 register ("linux", "gdc", &gdc);
133         }
134 
135         private char[] compile (FilePath file, const(char)[] cmd)
136         {
137                 auto temp = objname (file, ".o");
138                 if (args.quick is false || isOverdue(file, temp))
139                     exec (split(cmd~temp~" "~file.toString(), " "), Environment.get(), null);
140                 return temp;
141         }
142 
143         private auto gcc00 = "gcc -c -o";
144         private auto gcc32 = "gcc -c -m32 -o";
145         private auto gcc64 = "gcc -c -m64 -o";
146 
147         int dmd ()
148         {
149                 const(char)[] march;
150 
151                 if (args.march.length)
152                 {
153                     march = (args.march == "64") ? " -m64" : " -m32";
154                 }
155 
156                 auto dmd = "dmd -c -I"~args.root~march~" "~args.flags~" -of";
157                 foreach (file; scan(".d")) {
158                          auto obj = compile (file, dmd);
159                          addToLib(obj);
160                 }
161 
162                 makeLib(args.march == "32");
163                 return count;
164         }
165 
166         int ldc2 ()
167         {
168                 const(char)[] march;
169 
170                 if (args.march.length)
171                 {
172                     march = (args.march == "64") ? " -m64" : " -m32";
173                 }
174 
175                 auto ldc2 = "ldc2 -c " ~ march ~ " -I"~args.root~" "~args.flags~" -of";
176                 foreach (file; scan(".d")) {
177                          auto obj = compile (file, ldc2);
178                          addToLib(obj);
179                 }
180 
181                 makeLib(args.march == "32");
182                 return count;
183         }
184 
185         int gdc ()
186         {
187                 const(char)[] march;
188 
189                 if (args.march.length)
190                 {
191                     march = (args.march == "64") ? " -m64" : " -m32";
192                 }
193 
194                 auto gdc = "gdc -c -I"~args.root ~ march ~ " "~args.flags~" -o";
195                 foreach (file; scan(".d")) {
196                          auto obj = compile (file, gdc);
197                          addToLib(obj);
198                 }
199 
200                 makeLib(args.march == "32");
201                 return count;
202         }
203 
204 
205 }
206 
207 /*******************************************************************************
208       
209 *******************************************************************************/
210 
211 class MacOSX : FileFilter
212 {
213         this (ref Args args)
214         {
215                 super (args);
216                 include ("tango/sys/darwin");
217                 register ("osx", "dmd", &dmd);
218                 register ("osx", "ldc2", &ldc2);
219                 register ("osx", "gdc", &gdc);
220         }
221 
222         private char[] compile (FilePath file, const(char)[] cmd)
223         {
224                 auto temp = objname (file, ".o");
225                 if (args.quick is false || isOverdue(file, temp))
226                     exec (split(cmd~temp~" "~file.toString(), " "), Environment.get(), null);
227                 return temp;
228         }
229 
230         int dmd ()
231         {
232                 auto dmd = "dmd -c -I"~args.root~" "~args.flags~" -of";
233                 foreach (file; scan(".d")) {
234                          auto obj = compile (file, dmd);
235                          addToLib(obj);
236                 }
237 
238                 makeLib(true);
239                 return count;
240         }
241 
242         int ldc2 ()
243         {
244                 auto ldc2 = "ldc2 -c -I"~args.root~" "~args.flags~" -of";
245                 foreach (file; scan(".d")) {
246                          auto obj = compile (file, ldc2);
247                          addToLib(obj);
248                 }
249 
250                 makeLib;
251                 return count;
252         }
253 
254         int gdc ()
255         {
256                 auto gdc = "gdc -c -I"~args.root~" "~args.flags~" -o";
257                 foreach (file; scan(".d")) {
258                          auto obj = compile (file, gdc);
259                          addToLib(obj);
260                 }
261 
262                 makeLib;
263                 return count;
264         }
265 }
266 
267 /*******************************************************************************
268       
269 *******************************************************************************/
270 
271 class FreeBSD : FileFilter
272 {
273         this (ref Args args)
274         {
275                 super (args);
276                 include ("tango/sys/freebsd");
277                 register ("freebsd", "dmd", &dmd);
278                 register ("freebsd", "ldc2", &ldc2);
279                 register ("freebsd", "gdc", &gdc);
280         }
281 
282         private char[] compile (FilePath file, const(char)[] cmd)
283         {
284                 auto temp = objname (file, ".o");
285                 if (args.quick is false || isOverdue(file, temp))
286                     exec (split(cmd~temp~" "~file.toString(), " "), Environment.get(), null);
287                 return temp;
288         }
289 
290         private auto gcc = "gcc -c -o";
291         private auto gcc32 = "gcc -c -m32 -o";
292 
293         int dmd ()
294         {
295                 auto dmd = "dmd -version=freebsd -c -I"~args.root~" "~args.flags~" -of";
296                 foreach (file; scan(".d")) {
297                          auto obj = compile (file, dmd);
298                          addToLib(obj);
299                 }
300 
301                 makeLib(true);
302                 return count;
303         }
304 
305         int ldc2 ()
306         {
307                 auto ldc2 = "ldc2 -c -I"~args.root~" "~args.flags~" -of";
308                 foreach (file; scan(".d")) {
309                          auto obj = compile (file, ldc2);
310                          addToLib(obj);
311                 }
312 
313                 makeLib;
314                 return count;
315         }
316 
317         int gdc ()
318         {
319                 auto gdc = "gdc -fversion=freebsd -c -I"~args.root~"/tango/core -I"~args.root~" "~args.flags~" -o";
320                 foreach (file; scan(".d")) {
321                          auto obj = compile (file, gdc);
322                          addToLib(obj);
323                 }
324 
325                 makeLib;
326                 return count;
327         }
328 
329 
330 }
331 
332 class Solaris : FileFilter
333 {
334         this (ref Args args)
335         {
336                 super (args);
337                 include ("tango/sys/solaris");
338                 register ("solaris", "dmd", &dmd);
339                 register ("solaris", "ldc2", &ldc2);
340                 register ("solaris", "gdc", &gdc);
341         }
342 
343         private char[] compile (FilePath file, const(char)[] cmd)
344         {
345                 auto temp = objname (file, ".o");
346                 if (args.quick is false || isOverdue(file, temp))
347                     exec (split(cmd~temp~" "~file.toString(), " "), Environment.get(), null);
348                 return temp;
349         }
350 
351         private auto gcc = "gcc -c -o";
352         private auto gcc32 = "gcc -c -m32 -o";
353 
354         int dmd ()
355         {
356                 auto dmd = "dmd -version=solaris -c -I"~args.root~" "~args.flags~" -of";
357                 foreach (file; scan(".d")) {
358                          auto obj = compile (file, dmd);
359                          addToLib(obj);
360                 }
361 
362                 makeLib(true);
363                 return count;
364         }
365 
366         int ldc2 ()
367         {
368                 auto ldc2 = "ldc2 -c -I"~args.root~" "~args.flags~" -of";
369                 foreach (file; scan(".d")) {
370                          auto obj = compile (file, ldc2);
371                          addToLib(obj);
372                 }
373 
374                 makeLib;
375                 return count;
376         }
377 
378         int gdc ()
379         {
380                 auto gdc = "gdc -fversion=solaris -c -I"~args.root~" "~args.flags~" -o";
381                 foreach (file; scan(".d")) {
382                          auto obj = compile (file, gdc);
383                          addToLib(obj);
384                 }
385 
386                 makeLib;
387                 return count;
388         }
389 }
390 
391 
392 /*******************************************************************************
393       
394 *******************************************************************************/
395 
396 class FileFilter
397 {
398         alias int delegate()    Builder;
399 
400         Array                   libs;
401         Args                    args;
402         int                     count;
403         const(char)[]                  suffix;
404         bool[char[]]            excluded;         
405         static Builder[char[]]  builders;
406 
407         /***********************************************************************
408 
409         ***********************************************************************/
410 
411         static void register (const(char)[] platform, const(char)[] compiler, Builder builder)
412         {
413                 builders [platform~compiler] = builder;
414         }
415 
416         /***********************************************************************
417 
418         ***********************************************************************/
419 
420         static Builder builder (const(char)[] platform, const(char)[] compiler)
421         {       
422                 auto s = platform~compiler;
423                 auto b = s in builders;
424                 if (b)
425                     return *b;
426                 throw new Exception ("unsupported combination of "~platform.idup~" and "~compiler.idup);
427         }
428 
429         /***********************************************************************
430 
431         ***********************************************************************/
432 
433         this (ref Args args)
434         {
435                 this.args = args;
436 
437                 libs = new Array (0, 1024 * 16);
438 
439                 exclude ("tango/sys/win32");
440                 exclude ("tango/sys/darwin");
441                 exclude ("tango/sys/freebsd");
442                 exclude ("tango/sys/linux");
443                 exclude ("tango/sys/solaris");
444         }
445 
446         /***********************************************************************
447 
448         ***********************************************************************/
449 
450         final FilePath[] scan (const(char)[] suffix)
451         {
452                 this.suffix = suffix;
453                 auto files = sweep (FilePath(args.root~"/tango"));
454                 foreach(file; files)
455                     if(args.user || containsPattern(file.folder, "core"))
456                         this.count++;
457                 return files;
458         }
459         
460         /***********************************************************************
461 
462         ***********************************************************************/
463 
464         final FilePath[] sweep(FilePath root)
465         {
466             FilePath[] files;
467             FilePath[] folders;
468             
469             foreach (path; root.toList(&filter))
470             {
471                 if(path.isFolder)
472                     folders ~= path;
473                 else
474                     files ~= path;
475             }
476             
477             foreach(folder; folders)
478             {
479                 files ~= sweep(folder);
480             }
481             
482             return files;
483         }
484         
485         /***********************************************************************
486 
487         ***********************************************************************/
488 
489         final void exclude (const(char)[] path)
490         {
491                 assert(Path.exists(Path.join(args.root, path)), "FileFilter.exclude: Path does not exist: " ~ path);
492                 assert(path[$-1] != '/', "FileFilter.exclude: Inconsistent path syntax, no trailing '/' allowed: " ~ path);
493                 excluded[path] = true;
494         }
495 
496         /***********************************************************************
497 
498         ***********************************************************************/
499 
500         final void include (const(char)[] path)
501         {
502                 assert(path in excluded, "FileFilter.include: Path need to be excluded first: " ~ path);
503                 excluded.remove (path);
504         }
505 
506         /***********************************************************************
507 
508         ***********************************************************************/
509 
510         private bool filter (FilePath fp, bool isDir)
511         {
512                 if (isDir)
513                    {    
514                    auto tango = locatePatternPrior (fp.path, "tango");
515                    if (tango < fp.path.length)
516                        return ! (fp.toString()[tango..$] in excluded);
517                    return false;
518                    }
519 
520                 return fp.suffix == suffix;
521         }
522 
523         /***********************************************************************
524               
525         ***********************************************************************/
526         
527         private char[] objname (FilePath fp, const(char)[] ext=".obj")
528         {
529                 auto tmp = fp.folder [args.root.length+1 .. $] ~ fp.name ~ args.flags;
530                 foreach (i, ref c; tmp)
531                          if (c is '.' || c is '/' || c is '=' || c is ' ' || c is '"')
532                              c = '-';  
533                 return tmp ~ ext ;
534         }
535 
536         /***********************************************************************
537               
538         ***********************************************************************/
539         
540         private bool isOverdue (FilePath fp, const(char)[] objfile)
541         {
542                 if (! Path.exists (objfile))
543                       return true;
544 
545                 auto src = fp.timeStamps().modified;
546                 auto obj = Path.modified (objfile);
547                 return src >= obj;
548         }
549         
550         /***********************************************************************
551 
552         ***********************************************************************/
553 
554         private void addToLib (const(char)[] obj)
555         {
556                 version (Windows)
557                          const Eol = "\r\n";
558                 else
559                          const Eol = " ";
560                 if (Path.exists (obj))
561                     libs (obj)(Eol);
562         }
563 
564         /***********************************************************************
565 
566         ***********************************************************************/
567 
568         @property private void makeLib (bool use32bit = false)
569         {
570                 if (libs.readable > 2)
571                    {
572                    auto files = cast(char[]) libs.slice() [0..$-1];
573                    
574                    if (args.dynamic)
575                    {
576                        version (osx)
577                        {
578                            auto path = Path.parse(args.lib);
579                            auto name = path.file;
580                            auto options = "-dynamiclib -install_name @rpath/" ~ name ~ " -Xlinker -headerpad_max_install_names";
581                            auto gcc = use32bit ? "gcc -m32 " : "gcc ";
582                            exec (gcc ~ options ~ " -o " ~ args.lib ~ " " ~ files ~ " -lz -lbz2");                        
583                        }
584                        
585                    }
586                    
587                    else
588                        exec ("ar -r "~args.lib~" "~ files);        
589         
590                    if (args.quick is false)
591                        // TODO: remove the list of filenames in 'files' 
592                       {}
593                    }
594         }
595 
596         /***********************************************************************
597               
598         ***********************************************************************/
599         
600         void exec (const(char)[] cmd)
601         {
602                 exec (split(cmd, " "), null, null);
603         }
604         
605         /***********************************************************************
606               
607         ***********************************************************************/
608         
609         void exec (const(char[])[] cmd, const(char[])[char[]] env, const(char)[] workDir)
610         {
611                 if (args.verbose)
612                    {
613                    foreach (str; cmd)
614                             Stdout (str)(' ');
615                    Stdout.newline;
616                    }  
617                          
618                 if (! args.inhibit)
619                    {
620                    scope proc = new Process (cmd, env);
621                    scope (exit) proc.close();
622                    if (workDir) 
623                        proc.workDir = workDir;
624                    if (env is null)
625                        proc.copyEnv (true);
626 
627                    proc.execute();
628                    Stdout.stream.copy (proc.stderr);
629                    Stdout.stream.copy (proc.stdout);
630                    auto result = proc.wait();
631                    if (result.status != 0 || result.reason != Process.Result.Exit)
632                        throw new Exception (result.toString().idup);
633                    }
634         }
635 }
636 
637 
638 /*******************************************************************************
639       
640 *******************************************************************************/
641 
642 struct Args
643 {
644         bool    user,
645                 quick,
646                 inhibit,
647                 verbose,
648                 dynamic;
649 
650         const(char)[]  os,
651                        lib,
652                        root,
653                        flags,
654                        compiler,
655                        march;
656                 
657 
658         const(char)[]  usage = "Bob is a build tool for the sole purpose to compile the Tango library.\n"
659                                "Usage: bob <options> tango-path\n"
660                                "Arguments:\n"
661                                "\t[-v]\t\t\tverbose output\n"
662                                "\t[-q]\t\t\tquick execution\n"
663                                "\t[-i]\t\t\tinhibit execution\n"
664                                "\t[-u]\t\t\tinclude user modules\n"
665                                "\t[-d]\t\t\tbuild Tango as a dynamic/shared library\n"
666                                "\t[-m=64|32]\tCompile for 32/64 bit\n"
667                                "\t[-c=dmd|gdc|ldc2]\tspecify a compiler to use\n"                        
668                                "\t[-g=basic|cdgc|stub]\tspecify the GC implementation to include in the runtime\n"
669                                "\t[-o=\"options\"]\t\tspecify D compiler options\n"
670                                "\t[-l=libname]\t\tspecify lib name (sans .ext)\n"
671                                "\t[-p=sysname]\t\tdetermines package filtering (windows|linux|osx|freebsd|solaris)\n\n"
672                                "Example: .\\build\\bin\\win32\\bob.exe -vu -c=dmd .\n\n";
673 
674         bool populate (const(char[])[] arg)
675         {       
676                 auto args = new Arguments;
677                 auto q = args('q');
678                 auto u = args('u');
679                 auto i = args('i');
680                 auto v = args('v');
681                 auto l = args('l').smush().params(1);
682                 auto p = args('p').smush().params(1);
683                 auto o = args('o').smush().params(1).defaults("-release");
684                 auto c = args('c').smush().params(1).defaults("dmd").restrict("dmd", "gdc", "ldc2");
685                 auto n = args(null).params(1).required.title("tango-path");
686                 auto h = args("help").aliased('h').aliased('?').halt();
687                 auto d = args('d');
688                 auto m = args('m').params(1).restrict("64", "32");
689 
690                 version (Windows)
691                          p.defaults("windows");
692                 else
693                 version (linux)
694                          p.defaults("linux");
695                 else
696                 version (osx)
697                          p.defaults("osx");
698                 else
699                 version (freebsd)
700                          p.defaults("freebsd");
701                 else
702                 version (solaris)
703                          p.defaults("solaris");
704                 else
705                    p.required;
706 
707                 if (args.parse (arg))
708                    {
709                    user = u.set;
710                    quick = q.set;
711                    inhibit = i.set;
712                    verbose = v.set;
713                    dynamic = d.set;
714                    os = p.assigned()[0];
715                    root = n.assigned()[0];
716                    flags = o.assigned()[0];
717                    compiler = c.assigned()[0];
718                    march = m.assigned().length > 0 ? m.assigned()[0] : "";
719                    
720                    if(l.assigned().length == 0)
721                    {
722                         lib = "libtango-";
723                         switch(c.assigned()[0])
724                         {
725                             case "dmd":
726                                 lib ~= "dmd";
727                                 break;
728                             case "gdc":
729                                 lib ~= "gdc";
730                                 break;
731                             case "ldc2":
732                                 lib ~= "ldc";
733                                 break;
734                             default:
735                                 assert(0);
736                         }
737                    }
738                    else
739                    {
740                        lib = l.assigned()[0];
741                    }
742                        
743                     if(compiler == "gdc" && flags == "-release")
744                         flags = "-frelease";
745                    
746                    if (dynamic)
747                    {
748                        version (osx)
749                            lib ~= ".dylib";
750                        else
751                            throw new Exception("Building Tango as a dynamic library is currently only supported on Mac OS X", __FILE__, __LINE__);
752                    }
753                    else
754                    {
755                        version (Windows)
756                            lib ~= ".lib";
757                        else
758                            lib ~= ".a";
759                    }
760                    
761                    return true;
762                    }
763 
764                 stdout (usage);
765                 if (! h.set)
766                       stdout (args.errors (&stdout.layout.sprint));
767                 return false;
768         }
769 }
770