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