comparison build-tool/bob.d @ 134:89e8b0d92f36

Ported to bob2 !!!
author David Bryant <bagnose@gmail.com>
date Thu, 02 Aug 2012 17:20:52 +0930
parents
children
comparison
equal deleted inserted replaced
133:9e1a313d8003 134:89e8b0d92f36
1 // Copyright Graham St Jack
2 module bob;
3
4 import std.stdio;
5 import std.ascii;
6 import std.string;
7 import std.format;
8 import std.process;
9 import std.algorithm;
10 import std.range;
11 import std.file;
12 import std.path;
13 import std.conv;
14 import std.datetime;
15 import std.getopt;
16 import std.concurrency;
17 import std.functional;
18 import std.exception;
19
20 import core.sys.posix.sys.wait;
21 import core.sys.posix.signal;
22
23
24 /*========================================================================
25
26 A build tool suitable for C/C++ and D code, written in D.
27
28 Objectives of this build tool are:
29 * Easy to write and maintain build scripts (Bobfiles):
30 - Simple syntax.
31 - Automatic determination of which in-project libraries to link.
32 * Auto execution and evaluation of unit tests.
33 * Enforcement of dependency rules.
34 * Support for building source code from multiple repositories.
35 * Support for C/C++ and D.
36 * Support for code generation:
37 - A source file isn't scanned for imports/includes until after it is up to date.
38 - Dependencies inferred from these imports are automatically applied.
39
40
41 Directory Structure
42 -------------------
43
44 The source code is arranged into repositories, and within repositories
45 into packages which correspond to the directory structure.
46
47 Packages should contain not only library source code, but also tests, utilities,
48 documentation and data. The intention is to make packages easily reusable
49 by putting all their pieces in one location.
50
51 A typical directory structure is:
52
53 repo repository
54 |
55 +--package-name package's Bobfile and library source
56 |
57 +--doc the package's documentation
58 +--data data files needed by the package
59 +--test source code for automated regression tests
60 +--util source code for non-test executables
61 |
62 +--child-package(s)
63
64
65 Dependency rules
66 ----------------
67
68 Files and their owning packages are arranged in a tree with cross-linking
69 dependencies. Each node in the tree can be public or protected. The root of the
70 tree contains its children publicly.
71
72 The dependency rules are:
73 * A protected node can only be referred to by sibling nodes or nodes contained
74 by those siblings.
75 * A node can only refer to another node if its parent transitively refers to or
76 transitively contains that other node.
77 * Circular dependencies are not allowed.
78
79 An object file can only be used once - either in a library or an executable.
80 Dynamic libraries don't count as a use - they are just a repackaging.
81
82 A dynamic library cannot contain the same static library as another dynamic library.
83
84
85 Configure
86 ---------
87
88 Bundled with bob is a configure procedure that is mainly implemented in
89 configure_functions.d. Each project contains a configure.d program that
90 is invoked via a configure.sh script.
91
92 Configure establishes a build directory complete with scripts necessary
93 to build and run a project.
94
95 Refer to configure_functions.d for details.
96
97
98 Bobfiles
99 --------
100
101 Each package has a Bobfile. On start, bob (the builder) reads the
102 Bobfile specified in the Boboptions file and follows its directives.
103
104 Bobfiles must refer to things in dependency order, so that anything referred
105 to is defined earlier.
106
107
108 General rule format in Bobfiles is:
109
110 # This line is a comment.
111
112 # General form of a rule. Arguments are space-separated tokens. Args are optional.
113 rulename target : sources : arg1 : arg2 : arg3;
114
115 # Resolves to 0 or 1 rule, choosing the first that matches an architecture specified
116 # during configure. [] is a default that matches regardless of architecture.
117 ARCHITECTURE{
118 [arch-1] rulename target : arg1 : arg2 : arg3;
119 [arch-2] rulename target : arg1 : arg2 : arg3;
120 [] rulename target : arg1 : arg2 : arg3;
121 }
122
123
124 Specific rules are:
125
126 # This package contains packages. eg: package math ipc;
127 package names [: protected]; # defaults to public
128
129 # This package refers to others. eg: refer math ipc;
130 # Referenced packages are specified by path relative to root. eg: icp/client
131 refer packages;
132
133 # Library: for D/C/C++, list both header and body files, all of which must be local.
134 # eg: static-lib math : matrix.h : matrix.cc : m;
135 static-lib name : public-sources : protected-sources : required-system-libs;
136
137 # dynamic-libs can only contain static-libs within their package or its
138 # children. The contained static-libs are specified as a relative name trail,
139 # with the last element optionally omitted if it is the same as the package name.
140 # eg: dynamic-lib ipc : common client server;
141 dynamic-lib name : static-libs;
142 dynamic-lib name : static-libs : plugin;
143
144
145 # Executables: makes an exe from a single source file and any libs deduced from
146 # imports and includes. Also links with specified system libs, plus any specified
147 # by deduced libraries.
148 # Tests are auto-executed and build fails on test error.
149 test-util name : source : required-system-libs; # source file in test dir or generated
150 priv-util name : source : required-system-libs; # source file in util dir or generated
151 dist-util name : source : required-system-libs; # source file in util dir or generated
152
153 # Shell
154 priv-shell name; # source file in util dir or generated
155 dist-shell name; # source file in util dir or generated
156
157 # Data - any dirs named specify whole contained tree, not including symlinks
158 test-data name; # source in data dir or generated.
159 dist-data name; # source in data dir or generated.
160
161 # Docco - rst files converted to html
162 doc names; # source in doc dir or generated.
163 doc-data names; # source in doc dir or generated.
164
165
166 Build directory structure
167 -------------------------
168
169 Bob assumes that a build directory has been established
170 by an earlier configure operation. The configure sets up
171 a Boboptions file containing definitions of variables used by rules.
172
173 The format of Boboptions is:
174 key = value;
175
176 Usually bob is invoked from a build script established during configure.
177
178 Under the build directory we have:
179
180 obj
181 static-libs
182 packages(s)
183 obj-files
184 generated-sources
185 packages(s)
186 priv
187 package(s)
188 test-exes
189 util-exes
190 test
191 test-exe-specific-test-dir(s)
192 test-results
193 test-working-files
194 doc
195 docs
196 package(s)
197 dist
198 data All dist-data
199 lib All dynamic-libs except plugins
200 plugin Dynalic-libs designated as plugins
201 bin All dist-util
202
203
204
205 Search paths
206 ------------
207
208 Compilers are told to look in 'src' and 'obj' directories for input files.
209 The src directory contains links to each top-level package in all the
210 repositories that comprise the project.
211
212 Therefore, include directives have to include the path starting from
213 the top-level package names, which must be unique.
214
215 This namespacing avoids problems of duplicate filenames
216 at the cost of the compiler being able to find everything, even files
217 that should not be accessible. Bob therefore enforces all visibility
218 rules before invoking the compiler.
219
220
221 The build process
222 -----------------
223
224 Bob reads the project Bobfile, transiting into
225 other-package Bobfiles as packages are mentioned.
226
227 Bob assumes that new packages, libraries, etc
228 are mentioned in dependency order. That is, when each thing is
229 mentioned, everything it depends on, including dependencies inferred by
230 include/import statements in source code, has already been mentioned.
231
232 The planner scans the Bobfiles, binding files to specific
233 locations in the filesystem as it goes, and builds the dependency graph.
234
235 The file state sequence is:
236 initial
237 dependencies_clean skipped if no dependencies
238 building skipped if no build action
239 up-to-date
240 scanning_for_includes
241 includes_known
242 clean
243
244 As files become buildable, actions are passed to workers.
245
246 Results cause the dependency graph to be updated, allowing more actions to
247 be issued. Specifically, generated source files are scanned for import/include
248 after they are up to date, and the dependency graph and action commands are
249 adjusted accordingly.
250
251 //==========================================================================*/
252
253 /+
254 // signal handler for otherwise-fatal thread-specific signals
255 extern (C) void threadSpecificSignalHandler(int signum) {
256 string name() {
257 switch (signum) {
258 case SIGSEGV: return "SIGSEGV";
259 case SIGFPE: return "SIGFPE";
260 case SIGILL: return "SIGILL";
261 case SIGABRT: return "SIGABRT";
262 default: return "";
263 }
264 }
265
266 writefln("called!!!");
267 stdout.flush;
268 writefln("got signal %s %s", signum, name);
269 stdout.flush;
270 throw new Error(format("Got signal %s %s", signum, name));
271 }
272
273 // install a signal handler
274 sigaction_t setHandler(int signum, sigfn_t handler) {
275 sigset_t empty_mask;
276 sigemptyset(&empty_mask);
277
278 sigaction_t new_action;
279 new_action.sa_handler = handler;
280 new_action.sa_mask = empty_mask;
281 new_action.sa_flags = SA_RESETHAND;
282
283 sigaction_t old_action;
284
285 sigaction(signum, &new_action, &old_action);
286
287 return old_action;
288 }
289
290 shared static this() {
291 writefln("setting up thread-specific signal handlers");
292
293 // set up shared signal handlers for fatal thread-specific signals
294 setHandler(SIGFPE, &threadSpecificSignalHandler);
295 setHandler(SIGILL, &threadSpecificSignalHandler);
296 setHandler(SIGSEGV, &threadSpecificSignalHandler);
297
298 // unblock the signals
299 sigset_t unblock_set, prev_set;
300 sigemptyset(&unblock_set);
301 sigaddset(&unblock_set, SIGILL);
302 sigaddset(&unblock_set, SIGSEGV);
303 sigaddset(&unblock_set, SIGFPE);
304 pthread_sigmask(SIG_UNBLOCK, &unblock_set, &prev_set);
305 }
306 +/
307
308
309 //-----------------------------------------------------------------------------------------
310 // PriorityQueue - insert items in any order, and remove largest-first
311 // (or smallest-first if "a > b" is passed for less).
312 //
313 // A simplified adaptation of std.container.BinaryHeap. The original doesn't
314 // have the behaviour needed here.
315 //
316 // It is a simple input range (empty(), front() and popFront()).
317 //
318 // Notes from Wikipedia article on Binary Heap:
319 // * Tree is concocted using index arithetic on underlying array, as follows:
320 // First layer is 0. Second is 1,2. Third is 3,4,5,6, etc.
321 // Therefore parent of index i is (i-1)/2 and children of index i are 2*i+1 and 2*i+2
322 // * Tree is balanced, with incomplete population to right of bottom layer.
323 // * A parent is !less all its children.
324 // * Insert:
325 // - Append to array.
326 // - Swap new element with parent until parent !less child.
327 // * Remove:
328 // - Replace root with the last element and reduce the length of the array.
329 // - If moved element is less than a child, swap with largest child.
330 //-----------------------------------------------------------------------------------------
331 struct PriorityQueue(T, alias less = "a < b") {
332 private:
333
334 T[] _store; // underlying store, whose length is the queue's capacity
335 size_t _used; // the used length of _store
336
337 alias binaryFun!(less) comp;
338
339 public:
340
341 @property size_t length() const nothrow { return _used; }
342 @property size_t capacity() const nothrow { return _store.length; }
343 @property bool empty() const nothrow { return !length; }
344
345 @property const(T) front() const { enforce(!empty); return _store[0]; }
346
347 // Insert a value into the queue
348 size_t insert(T value)
349 {
350 // put the new element at the back of the store
351 if ( length == capacity) {
352 _store.length = (capacity + 1) * 2;
353 }
354 _store[_used] = value;
355
356 // percolate-up the new element
357 for (size_t n = _used; n; )
358 {
359 auto parent = (n - 1) / 2;
360 if (!comp(_store[parent], _store[n])) break;
361 swap(_store[parent], _store[n]);
362 n = parent;
363 }
364 ++_used;
365 return 1;
366 }
367
368 void popFront()
369 {
370 enforce(!empty);
371
372 // replace the front element with the back one
373 if (_used > 1) {
374 _store[0] = _store[_used-1];
375 }
376 --_used;
377
378 // percolate-down the front element (which used to be at the back)
379 size_t parent = 0;
380 for (;;)
381 {
382 auto left = parent * 2 + 1, right = left + 1;
383 if (right > _used) {
384 // no children - done
385 break;
386 }
387 if (right == _used) {
388 // no right child - possibly swap parent with left, then done
389 if (comp(_store[parent], _store[left])) swap(_store[parent], _store[left]);
390 break;
391 }
392 // both left and right children - swap parent with largest of itself and left or right
393 auto largest = comp(_store[parent], _store[left])
394 ? (comp(_store[left], _store[right]) ? right : left)
395 : (comp(_store[parent], _store[right]) ? right : parent);
396 if (largest == parent) break;
397 swap(_store[parent], _store[largest]);
398 parent = largest;
399 }
400 }
401 }
402
403
404 //------------------------------------------------------------------------------
405 // Synchronized object that launches external processes in the background
406 // and keeps track of their PIDs. A one-shot bail() method kills all those
407 // launched processes and prevents any more from being launched.
408 //
409 // bail() is called by the error() functions, which then throw an exception.
410 //
411 // We also install a signal handler to bail on receipt of various signals.
412 //------------------------------------------------------------------------------
413
414 class BailException : Exception {
415 this() {
416 super("Bail");
417 }
418 }
419
420 synchronized class Launcher {
421 private {
422 bool bailed;
423 bool[int] children;
424 }
425
426 // launch a process if we haven't bailed
427 pid_t launch(string command) {
428 if (bailed) {
429 say("Aborting launch because we have bailed");
430 throw new BailException();
431 }
432 int child = spawnvp(P_NOWAIT, "/bin/bash", ["bash", "-c", command]);
433 //say("spawned child, pid=%s", child);
434 children[child] = true;
435 return child;
436 }
437
438 // a child has been finished with
439 void completed(pid_t child) {
440 children.remove(child);
441 //say("completed child, pid=%s", child);
442 }
443
444 // bail, doing nothing if we had already bailed
445 bool bail() {
446 if (!bailed) {
447 bailed = true;
448 foreach (child; children.byKey) {
449 //say("killing child, pid=%s", child);
450 kill(child, SIGTERM);
451 }
452 return false;
453 }
454 else {
455 return true;
456 }
457 }
458 }
459
460 shared Launcher launcher;
461
462 void doBail() {
463 launcher.bail;
464 }
465 extern (C) void mySignalHandler(int sig) {
466 // launch a thread to initiate a bail
467 say("got signal %s", sig);
468 spawn(&doBail);
469 }
470
471
472 shared static this() {
473 // set up shared Launcher and signal handling
474
475 launcher = new shared(Launcher)();
476
477 /*
478 signal(SIGTERM, &mySignalHandler);
479 signal(SIGINT, &mySignalHandler);
480 signal(SIGHUP, &mySignalHandler);
481 */
482 }
483
484
485
486 //------------------------------------------------------------------------------
487 // printing utility functions
488 //------------------------------------------------------------------------------
489
490 // where something originated from
491 struct Origin {
492 string path;
493 uint line;
494 }
495
496 private void sayNoNewline(A...)(string fmt, A a) {
497 auto w = appender!(char[])();
498 formattedWrite(w, fmt, a);
499 stderr.write(w.data);
500 }
501
502 void say(A...)(string fmt, A a) {
503 auto w = appender!(char[])();
504 formattedWrite(w, fmt, a);
505 stderr.writeln(w.data);
506 stderr.flush;
507 }
508
509 void fatal(A...)(string fmt, A a) {
510 say(fmt, a);
511 launcher.bail;
512 throw new BailException();
513 }
514
515 void error(A...)(ref Origin origin, string fmt, A a) {
516 sayNoNewline("%s|%s| ERROR: ", origin.path, origin.line);
517 fatal(fmt, a);
518 }
519
520 void errorUnless(A ...)(bool condition, Origin origin, lazy string fmt, lazy A a) {
521 if (!condition) {
522 error(origin, fmt, a);
523 }
524 }
525
526
527 //-------------------------------------------------------------------------
528 // path/filesystem utility functions
529 //-------------------------------------------------------------------------
530
531 //
532 // Return the given path with the top level directory removed
533 //
534 string clip(string path) {
535 for (uint i = 0; i < path.length; ++i) {
536 if (isDirSeparator(path[i])) return path[i+1..$];
537 }
538 return "";
539 }
540
541
542 //
543 // Ensure that the parent dir of path exists
544 //
545 void ensureParent(string path) {
546 static bool[string] doesExist;
547
548 string dir = dirName(path);
549 if (dir !in doesExist) {
550 if (!exists(dir)) {
551 ensureParent(dir);
552 say("%-15s %s", "Mkdir", dir);
553 mkdir(dir);
554 }
555 else if (!isDir(dir)) {
556 error(Origin(), "%s is not a directory!", dir);
557 }
558 doesExist[path] = true;
559 }
560 }
561
562
563 //
564 // return the modification time of the file at path
565 // Note: A zero-length target file is treated as if it doesn't exist.
566 //
567 long modifiedTime(string path, bool isTarget) {
568 if (!exists(path) || (isTarget && getSize(path) == 0)) {
569 return 0;
570 }
571 SysTime fileAccessTime, fileModificationTime;
572 getTimes(path, fileAccessTime, fileModificationTime);
573 return fileModificationTime.stdTime;
574 }
575
576
577 // return the privacy implied by args
578 Privacy privacyOf(ref Origin origin, string[] args) {
579 if (!args.length ) return Privacy.PUBLIC;
580 else if (args[0] == "protected") return Privacy.PROTECTED;
581 else if (args[0] == "semi-protected") return Privacy.SEMI_PROTECTED;
582 else if (args[0] == "private") return Privacy.PRIVATE;
583 else if (args[0] == "public") return Privacy.PUBLIC;
584 else error(origin, "privacy must be one of public, semi-protected, protected or private");
585 assert(0);
586 }
587
588
589
590
591 //------------------------------------------------------------------------
592 // File parsing
593 //------------------------------------------------------------------------
594
595 // options read from Boboptions file
596 string[string] options;
597 bool[string] architectures;
598 bool[string] validArchitectures;
599
600 //
601 // Read an options file, populating options
602 // Format is: key=value;\n
603 // value can contain '='
604 //
605 void readOptions() {
606 string path = "Boboptions";
607 Origin origin = Origin(path, 1);
608
609 errorUnless(exists(path) && isFile(path), origin, "can't read Boboptions %s", path);
610
611 string content = readText(path);
612
613 string key;
614 int anchor = 0;
615 foreach (int pos, char ch ; content) {
616 if (ch == '\n') ++origin.line;
617 if (key is null && ch == '=') {
618 key = strip(content[anchor..pos]);
619 anchor = pos + 1;
620 }
621 else if (ch == ';') {
622 if (key is null) error(origin, "option terminated without a key");
623 string str = strip(content[anchor..pos]);
624 if (key == "VALID_ARCHITECTURES") {
625 foreach (arch; split(str)) {
626 validArchitectures[arch] = true;
627 }
628 }
629 else if (key == "ARCHITECTURES") {
630 foreach (arch; split(str)) {
631 architectures[arch] = true;
632 }
633 }
634 options[key] = str;
635 key = null;
636 }
637 else if (ch == '\n') {
638 errorUnless(key is null, origin, "option %s not terminated with ';'", key);
639 anchor = pos + 1;
640 }
641 }
642 errorUnless(key is null, origin, "%s ends in unterminated option", path);
643 }
644
645 string getOption(string key) {
646 auto value = key in options;
647 if (value) {
648 return *value;
649 }
650 else {
651 return "";
652 }
653 }
654
655
656
657 //
658 // Scan file for includes, returning an array of included trails
659 // # include "trail"
660 //
661 // All of the files found should have trails relative to "src" (if source)
662 // or "obj" (if generated). All system includes must use angle-brackets,
663 // and are not returned from a scan.
664 //
665 struct Include {
666 string trail;
667 uint line;
668 }
669
670 Include[] scanForIncludes(string path) {
671 Include[] result;
672 Origin origin = Origin(path, 1);
673
674 enum Phase { START, HASH, WORD, INCLUDE, QUOTE, NEXT }
675
676 if (exists(path) && isFile(path)) {
677 string content = readText(path);
678 int anchor = 0;
679 Phase phase = Phase.START;
680
681 foreach (int i, char ch; content) {
682 if (ch == '\n') {
683 phase = Phase.START;
684 ++origin.line;
685 }
686 else {
687 switch (phase) {
688 case Phase.START:
689 if (ch == '#') {
690 phase = Phase.HASH;
691 }
692 else if (!isWhite(ch)) {
693 phase = Phase.NEXT;
694 }
695 break;
696 case Phase.HASH:
697 if (!isWhite(ch)) {
698 phase = Phase.WORD;
699 anchor = i;
700 }
701 break;
702 case Phase.WORD:
703 if (isWhite(ch)) {
704 if (content[anchor..i] == "include") {
705 phase = Phase.INCLUDE;
706 }
707 else {
708 phase = Phase.NEXT;
709 }
710 }
711 break;
712 case Phase.INCLUDE:
713 if (ch == '"') {
714 phase = Phase.QUOTE;
715 anchor = i+1;
716 }
717 else if (!isWhite(ch)) {
718 phase = Phase.NEXT;
719 }
720 break;
721 case Phase.QUOTE:
722 if (ch == '"') {
723 result ~= Include(content[anchor..i].idup, origin.line);
724 phase = Phase.NEXT;
725 }
726 break;
727 case Phase.NEXT:
728 break;
729 default:
730 error(origin, "invalid phase");
731 }
732 }
733 }
734 }
735 return result;
736 }
737
738
739 //
740 // Scan a D source file for imports.
741 //
742 // The parser is simple and fast, but can't deal with version
743 // statements or mixins. This is ok for now because it only needs
744 // to work for source we have control over.
745 //
746 // The approach is:
747 // * Scan for a line starting with "static", "public", "private" or ""
748 // followed by "import".
749 // * Then look for:
750 // ':' - module is previous word, and then skip to next ';'.
751 // ',' - module is previous word.
752 // ';' - module is previous word.
753 // The import is terminated by a ';'.
754 //
755
756 Include[] scanForImports(string path) {
757 Include[] result;
758 string content = readText(path);
759 string word;
760 int anchor, line=1;
761 bool inWord, inImport, ignoring;
762
763 string[] externals = split(getOption("DEXTERNALS"));
764
765 foreach (int pos, char ch; content) {
766 if (ch == '\n') {
767 line++;
768 }
769 if (ignoring) {
770 if (ch == ';' || ch == '\n') {
771 // resume looking for imports
772 ignoring = false;
773 inWord = false;
774 inImport = false;
775 }
776 else {
777 // ignore
778 }
779 }
780 else {
781 // we are not ignoring
782
783 if (inWord && (isWhite(ch) || ch == ':' || ch == ',' || ch == ';')) {
784 inWord = false;
785 word = content[anchor..pos];
786
787 if (!inImport) {
788 if (isWhite(ch)) {
789 if (word == "import") {
790 inImport = true;
791 }
792 else if (word != "public" && word != "private" && word != "static") {
793 ignoring = true;
794 }
795 }
796 else {
797 ignoring = true;
798 }
799 }
800 }
801
802 if (inImport && word && (ch == ':' || ch == ',' || ch == ';')) {
803 // previous word is a module name
804
805 string trail = std.array.replace(word, ".", dirSeparator) ~ ".d";
806
807 bool ignored = false;
808 foreach (external; externals) {
809 string ignoreStr = external ~ dirSeparator;
810 if (trail.length >= ignoreStr.length && trail[0..ignoreStr.length] == ignoreStr) {
811 ignored = true;
812 break;
813 }
814 }
815
816 if (!ignored) {
817 result ~= Include(trail, line);
818 }
819 word = null;
820
821 if (ch == ':') ignoring = true;
822 else if (ch == ';') inImport = false;
823 }
824
825 if (!inWord && !(isWhite(ch) || ch == ':' || ch == ',' || ch == ';')) {
826 inWord = true;
827 anchor = pos;
828 }
829 }
830 }
831
832 return result;
833 }
834
835
836 //
837 // read a Bobfile, returning all its statements
838 //
839 // // a simple statement
840 // rulename targets... : arg1... : arg2... : arg3...; // can expand Boboptions variable with ${var-name}
841 //
842 // // a conditional statement resolving to 0 or one of its constituents, based on ARCHITECTURE Boboption.
843 // ARCHITECTURE{
844 // [arch-1] rulename targets... : arg1... : arg2... ;
845 // [arch-2] rulename targets... : arg1... : arg2... ;
846 // [] rulename targets... : arg1... : arg2... ; // optional default
847 // }
848 //
849 //
850
851 struct Statement {
852 Origin origin;
853 int phase; // 0==>empty, 1==>rule populated, 2==rule,targets populated, etc
854 string rule;
855 string[] targets;
856 string[] arg1;
857 string[] arg2;
858 string[] arg3;
859
860 string toString() const {
861 string result;
862 if (phase >= 1) result ~= rule;
863 if (phase >= 2) result ~= format(" : %s", targets);
864 if (phase >= 3) result ~= format(" : %s", arg1);
865 if (phase >= 4) result ~= format(" : %s", arg2);
866 if (phase >= 5) result ~= format(" : %s", arg3);
867 return result;
868 }
869 }
870
871 Statement[] readBobfile(string path) {
872 Statement[] statements;
873 Origin origin = Origin(path, 1);
874 errorUnless(exists(path) && isFile(path), origin, "can't read Bobfile %s", path);
875
876 string content = readText(path);
877 Statement statement;
878
879 int anchor = 0;
880 bool inWord = false;
881 bool inComment = false;
882 bool inCondition = false;
883 bool conditionMatch = false;
884 bool conditionSatisfied = false;
885
886 foreach (int pos, char ch ; content) {
887 if (ch == '\n') {
888 ++origin.line;
889 }
890 if (ch == '#') {
891 inComment = true;
892 inWord = false;
893 }
894 if (inComment) {
895 if (ch == '\n') {
896 inComment = false;
897 anchor = pos;
898 }
899 }
900 else if ((isWhite(ch) || ch == ':' || ch == ';')) {
901 if (inWord) {
902 inWord = false;
903 string word = content[anchor..pos];
904
905 if (word == "ARCHITECTURE{") {
906 // start an architecture condition
907 errorUnless(!inCondition, origin,
908 "nested ARCHITECTURE condition in %s", path);
909 errorUnless(statement.phase == 0, origin,
910 "ARCHITECTURE condition inside a statement in %s", path);
911 inCondition = true;
912 conditionMatch = false;
913 conditionSatisfied = false;
914 }
915 else if (word.length >= 2 && word[0] == '[' && word[$-1] == ']') {
916 // architecture specifier preceeds statement
917 string architecture = word[1..$-1];
918 errorUnless(inCondition, origin,
919 "architecture '%s' specifier when not in condition in %s",
920 architecture, path);
921 errorUnless(statement.phase == 0, origin,
922 "nested statements in %s near %s", path, word);
923 errorUnless(architecture == "" || architecture in validArchitectures, origin,
924 "invalid architecture '%s' in %s", architecture, path);
925 if (!conditionSatisfied && (architecture == "" || architecture in architectures)) {
926 conditionMatch = true;
927 }
928 }
929 else if (word == "}") {
930 inCondition = false;
931 conditionMatch = false;
932 conditionSatisfied = false;
933 }
934 else {
935 // should be a word in a statement
936
937 string[] words = [word];
938
939 if (word.length > 3 && word[0..2] == "${" && word[$-1] == '}') {
940 // macro substitution
941 words = split(getOption(word[2..$-1]));
942 }
943
944 if (word.length > 0) {
945 if (statement.phase == 0) {
946 statement.origin = origin;
947 statement.rule = words[0];
948 ++statement.phase;
949 }
950 else if (statement.phase == 1) {
951 statement.targets ~= words;
952 }
953 else if (statement.phase == 2) {
954 statement.arg1 ~= words;
955 }
956 else if (statement.phase == 3) {
957 statement.arg2 ~= words;
958 }
959 else if (statement.phase == 4) {
960 statement.arg3 ~= words;
961 }
962 else {
963 error(origin, "Too many arguments in %s", path);
964 }
965 }
966 }
967 }
968
969 if (ch == ':' || ch == ';') {
970 ++statement.phase;
971 if (ch == ';') {
972 if (statement.phase > 1 && (!inCondition || conditionMatch)) {
973 statements ~= statement;
974 if (inCondition) {
975 conditionSatisfied = true;
976 conditionMatch = false;
977 //say("conditional resolves to statement '%s'", statement.toString);
978 }
979 }
980 statement = statement.init;
981 }
982 }
983 }
984 else if (!inWord) {
985 inWord = true;
986 anchor = pos;
987 }
988 }
989 errorUnless(statement.phase == 0, origin, "%s ends in unterminated rule", path);
990 return statements;
991 }
992
993
994 //-------------------------------------------------------------------------
995 // Planner
996 //
997 // Planner reads Bobfiles, understands what they mean, builds
998 // a tree of packages, etc, understands what it all means, enforces rules,
999 // binds everything to filenames, discovers modification times, scans for
1000 // includes, and schedules actions for processing by the worker.
1001 //
1002 // Also receives results of successful actions from the Worker,
1003 // does additional scanning for includes, updates modification
1004 // times and schedules more work.
1005 //
1006 // A critical feature is that scans for includes are deferred until
1007 // a file is up-to-date.
1008 //-------------------------------------------------------------------------
1009
1010
1011 // some thread-local "globals" to make things easier
1012 bool g_print_rules;
1013 bool g_print_deps;
1014 bool g_print_details;
1015
1016
1017 //
1018 // Action - specifies how to build some files, and what they depend on
1019 //
1020 final class Action {
1021 static Action[string] byName;
1022 static int nextNumber;
1023 static PriorityQueue!Action queue;
1024
1025 string name; // the name of the action
1026 string command; // the action command-string
1027 int number; // influences build order
1028 File[] builds; // files that this action builds
1029 File[] depends; // files that the action's targets depend on
1030 bool issued; // true if the action has been issued to a worker
1031
1032 this(ref Origin origin, string name_, string command_, File[] builds_, File[] depends_) {
1033 name = name_;
1034 command = command_;
1035 number = nextNumber++;
1036 builds = builds_;
1037 depends = depends_;
1038 errorUnless(!(name in byName), origin, "Duplicate command name=%s", name);
1039 byName[name] = this;
1040
1041 // add the bobfile responsible for all the files built by this action
1042 File bobfile;
1043 foreach (build; builds) {
1044 File b = build.bobfile;
1045 assert(b !is null);
1046 if (bobfile is null) bobfile = b;
1047 assert(b is bobfile);
1048 }
1049 assert(bobfile !is null);
1050 depends ~= bobfile;
1051
1052 // set up reverse dependencies between builds and depends
1053 foreach (depend; depends) {
1054 foreach (built; builds) {
1055 depend.dependedBy[built] = true;
1056 if (g_print_deps) say("%s depends on %s", built.path, depend.path);
1057 }
1058 }
1059 }
1060
1061 // add an extra depend to this action
1062 void addDependency(File depend) {
1063 if (issued) fatal("Cannot add a dependancy to issued action %s", this);
1064 if (builds.length != 1) {
1065 fatal("cannot add a dependency to an action that builds more than one file: %s", name);
1066 }
1067 depends ~= depend;
1068
1069 // set up references and reverse dependencies between builds and depend
1070 foreach (built; builds) {
1071 depend.dependedBy[built] = true;
1072 if (g_print_deps) say("%s depends on %s", built.path, depend.path);
1073 }
1074 }
1075
1076 // augment this action's command string with some text
1077 void augment(string augmentation) {
1078 assert(!issued);
1079 command ~= augmentation;
1080 }
1081
1082
1083 // issue this action
1084 void issue() {
1085 assert(!issued);
1086 issued = true;
1087 queue.insert(this);
1088 }
1089
1090 override string toString() {
1091 return name;
1092 }
1093 override int opCmp(Object o) const {
1094 // reverse order
1095 if (this is o) return 0;
1096 Action a = cast(Action)o;
1097 if (a is null) return -1;
1098 return a.number - number;
1099 }
1100 }
1101
1102
1103 //
1104 // SysLib - represents a library outside the project.
1105 //
1106 final class SysLib {
1107 static SysLib[string] byName;
1108
1109 string name;
1110
1111 // assorted Object overrides for printing and use as an associative-array key
1112 override string toString() const {
1113 return name;
1114 }
1115
1116 this(string name_) {
1117 name = name_;
1118 byName[name] = this;
1119 }
1120 }
1121
1122
1123 //
1124 // Node - abstract base class for things in an ownership tree
1125 // with cross-linked dependencies. Used to manage allowed references.
1126 //
1127
1128 // additional constraint on allowed references
1129 enum Privacy { PUBLIC, // no additional constraint
1130 SEMI_PROTECTED, // only accessable to descendents of grandparent
1131 PROTECTED, // only accessible to children of parent
1132 PRIVATE } // not accessible
1133
1134 class Node {
1135 static Node[string] byTrail;
1136
1137 string name; // simple name this node adds to parent
1138 string trail; // slash-separated name components from root to this
1139 Node parent;
1140 Privacy privacy;
1141 Node[] children;
1142 Node[] refers;
1143
1144 // assorted Object overrides for printing and use as an associative-array key
1145 override string toString() const {
1146 return trail;
1147 }
1148
1149 // create the root of the tree
1150 this() {
1151 trail = "root";
1152 assert(trail !in byTrail, "already have root node");
1153 byTrail[trail] = this;
1154 }
1155
1156 // create a node and place it into the tree
1157 this(ref Origin origin, Node parent_, string name_, Privacy privacy_) {
1158 assert(parent_);
1159 errorUnless(dirName(name_) == ".", origin, "Cannot define node with multi-part name '%s'", name_);
1160 parent = parent_;
1161 name = name_;
1162 privacy = privacy_;
1163 if (parent.parent) {
1164 // child of non-root
1165 trail = buildPath(parent.trail, name);
1166 }
1167 else {
1168 // child of the root
1169 trail = name;
1170 }
1171 parent.children ~= this;
1172 errorUnless(trail !in byTrail, origin, "%s already known", trail);
1173 byTrail[trail] = this;
1174 }
1175
1176 // return true if this is a descendant of other
1177 private bool isDescendantOf(Node other) {
1178 for (auto node = this; node !is null; node = node.parent) {
1179 if (node is other) return true;
1180 }
1181 return false;
1182 }
1183
1184 // return true if this is a visible descendant of other
1185 private bool isVisibleDescendantOf(Node other, Privacy allowed) {
1186 for (auto node = this; node !is null; node = node.parent) {
1187 if (node is other) return true;
1188 if (node.privacy > allowed) break;
1189 if (allowed > Privacy.PUBLIC) allowed--;
1190 }
1191 return false;
1192 }
1193
1194 // return true if other is a visible-child or reference of this,
1195 // or is a visible-descendant of them
1196 private bool allowsRefTo(ref Origin origin,
1197 Node other,
1198 size_t depth = 0,
1199 Privacy allowPrivacy = Privacy.PROTECTED,
1200 bool[Node] checked = null) {
1201 errorUnless(depth < 100, origin, "circular reference involving %s referring to %s", this, other);
1202 //say("for %s: checking if %s allowsReferenceTo %s", origin.path, this, other);
1203 if (other is this || other.isVisibleDescendantOf(this, allowPrivacy)) {
1204 if (g_print_details) say("%s allows reference to %s via containment", this, other);
1205 return true;
1206 }
1207 foreach (node; refers) {
1208 // referred-to nodes grant access to their public children, and referred-to
1209 // suiblings grant access to their semi-protected children
1210 if (node !in checked) {
1211 checked[node] = true;
1212 if (node.allowsRefTo(origin,
1213 other,
1214 depth+1,
1215 node.parent is this.parent ? Privacy.SEMI_PROTECTED : Privacy.PUBLIC,
1216 checked)) {
1217 if (g_print_details) say("%s allows reference to %s via explicit reference", this, other);
1218 return true;
1219 }
1220 }
1221 }
1222 return false;
1223 }
1224
1225 // Add a reference to another node. Cannot refer to:
1226 // * Nodes that aren't defined yet.
1227 // * Self.
1228 // * Ancestors.
1229 // * Nodes whose selves or ancestors have not been referred to by our parent.
1230 // Also can't explicitly refer to children - you get that implicitly.
1231 final void addReference(ref Origin origin, Node other, string cause = null) {
1232 errorUnless(other !is null, origin, "%s cannot refer to NULL node", this);
1233 errorUnless(other != this, origin, "%s cannot refer to self", this);
1234 errorUnless(!this.isDescendantOf(other), origin, "%s cannot refer to ancestor %s", this, other);
1235 errorUnless(!other.isDescendantOf(this), origin, "%s cannnot explicitly refer to descendant %s", this, other);
1236 errorUnless(this.parent.allowsRefTo(origin, other), origin, "Parent %s does not allow %s to refer to %s", parent, this, other);
1237 errorUnless(!other.allowsRefTo(origin, this), origin, "%s cannot refer to %s because of a circularity", this, other);
1238 if (g_print_deps) say("%s refers to %s%s", this, other, cause);
1239 refers ~= other;
1240 }
1241 }
1242
1243
1244 //
1245 // Pkg - a package. Has a Bobfile, assorted source and built files, and sub-packages
1246 // Used to group files together for dependency control, and to house a Bobfile.
1247 //
1248 final class Pkg : Node {
1249
1250 File bobfile;
1251
1252 this(ref Origin origin, Node parent_, string name_, Privacy privacy_) {
1253 super(origin, parent_, name_, privacy_);
1254 bobfile = File.addSource(origin, this, "Bobfile", Privacy.PRIVATE, false);
1255 }
1256 }
1257
1258
1259
1260 //
1261 // A file
1262 //
1263 class File : Node {
1264 static File[string] byPath; // Files by their path
1265 static bool[File] allActivated; // all activated files
1266 static bool[File] outstanding; // outstanding buildable files
1267 static int nextNumber;
1268
1269 // Statistics
1270 static uint numBuilt; // number of files targeted
1271 static uint numUpdated; // number of files successfully updated by actions
1272
1273 string path; // the file's path
1274 int number; // order of file creation
1275 bool scannable; // true if the file and its includes should be scanned for includes
1276 bool built; // true if this file will be built by an action
1277 Action action; // the action used to build this file (null if non-built)
1278
1279 long modTime; // the modification time of the file
1280 bool[File] dependedBy; // Files that depend on this
1281 bool used; // true if this file has been used already
1282
1283 // state-machine stuff
1284 bool activated; // considered by touch() for files with an action
1285 bool scanned; // true if this has already been scanned for includes
1286 File[] includes; // the Files this includes
1287 bool[File] includedBy; // Files that include this
1288 bool clean; // true if usable by higher-level files
1289 long includeModTime; // transitive max of mod_time and includes include_mod_time
1290
1291 // analysis stuff
1292 File youngestDepend;
1293 File youngestInclude;
1294
1295
1296 // return a prospective path to a potential file.
1297 static string prospectivePath(string start, Node parent, string extra) {
1298 Node node = parent;
1299 while (node !is null) {
1300 Pkg pkg = cast(Pkg) node;
1301 if (pkg) {
1302 return buildPath(start, pkg.trail, extra);
1303 }
1304 node = node.parent;
1305 }
1306 fatal("prospective file %s's parent %s has no package in its ancestry", extra, parent);
1307 assert(0);
1308 }
1309
1310 // return the bobfile that this file is declared in
1311 final File bobfile() {
1312 Node node = parent;
1313 while (node) {
1314 Pkg pkg = cast(Pkg) node;
1315 if (pkg) {
1316 return pkg.bobfile;
1317 }
1318 node = node.parent;
1319 }
1320 fatal("file %s has no package in its ancestry", this);
1321 assert(0);
1322 }
1323
1324 this(ref Origin origin, Node parent_, string name_, Privacy privacy_, string path_, bool scannable_, bool built_) {
1325 super(origin, parent_, name_, privacy_);
1326
1327 path = path_;
1328 scannable = scannable_;
1329 built = built_;
1330
1331 number = nextNumber++;
1332
1333 modTime = modifiedTime(path, built);
1334
1335 errorUnless(path !in byPath, origin, "%s already defined", path);
1336 byPath[path] = this;
1337
1338 if (built) {
1339 //say("built file %s", path);
1340 ++numBuilt;
1341 }
1342
1343 }
1344
1345 // Add a source file specifying its trail within its package
1346 static File addSource(ref Origin origin, Node parent, string extra, Privacy privacy, bool scannable) {
1347
1348 // three possible paths to the file
1349 string path1 = prospectivePath("obj", parent, extra); // a built file in obj directory tree
1350 string path2 = prospectivePath("src", parent, extra); // a source file in src directory tree
1351 string path3 = baseName(extra); // a configure-generated source file in build directory
1352
1353 string name = baseName(extra);
1354
1355 File * file = path1 in byPath;
1356 if (file) {
1357 // this is a built source file we already know about
1358 errorUnless(!file.used, origin, "%s has already been used", path1);
1359 return *file;
1360 }
1361 else if (exists(path2)) {
1362 // a source file under src
1363 return new File(origin, parent, name, privacy, path2, scannable, false);
1364 }
1365 else if (exists(path3)) {
1366 // a source file in build dir
1367 return new File(origin, parent, name, privacy, path3, scannable, false);
1368 }
1369 else {
1370 error(origin, "Could not find source file %s in %s, %s or %s", name, path1, path2, path3);
1371 assert(0);
1372 }
1373 }
1374
1375 // This file has been updated
1376 final void updated() {
1377 ++numUpdated;
1378 modTime = modifiedTime(path, true);
1379 if (g_print_details) say("Updated %s, mod_time %s", this, modTime);
1380 if (action !is null) {
1381 action = null;
1382 outstanding.remove(this);
1383 }
1384 touch;
1385 }
1386
1387 // Scan this file for includes, returning them after making sure those files
1388 // themselves exist and have already been scanned for includes
1389 private void scan() {
1390 errorUnless(!scanned, Origin(path, 1), "%s has been scanned for includes twice!", this);
1391 scanned = true;
1392 if (scannable) {
1393
1394 // scan for includes that are part of the project, and thus must already be known
1395 Include[] entries;
1396 string ext = extension(path);
1397 if (ext == ".c" || ext == ".cc" || ext == ".h") {
1398 entries = scanForIncludes(path);
1399 }
1400 else if (ext == ".d") {
1401 entries = scanForImports(path);
1402 }
1403 else {
1404 fatal("Don't know how to scan %s for includes/imports", path);
1405 }
1406
1407 foreach (entry; entries) {
1408 // verify that we know the included file
1409
1410 File * file;
1411 // under src using full trail?
1412 File * include = cast(File *) (buildPath("src", entry.trail) in byPath);
1413 if (include is null && baseName(entry.trail) == entry.trail) {
1414 // in src dir of the including file's package?
1415 include = cast(File *) (prospectivePath("src", parent, entry.trail) in byPath);
1416 }
1417 if (include is null) {
1418 // under obj?
1419 include = cast(File *) (buildPath("obj", entry.trail) in byPath);
1420 }
1421 if (include is null) {
1422 // build dir?
1423 include = entry.trail in byPath;
1424 }
1425 Origin origin = Origin(this.path, entry.line);
1426 errorUnless(include !is null, origin, "included/imported unknown file %s", entry.trail);
1427
1428 // add the included file to this file's includes
1429 includes ~= *include;
1430 include.includedBy[this] = true;
1431
1432 // tell all files that depend on this one that the include has been added
1433 includeAdded(origin, this, *include);
1434
1435 // now (after includeAdded) we can add a reference between this file and the included one
1436 //say("adding include-reference from %s to %s", this, *include);
1437 addReference(origin, *include);
1438 }
1439 if (g_print_deps && includes) say("%s includes=%s", this, includes);
1440
1441 // totally important to touch includes AFTER we know what all of them are
1442 foreach (include; includes) {
1443 include.touch;
1444 }
1445 }
1446 }
1447
1448
1449 // An include has been added from includer (which we depend on) to included.
1450 // Specialisations of File override this to infer additional depends.
1451 void includeAdded(ref Origin origin, File includer, File included) {
1452 //say("File.includeAdded called on %s with %s including %s", this, includer, included);
1453 foreach (depend; dependedBy.keys()) {
1454 //say(" passing on to %s", depend);
1455 depend.includeAdded(origin, includer, included);
1456 }
1457 }
1458
1459
1460 // This file's action is about to be issued, and this is the last chance to
1461 // augment its action. Specialisation should override this method if augmentation
1462 // is required. Return true if dependencies were added.
1463 bool augmentAction() {
1464 return false;
1465 }
1466
1467
1468 // This file has been touched - work out if its action should be issued
1469 // or if it is now clean, transiting to affected items if this becomes clean.
1470 // NOTE - nothing can become clean until AFTER all activation has been done by the planner.
1471 final void touch() {
1472 if (clean) return;
1473 if (g_print_details) say("touching %s", path);
1474 long newest;
1475
1476 if (activated && action && !action.issued) {
1477 // this items action may need to be issued
1478 //say("activated file %s touched", this);
1479
1480 foreach (depend; action.depends) {
1481 if (!depend.clean) {
1482 if (g_print_details) say("%s waiting for %s to become clean", path, depend.path);
1483 return;
1484 }
1485 if (newest < depend.includeModTime) {
1486 newest = depend.includeModTime;
1487 youngestDepend = depend;
1488 }
1489 }
1490 // all files this one depends on are clean
1491
1492 // give this file a chance to augment its action
1493 if (augmentAction()) {
1494 // dependency added - touch this file again to re-check dependencies
1495 touch;
1496 return;
1497 }
1498 else {
1499 // no dependencies were added, so we can issue the action now
1500
1501 if (modTime < newest) {
1502 // buildable and out of date - issue action to worker
1503 if (g_print_details) {
1504 say("%s is out of date with mod_time ", this, modTime);
1505 File other = youngestDepend;
1506 File prevOther = this;
1507 while (other && other.includeModTime > prevOther.modTime) {
1508 say(" %s mod_time %s (younger by %s)",
1509 other,
1510 other.includeModTime,
1511 other.includeModTime - modTime);
1512 other = other.youngestDepend;
1513 }
1514 }
1515 action.issue;
1516 return;
1517 }
1518 else {
1519 // already up to date - no need for building
1520 if (g_print_details) say("%s is up to date", path);
1521 action = null;
1522 outstanding.remove(this);
1523 }
1524 }
1525 }
1526
1527 if (action) return;
1528 errorUnless(modTime > 0, Origin(path, 1), "%s (%s) is up to date with zero mod_time!", path, trail);
1529 // This file is up to date
1530
1531 // Scan for includes, possibly becoming clean in the process
1532 if (!scanned) scan;
1533 if (clean) return;
1534
1535 // Find out if includes are clean and what our effective mod_time is
1536 newest = modTime;
1537 foreach (include; includes) {
1538 if (!include.clean) {
1539 return;
1540 }
1541 if (newest < include.includeModTime) {
1542 newest = include.includeModTime;
1543 youngestInclude = include;
1544 }
1545 }
1546 includeModTime = newest;
1547 if (g_print_details) {
1548 say("%s is clean with effective mod_time %s", this, includeModTime);
1549 File other = youngestInclude;
1550 File prevOther = this;
1551 while (other && other.includeModTime > prevOther.modTime) {
1552 say(" %s mod_time %s (younger by %s)",
1553 other,
1554 other.includeModTime,
1555 other.includeModTime - prevOther.modTime);
1556 other = other.youngestInclude;
1557 }
1558 }
1559 // All includes are clean, so we are too
1560
1561 clean = true;
1562
1563 // touch everything that includes or depends on this
1564 foreach (other; includedBy.byKey()) {
1565 other.touch;
1566 }
1567 foreach (other; dependedBy.byKey()) {
1568 if (other.activated) other.touch;
1569 }
1570 }
1571 }
1572
1573
1574 //
1575 // Obj - an object file built from a source file
1576 //
1577 final class Obj : File {
1578 static Obj[File] bySource; // object files by the source file they are built from
1579
1580 this(ref Origin origin, File source) {
1581
1582 string name_ = setExtension(source.name, "o");
1583 string path_ = prospectivePath("obj", source.parent, name_);
1584
1585 super(origin, source.parent, name_, Privacy.PUBLIC, path_, false, true);
1586
1587 errorUnless(source !in bySource, origin,
1588 "source file %s already used to build an object file", source);
1589 bySource[source] = this;
1590
1591 string actionName;
1592 string actionCommand;
1593
1594 switch (extension(source.name)) {
1595 case ".cc":
1596 case ".cpp":
1597 actionName = format("%-15s %s", "C++", path);
1598 actionCommand = format("g++ -c %s -DTRACE_DOMAIN=\\\"%s\\\"",
1599 getOption("C++FLAGS"), source.path);
1600 foreach (dir; split(getOption("HEADERS"))) {
1601 actionCommand ~= format(" -isystem %s", dir);
1602 }
1603 actionCommand ~= " -iquote src -iquote obj -iquote .";
1604 actionCommand ~= format(" -o %s %s", path, source.path);
1605 break;
1606 case ".c":
1607 actionName = format("%-15s %s", "C", path);
1608 actionCommand = format("gcc -c %s -DTRACE_DOMAIN=\\\"%s\\\"",
1609 getOption("CCFLAGS"), source.path);
1610 foreach (dir; split(getOption("HEADERS"))) {
1611 actionCommand ~= format(" -isystem %s", dir);
1612 }
1613 actionCommand ~= " -iquote src -iquote obj -iquote .";
1614 actionCommand ~= format(" -o %s %s", path, source.path);
1615 break;
1616 case ".d":
1617 actionName = format("%-15s %s", "D", path);
1618 actionCommand = format("dmd -c %s", getOption("DFLAGS"));
1619 foreach (dir; split(getOption("IMPORTS"))) {
1620 actionCommand ~= format(" -I%s", dir);
1621 }
1622 actionCommand ~= " -Isrc -Iobj";
1623 actionCommand ~= format(" -of%s %s", path, source.path);
1624 break;
1625 default:
1626 error(origin, "Unsupported source file extension %s", extension(source.name));
1627 }
1628
1629 action = new Action(origin, actionName, actionCommand, [this], [source]);
1630 addReference(origin, source);
1631 }
1632 }
1633
1634
1635 //
1636 // Binary - a binary file incorporating object files and 'owning' source files.
1637 //
1638 abstract class Binary : File {
1639 static Binary[File] byContent; // binaries by the header and body files they 'contain'
1640
1641 struct Source {
1642 string name;
1643 Privacy privacy;
1644 }
1645
1646 bool isD;
1647 File[] objs;
1648 File[] headers;
1649 bool[SysLib] reqSysLibs;
1650 bool[Binary] reqBinaries;
1651
1652
1653 // create a binary using files from this package.
1654 // All the sources themselves may be already-known built files,
1655 // but can't already be used by another Binary.
1656 this(ref Origin origin, Pkg pkg, string name_, string path_, string[] requires) {
1657 super(origin, pkg, name_, Privacy.PUBLIC, path_, false, true);
1658
1659 // required system libraries
1660 foreach (req; requires) {
1661 if (req !in SysLib.byName) {
1662 new SysLib(req);
1663 }
1664 SysLib lib = SysLib.byName[req];
1665 if (lib !in reqSysLibs) {
1666 reqSysLibs[lib] = true;
1667 }
1668 }
1669 }
1670
1671 // Add sources to this Binary. Adding them after construction allows generated source
1672 // files to be children of the Binary.
1673 void addMySources(ref Origin origin, Source[] sources) {
1674
1675 bool isScannable(string extension) {
1676 return
1677 extension == ".c" ||
1678 extension == ".cc" ||
1679 extension == ".h" ||
1680 extension == ".d";
1681 }
1682
1683 errorUnless(sources.length > 0, origin, "binary must have at least one source file");
1684 foreach (source; sources) {
1685 string ext = extension(source.name);
1686 File sourceFile = File.addSource(origin, this, source.name, source.privacy, isScannable(ext));
1687 byContent[sourceFile] = this;
1688
1689 if (ext == ".d" || ext == ".cc" || ext == ".c") {
1690 // a source file that we generate an Obj from
1691 objs ~= new Obj(origin, sourceFile);
1692
1693 if (ext == ".d") {
1694 isD = true;
1695 }
1696 }
1697 else if (ext == ".ipc") {
1698 // an inter-process-communication file from which we generate a header file
1699
1700 // make sure the ipc-compiler has already been defined
1701 string compilerPath = buildPath("dist", "bin", "ipc-compiler");
1702 File* compiler = compilerPath in File.byPath;
1703 errorUnless(compiler !is null,
1704 origin,
1705 "Cannot cook an ipc files before creating the compiler (%s)",
1706 compilerPath);
1707
1708 // build a header file from this ipc file
1709 string destPath = buildPath("obj", parent.trail, stripExtension(source.name) ~ ".h");
1710 File dest = new File(origin, this, baseName(destPath), source.privacy, destPath, true, true);
1711 dest.action = new Action(origin,
1712 format("%-15s %s", "cook-ipc", sourceFile.path),
1713 format("ipc-compiler %s %s", sourceFile.path, dest.path),
1714 [dest],
1715 [sourceFile, *compiler]);
1716 byContent[dest] = this;
1717 headers ~= dest;
1718 }
1719 else {
1720 headers ~= sourceFile;
1721 }
1722 }
1723 }
1724
1725 override void includeAdded(ref Origin origin, File includer, File included) {
1726 // A file we depend on (includer) has included another file (included).
1727 // If this means that this 'needs' another Binary, remember the fact and
1728 // also add a dependency on that other Binary. Note that the dependency
1729 // is often not 'real' (a StaticLib doesn't actually depend on other StaticLibs),
1730 // but it is a very useful simplification when working out which libraries an
1731 // Exe depends on.
1732 //say("%s: %s includes %s", this.path, includer.path, included.path);
1733 if (includer in byContent && byContent[includer] is this) {
1734 Binary * container = included in byContent;
1735 errorUnless(container !is null, origin, "included file is not contained in a library");
1736 if (*container !is this && *container !in reqBinaries) {
1737
1738 // we require the container of the included file
1739 reqBinaries[*container] = true;
1740
1741 // add a dependancy and a reference
1742 addReference(origin, *container, format(" because %s includes %s", includer.path, included.path));
1743 action.addDependency(*container);
1744 //say("%s requires %s", this.path, container.path);
1745 }
1746 }
1747 }
1748 }
1749
1750
1751 //
1752 // StaticLib - a static library.
1753 //
1754 final class StaticLib : Binary {
1755
1756 string uniqueName;
1757
1758 this(ref Origin origin, Pkg pkg, string name_, string[] requires) {
1759 uniqueName = std.array.replace(buildPath(pkg.trail, name_), dirSeparator, "-") ~ "-s";
1760 if (name_ == pkg.name) uniqueName = std.array.replace(pkg.trail, dirSeparator, "-") ~ "-s";
1761 string _path = buildPath("obj", format("lib%s.a", uniqueName));
1762 super(origin, pkg, name_, _path, requires);
1763 }
1764
1765 void addSources(ref Origin origin, string[] publicSources, string[] protectedSources) {
1766 Source[] sources;
1767 foreach (name; protectedSources) {
1768 sources ~= Source(name, Privacy.SEMI_PROTECTED);
1769 }
1770 foreach (name; publicSources) {
1771 sources ~= Source(name, Privacy.PUBLIC);
1772 }
1773 addMySources(origin, sources);
1774
1775 // action
1776 string command;
1777 if (objs.length) {
1778 command = format("rm -f %s; ar csr %s", path, path);
1779 foreach (obj; objs) {
1780 command ~= format(" %s", obj.path);
1781 }
1782 }
1783 else {
1784 command = format("rm -f %s; echo dummy > %s", path, path);
1785 }
1786 action = new Action(origin, format("%-15s %s", "StaticLib", path), command, [this], objs ~ headers);
1787 }
1788 }
1789
1790
1791 //
1792 // DynamicLib - a dynamic library. Contains all of the object files
1793 // from a number of specified StaticLibs. If defined prior to an Exe, the Exe will
1794 // link with the DynamicLib instead of those StaticLibs.
1795 //
1796 // Any StaticLibs required by the incorporated StaticLibs must also be incorporated
1797 // into DynamicLibs.
1798 //
1799 // The static lib names are relative to pkg, and therefore only descendants of the DynamicLib's
1800 // parent can be incorporated.
1801 //
1802 final class DynamicLib : File {
1803 static DynamicLib[StaticLib] byContent; // dynamic libs by the static libs they 'contain'
1804 Origin origin;
1805 bool augmented;
1806 string uniqueName;
1807
1808 StaticLib[] staticLibs;
1809
1810 this(ref Origin origin_, Pkg pkg, string name_, string[] staticTrails, bool isPlugin) {
1811 origin = origin_;
1812
1813 uniqueName = std.array.replace(buildPath(pkg.trail, name_), "/", "-");
1814 if (name_ == pkg.name) uniqueName = std.array.replace(pkg.trail, dirSeparator, "-");
1815 string _path = buildPath("dist", "lib", format("lib%s.so", uniqueName));
1816 if (isPlugin) _path = buildPath("dist", "lib", "plugins", format("lib%s.so", uniqueName));
1817
1818 super(origin, pkg, name_ ~ "-dynamic", Privacy.PUBLIC, _path, false, true);
1819
1820 foreach (trail; staticTrails) {
1821 string trail1 = buildPath(pkg.trail, trail, baseName(trail));
1822 string trail2 = buildPath(pkg.trail, trail);
1823 Node* node = trail1 in Node.byTrail;
1824 if (node is null || cast(StaticLib*) node is null) {
1825 node = trail2 in Node.byTrail;
1826 if (node is null || cast(StaticLib*) node is null) {
1827 error(origin,
1828 "Unknown static-lib %s, looked for with trails %s and %s",
1829 trail, trail1, trail2);
1830 }
1831 }
1832 StaticLib* staticLib = cast(StaticLib*) node;
1833 errorUnless(*staticLib !in byContent, origin,
1834 "static lib %s already used by dynamic lib %s",
1835 *staticLib, byContent[*staticLib]);
1836 addReference(origin, *staticLib);
1837 staticLibs ~= *staticLib;
1838 byContent[*staticLib] = this;
1839 }
1840 errorUnless(staticLibs.length > 0, origin, "dynamic-lib must have at least one static-lib");
1841
1842 // action
1843 bool[SysLib] gotSysLibs;
1844 string actionName = format("%-15s %s", "DynamicLib", path);
1845 string command = format("g++ -shared %s -o %s", getOption("LINKFLAGS"), path);
1846 File[] depLibs;
1847 foreach (staticLib; staticLibs) {
1848 depLibs ~= staticLib;
1849 foreach (obj; staticLib.objs) {
1850 command ~= format(" %s", obj.path);
1851 }
1852 }
1853
1854 action = new Action(origin, actionName, command, [cast(File)this], depLibs);
1855 }
1856
1857
1858 // Called just before our action is issued.
1859 // Verify that all the StaticLibs we now know that we depend on are contained by this or
1860 // another earlier-defined-than-this DynamicLib.
1861 // Add any required SysLibs to our action.
1862 override bool augmentAction() {
1863 if (augmented) return false;
1864 augmented = true;
1865
1866 bool[StaticLib] doneStaticLibs;
1867 bool[SysLib] gotSysLibs;
1868 SysLib[] sysLibs;
1869 bool added;
1870
1871 //say("augmenting action for DynamicLib %s", name);
1872 void accumulate(StaticLib lib) {
1873 //say(" accumulating %s", lib.name);
1874 foreach (other; lib.reqBinaries.keys) {
1875 StaticLib next = cast(StaticLib) other;
1876 assert(next !is null);
1877 if (next !in doneStaticLibs) {
1878 accumulate(next);
1879 }
1880 }
1881 if (lib !in doneStaticLibs) {
1882 doneStaticLibs[lib] = true;
1883 errorUnless(lib.objs.length == 0 ||
1884 (lib in byContent && byContent[lib].number <= number),
1885 origin,
1886 "dynamic-lib %s requires static-lib %s (%s) which "
1887 "is not contained in a pre-defined dynamic-lib",
1888 name, lib.trail, lib.path);
1889 foreach (sys; lib.reqSysLibs.keys) {
1890 if (sys !in gotSysLibs) {
1891 //say(" new required SysLib %s", sys.name);
1892 gotSysLibs[sys] = true;
1893 sysLibs ~= sys;
1894 added = true;
1895 }
1896 }
1897 }
1898 }
1899 foreach (lib; staticLibs) {
1900 accumulate(lib);
1901 }
1902
1903 string augmentation;
1904 foreach (sys; sysLibs) {
1905 augmentation ~= format(" -l%s", sys.name);
1906 }
1907 //say("augmentation is %s", augmentation);
1908 action.augment(augmentation);
1909 return added;
1910 }
1911 }
1912
1913
1914 //
1915 // Exe - An executable file
1916 //
1917 final class Exe : Binary {
1918
1919 bool augmented;
1920 string desc;
1921 string src;
1922 string dest;
1923
1924 // create an executable using files from this package, linking to libraries
1925 // that contain any included header files, and any required system libraries.
1926 // Note that any system libraries required by inferred local libraries are
1927 // automatically linked to.
1928 this(ref Origin origin, Pkg pkg, string kind, string name_, string[] requires) {
1929 // interpret kind
1930 switch (kind) {
1931 case "dist-util": desc = "DistUtil"; src = "util"; dest = buildPath("dist", "bin", name_); break;
1932 case "priv-util": desc = "PrivUtil"; src = "util"; dest = buildPath("priv", pkg.trail, name_); break;
1933 case "test-util": desc = "TestExe"; src = "test"; dest = buildPath("priv", pkg.trail, name_); break;
1934 default: assert(0, "invalid Exe kind " ~ kind);
1935 }
1936
1937 super(origin, pkg, name_ ~ "-exe", dest, requires);
1938
1939 if (kind == "test-util") {
1940 File test = new File(origin, pkg, name ~ "-result",
1941 Privacy.PRIVATE, dest ~ "-passed", false, true);
1942 test.action = new Action(origin,
1943 format("%-15s %s", "TestResult", test.path),
1944 format("./test %s", dest),
1945 [test],
1946 [this]);
1947 }
1948 }
1949
1950
1951 void addSources(ref Origin origin, string[] sources) {
1952 errorUnless(sources.length > 0, origin, "An exe must have at least one source file");
1953 Source[] _sources;
1954 foreach (source; sources) {
1955 _sources ~= Source(buildPath(src, source), Privacy.PROTECTED);
1956 }
1957 addMySources(origin, _sources);
1958
1959 // exe action
1960 string command;
1961 string ext = extension(sources[0]);
1962 switch (ext) {
1963 case ".cc":
1964 case ".c":
1965 command = format("%s %s -o %s",
1966 ext == "c" ? "gcc" : "g++",
1967 getOption("LINKFLAGS"), dest);
1968 foreach (dir; split(getOption("HEADERS"))) {
1969 command ~= format(" -isystem %s", dir);
1970 }
1971 foreach (obj; objs) {
1972 command ~= format(" %s", obj.path);
1973 }
1974 command ~= format(" -L%s", buildPath("dist", "lib"));
1975 command ~= format(" -L%s", buildPath("dist", "lib", "plugins"));
1976 command ~= format(" -Lobj");
1977 break;
1978 case ".d":
1979 command = format("dmd %s -of%s ", getOption("DLINKFLAGS"), path);
1980 foreach (obj; objs) {
1981 command ~= format(" %s", obj.path);
1982 }
1983 command ~= format(" -L-Lobj");
1984 break;
1985 default:
1986 error(origin, "Unsupported source file extension %s in %s", extension(sources[0]), path);
1987 }
1988
1989 action = new Action(origin, format("%-15s %s", desc, dest), command, [this], objs ~ headers);
1990 }
1991
1992
1993 // Called just before our action is issued - augment the action's command string
1994 // with the library dependencies that we should now know about via includeAdded().
1995 // Return true if dependencies were added.
1996 override bool augmentAction() {
1997 if (augmented) return false;
1998 augmented = true;
1999 bool added = false;
2000 //say("augmenting %s's action command", this);
2001
2002 // binaries we require, with most fundamental first
2003 bool[DynamicLib] gotDynamicLibs;
2004 bool[StaticLib] gotStaticLibs;
2005 bool[SysLib] gotSysLibs;
2006 DynamicLib[] dynamicLibs;
2007 StaticLib[] staticLibs;
2008 SysLib[] sysLibs;
2009
2010 // accumulate the libraries needed
2011 void accumulate(Binary binary) {
2012 //say("accumulating binary %s", binary.path);
2013 foreach (other; binary.reqBinaries.keys) {
2014 StaticLib lib = cast(StaticLib) other;
2015 assert(lib !is null);
2016 if (lib !in gotStaticLibs) {
2017 accumulate(other);
2018 }
2019 }
2020 if (binary is this) {
2021 foreach (sys; reqSysLibs.keys) {
2022 if (sys !in gotSysLibs) {
2023 //say(" using sys-lib %s", sys.name);
2024 gotSysLibs[sys] = true;
2025 sysLibs ~= sys;
2026 }
2027 }
2028 }
2029 else {
2030 StaticLib lib = cast(StaticLib) binary;
2031 if (lib !in gotStaticLibs) {
2032 //say(" require static-lib %s", lib.uniqueName);
2033 gotStaticLibs[lib] = true;
2034 foreach (sys; lib.reqSysLibs.keys) {
2035 if (sys !in gotSysLibs) {
2036 //say(" using sys-lib %s", sys.name);
2037 gotSysLibs[sys] = true;
2038 sysLibs ~= sys;
2039 }
2040 }
2041
2042 DynamicLib* dynamic = lib in DynamicLib.byContent;
2043 if (dynamic !is null && dynamic.number < this.number) {
2044 // use the dynamic lib that contains the static lib
2045 if (*dynamic !in gotDynamicLibs) {
2046 //say(" using dynamic-lib %s to cover %s", dynamic.name, lib.uniqueName);
2047 gotDynamicLibs[*dynamic] = true;
2048 dynamicLibs ~= *dynamic;
2049 action.addDependency(*dynamic);
2050 added = true;
2051
2052 // we have to also accumulate everything this dynamic lib needs
2053 foreach (contained; dynamic.staticLibs) {
2054 accumulate(contained);
2055 }
2056 }
2057 }
2058 else {
2059 // use the static lib
2060 //say(" using static-lib %s", lib.uniqueName);
2061 staticLibs ~= lib;
2062 }
2063 }
2064 }
2065 }
2066 //say("accumulating required libraries for exe %s", path);
2067 accumulate(this);
2068
2069 string extra;
2070 if (isD) extra = "-L";
2071
2072 string augmentation;
2073 foreach (lib; retro(staticLibs)) {
2074 if (lib.objs.length) {
2075 augmentation ~= format(" %s-l%s", extra, lib.uniqueName);
2076 }
2077 }
2078 foreach (lib; retro(dynamicLibs)) {
2079 augmentation ~= format(" %s-l%s", extra, lib.uniqueName);
2080 }
2081 foreach (lib; retro(sysLibs)) {
2082 augmentation ~= format(" %s-l%s", extra, lib.name);
2083 }
2084 //say("%s augmentation is '%s'", this, augmentation);
2085
2086 action.augment(augmentation);
2087 return added;
2088 }
2089 }
2090
2091
2092 //
2093 // Mark all built files in child or referred packages as needed
2094 //
2095 int markNeeded(Node given) {
2096 static bool[Node] done;
2097
2098 int qty = 0;
2099 if (given in done) return qty;
2100 done[given] = true;
2101
2102 File file = cast(File) given;
2103 if (file && file.built) {
2104 // activate this file
2105 if (file.action is null) {
2106 fatal("file %s activated before its action was set", file);
2107 }
2108 //say("activating %s", file.path);
2109 file.activated = true;
2110 File.allActivated[file] = true;
2111 File.outstanding[file] = true;
2112 qty++;
2113 }
2114
2115 // recurse into child and referred nodes
2116 foreach (child; chain(given.children, given.refers)) {
2117 qty += markNeeded(child);
2118 }
2119
2120 if (file) {
2121 // touch this file to trigger building
2122 file.touch;
2123 }
2124
2125 return qty;
2126 }
2127
2128
2129 //
2130 // Process a Bobfile
2131 //
2132 void processBobfile(string indent, Pkg pkg) {
2133 static bool[Pkg] processed;
2134 if (pkg in processed) return;
2135 processed[pkg] = true;
2136
2137 if (g_print_rules) say("%sprocessing %s", indent, pkg.bobfile);
2138 indent ~= " ";
2139 foreach (statement; readBobfile(pkg.bobfile.path)) {
2140 if (g_print_rules) say("%s%s", indent, statement.toString);
2141 switch (statement.rule) {
2142
2143 // packages
2144 case "contain":
2145 foreach (name; statement.targets) {
2146 errorUnless(dirName(name) == ".", statement.origin,
2147 "Contained packages have to be top-level");
2148 Privacy privacy = privacyOf(statement.origin, statement.arg1);
2149 Pkg newPkg = new Pkg(statement.origin, pkg, name, privacy);
2150 processBobfile(indent, newPkg);
2151 }
2152 break;
2153
2154 case "refer":
2155 foreach (trail; statement.targets) {
2156 Pkg* other = cast(Pkg*) (trail in Node.byTrail);
2157 if (other is null) {
2158 // create the referenced package which must be top-level, then refer to it
2159 errorUnless(dirName(trail) == ".", statement.origin,
2160 "Previously-unknown referenced package %s has to be top-level", trail);
2161 Pkg newPkg = new Pkg(statement.origin, Node.byTrail["root"], trail, Privacy.PUBLIC);
2162 processBobfile(indent, newPkg);
2163 pkg.addReference(statement.origin, newPkg);
2164 }
2165 else {
2166 // refer to the existing package
2167 errorUnless(other !is null, statement.origin,
2168 "Cannot refer to unknown pkg %s", trail);
2169 pkg.addReference(statement.origin, *other);
2170 }
2171 }
2172 break;
2173
2174 // libraries
2175 case "static-lib":
2176 {
2177 errorUnless(statement.targets.length == 1, statement.origin,
2178 "Can only have one static-lib name per rule");
2179 StaticLib lib = new StaticLib(statement.origin, pkg, statement.targets[0], statement.arg3);
2180 lib.addSources(statement.origin, statement.arg1, statement.arg2);
2181 }
2182 break;
2183
2184 case "dynamic-lib":
2185 {
2186 errorUnless(statement.targets.length == 1, statement.origin,
2187 "Can only have one dynamic-lib name per rule");
2188 new DynamicLib(statement.origin, pkg, statement.targets[0], statement.arg1,
2189 statement.arg2.length == 1 && statement.arg2[0] == "plugin");
2190 }
2191 break;
2192
2193 case "dist-util":
2194 case "priv-util":
2195 case "test-util":
2196 {
2197 errorUnless(statement.targets.length == 1,
2198 statement.origin,
2199 "Can only have one util/exe name per rule");
2200 Exe exe = new Exe(statement.origin, pkg, statement.rule, statement.targets[0], statement.arg2);
2201 exe.addSources(statement.origin, statement.arg1);
2202 }
2203 break;
2204
2205 case "tao-lib":
2206 {
2207 //
2208 // tao-lib lib-name : idl-sources; # idl files and gen src in package dir
2209 //
2210 errorUnless(statement.targets.length == 1, statement.origin,
2211 "Can only have one tao static-lib name per rule");
2212
2213
2214 //
2215 // create the static-lib
2216 //
2217
2218 auto requires = [
2219 "TAO_CosNaming",
2220 "TAO_PortableServer",
2221 "TAO_DynamicAny",
2222 "TAO",
2223 "ACE",
2224 "rt",
2225 "dl"];
2226 auto lib = new StaticLib(statement.origin, pkg, statement.targets[0], requires);
2227
2228 //
2229 // create the idl files and the source files generated from them
2230 //
2231
2232 string[] publicNames;
2233 string[] protectedNames;
2234 foreach (idlName; statement.arg1) {
2235
2236 // idl file
2237 File idl = File.addSource(statement.origin, pkg, idlName, Privacy.PROTECTED, false);
2238
2239 // paths of all generated files
2240 string base = buildPath("obj", pkg.trail, stripExtension(idlName));
2241 string[] junk = [
2242 base ~ "S_T.cc"];
2243 string[] publicPaths = [
2244 base ~ "C.h",
2245 base ~ "S.h" ];
2246 string[] protectedPaths = [
2247 base ~ "S_T.inl",
2248 base ~ "C.inl",
2249 base ~ "S.inl",
2250 base ~ "S_T.h",
2251 base ~ "C.cc",
2252 base ~ "S.cc" ];
2253
2254 // generated files
2255 File[] files;
2256 foreach (path; junk) {
2257 files ~= new File(statement.origin, lib, baseName(path),
2258 Privacy.PRIVATE, path, false, true);
2259 }
2260 foreach (path; publicPaths) {
2261 files ~= new File(statement.origin, lib, baseName(path),
2262 Privacy.PUBLIC, path, !(extension(path) == ".inl"), true);
2263 publicNames ~= baseName(path);
2264 }
2265 foreach (path; protectedPaths) {
2266 files ~= new File(statement.origin, lib, baseName(path),
2267 Privacy.PROTECTED, path, !(extension(path) == ".inl"), true);
2268 protectedNames ~= baseName(path);
2269 }
2270
2271 // action to generate the files and edit them to be correct
2272 string command = format("%s -in -Ce -cs C.cc -ss S.cc -sT S_T.cc", getOption("TAO_IDL"));
2273 if (toLower(getOption("GENERATE_EMPTY_SERVANT")) == "true") {
2274 command ~= " -GI -GIh \"_impl-rename.h\" -GIs \"_impl-rename.cc\" -GIe \"Impl\" -GIc -GIa";
2275 }
2276 foreach (d; split(getOption("IDL_HEADERS"))) {
2277 command ~= format(" -I%s", d);
2278 }
2279 command ~= format(" -I%s", dirName(idl.path));
2280 command ~= format(" -I%s", dirName(base));
2281 command ~= " -o " ~ buildPath("obj", pkg.trail) ~ " " ~ idl.path ~ " &&";
2282 command ~=
2283 ` sed --in-place --separate` ~
2284 ` -e '/#include/s/\"orbsvcs\/\([^\"]*\)\"/\<orbsvcs\/\1\>/'` ~
2285 ` -e '/#include/s/\"tao\/\([^\"]*\)\"/\<tao\/\1\>/'` ~
2286 ` -e '/#include/s/\"ace\/\([^\"]*\)\"/\<ace\/\1\>/'` ~
2287 ` -e '/#include \"/s|#include \"|#include \"` ~ pkg.trail ~ `/|'` ~ // XXX should not do this if included path contains a slash
2288 ` -e '/#include .*.cc/d'` ~
2289 ` -e 's/[[:space:]]\+$//'`;
2290 foreach (file; files) {
2291 command ~= " " ~ file.path;
2292 }
2293 auto action = new Action(statement.origin, format("%-15s %s", statement.rule, idl.path),
2294 command, files, [idl]);
2295
2296 foreach (file; files) {
2297 file.action = action;
2298 }
2299 }
2300
2301 // add the generated source files as the sources of the library, completing its definition
2302 lib.addSources(statement.origin, publicNames, protectedNames);
2303 }
2304 break;
2305
2306 case "dist-data":
2307 case "test-data":
2308 case "util-data":
2309 case "doc-data":
2310 {
2311 void dataRule(ref Origin origin, Pkg pkg, string kind, string extra) {
2312 string fromPath;
2313 string destPath;
2314 if (kind == "doc-data") {
2315 fromPath = buildPath("src", pkg.trail, "doc", extra);
2316 destPath = buildPath("priv", pkg.trail, "doc", extra);
2317 }
2318 else if (kind == "test-data") {
2319 fromPath = buildPath("src", pkg.trail, "test", extra);
2320 destPath = buildPath("priv", pkg.trail, extra);
2321 }
2322 else if (kind == "util-data") {
2323 fromPath = buildPath("src", pkg.trail, "util", extra);
2324 destPath = buildPath("priv", pkg.trail, extra);
2325 }
2326 else if (kind == "dist-data") {
2327 fromPath = buildPath("src", pkg.trail, "data", extra);
2328 destPath = buildPath("dist", "data", extra);
2329 }
2330
2331 if (isDir(fromPath)) {
2332 // recurse into directory
2333 foreach (string path; dirEntries(fromPath, SpanMode.shallow)) {
2334 dataRule(origin, pkg, kind, buildPath(extra, path.baseName));
2335 }
2336 }
2337 else {
2338 // set up from and dest files, and an action to create the dest.
2339 string name = std.array.replace(extra, dirSeparator, "-");
2340 File from = new File(origin, pkg, name ~ "-src", Privacy.PUBLIC, fromPath, false, false);
2341 File dest = new File(origin, pkg, name ~ "-dest", Privacy.PUBLIC, destPath, false, true);
2342 dest.action = new Action(origin,
2343 format("%-15s %s", "Data", dest.path),
2344 format("cp %s %s", from.path, dest.path),
2345 [dest],
2346 [from]);
2347 }
2348 }
2349
2350 foreach (name; statement.targets) {
2351 dataRule(statement.origin, pkg, statement.rule, name);
2352 }
2353 }
2354 break;
2355
2356 case "doc":
2357 {
2358 foreach (name; statement.targets) {
2359 string fromPath = buildPath("src", pkg.trail, "doc", name ~ ".rst");
2360 string destPath = buildPath("priv", pkg.trail, "doc", name ~ ".html");
2361
2362 errorUnless(exists(fromPath) && !isDir(fromPath), statement.origin,
2363 "%s not found", fromPath);
2364
2365 File from = new File(statement.origin, pkg, name ~ "-rst",
2366 Privacy.PUBLIC, fromPath, false, false);
2367
2368 File dest = new File(statement.origin, pkg, name ~ "-html",
2369 Privacy.PUBLIC, destPath, false, true);
2370
2371 dest.action = new Action(statement.origin,
2372 format("%-15s %s", "Doc", dest.path),
2373 format("%s --exit-status=2 %s %s",
2374 getOption("RST2HTML"), from.path, dest.path),
2375 [dest],
2376 [from]);
2377 }
2378 }
2379 break;
2380
2381 case "dist-shell":
2382 case "priv-shell":
2383 {
2384 foreach (name; statement.targets) {
2385
2386 string fromPath = buildPath("src", pkg.trail, "util", name);
2387 string destPath;
2388 if (statement.rule == "dist-shell") {
2389 destPath = buildPath("dist", "bin", name);
2390 }
2391 else {
2392 destPath = buildPath("priv", pkg.trail, name);
2393 }
2394
2395 errorUnless(exists(fromPath) && !isDir(fromPath), statement.origin,
2396 "%s not found", fromPath);
2397
2398 File from = new File(statement.origin, pkg, name ~ "-sh",
2399 Privacy.PUBLIC, fromPath, false, false);
2400
2401 File dest = new File(statement.origin, pkg, name,
2402 Privacy.PUBLIC, destPath, false, true);
2403
2404 dest.action = new Action(statement.origin,
2405 format("%-15s %s", "Shell", dest.path),
2406 format("cp -f %s %s && chmod +x %s",
2407 from.path, dest.path, dest.path),
2408 [dest],
2409 [from]);
2410 }
2411 }
2412 break;
2413
2414 default:
2415 error(statement.origin, "Unsupported rule '%s'", statement.rule);
2416 }
2417 }
2418 }
2419
2420
2421 // remove any files in obj, priv and dist that aren't marked as needed
2422 void cleandirs() {
2423 void cleanDir(string name) {
2424 //say("cleaning dir %s, cdw=%s", name, getcwd);
2425 if (exists(name) && isDir(name)) {
2426 bool[string] dirs;
2427 foreach (DirEntry entry; dirEntries(name, SpanMode.depth, false)) {
2428 //say(" considering %s", entry.name);
2429 bool isDir = attrIsDir(entry.linkAttributes);
2430
2431 if (!isDir) {
2432 File* file = entry.name in File.byPath;
2433 if (file is null || (*file) !in File.allActivated) {
2434 say("Removing unwanted file %s", entry.name);
2435 std.file.remove(entry.name);
2436 }
2437 else {
2438 // leaving a file in place
2439 //say(" keeping activated file %s", file.path);
2440 dirs[entry.name.dirName] = true;
2441 }
2442 }
2443 else {
2444 if (entry.name !in dirs) {
2445 //say("removing empty dir %s", entry.name);
2446 rmdir(entry.name);
2447 }
2448 else {
2449 //say(" keeping non-empty dir %s", entry.name);
2450 dirs[entry.name.dirName] = true;
2451 }
2452 }
2453 }
2454 }
2455 }
2456 cleanDir("obj");
2457 cleanDir("priv");
2458 cleanDir("dist");
2459 }
2460
2461
2462 //
2463 // Planner function
2464 //
2465 bool doPlanning(int numJobs, bool printRules, bool printDeps, bool printDetails) {
2466
2467 // state variables
2468 size_t inflight;
2469 bool[string] workers;
2470 bool[string] idlers;
2471 bool exiting;
2472 bool success = true;
2473
2474 // receive registration message from each worker and remember its name
2475 while (workers.length < numJobs) {
2476 receive( (string worker) { workers[worker] = true; idlers[worker] = true; } );
2477 }
2478
2479 // local function: an action has completed successfully - update all files built by it
2480 void actionCompleted(string worker, string action) {
2481 //say("%s %s succeeded", action, worker);
2482 --inflight;
2483 idlers[worker] = true;
2484 try {
2485 foreach (file; Action.byName[action].builds) {
2486 file.updated;
2487 }
2488 }
2489 catch (BailException ex) { exiting = true; success = false; }
2490 }
2491
2492 // local function: a worker has terminated - remove it from workers and remember we are exiting
2493 void workerTerminated(string worker) {
2494 exiting = true;
2495 workers.remove(worker);
2496 //say("%s has terminated - %s workers remaining", worker, workers.length);
2497 }
2498
2499
2500 // set up some globals
2501 readOptions;
2502 g_print_rules = printRules;
2503 g_print_deps = printDeps;
2504 g_print_details = printDetails;
2505
2506 string projectPackage = getOption("PROJECT-PACKAGE");
2507
2508 int needed;
2509 try {
2510 // read the project Bobfile and descend into all those it refers to
2511 auto root = new Node();
2512 auto project = new Pkg(Origin(), root, projectPackage, Privacy.PRIVATE);
2513 processBobfile("", project);
2514
2515 // mark all files needed by the project Bobfile as needed, priming the action queue
2516 needed = markNeeded(project);
2517
2518 cleandirs();
2519 }
2520 catch (BailException ex) { exiting = true; success = false; }
2521
2522 while (workers.length) {
2523
2524 // give any idle workers something to do
2525 //say("%s idle workers and %s actions in priority queue", idlers.length, Action.queue.length);
2526 string[] toilers;
2527 foreach (idler, dummy; idlers) {
2528
2529 if (!exiting && !File.outstanding.length) exiting = true;
2530
2531 Tid tid = std.concurrency.locate(idler);
2532
2533 if (exiting) {
2534 // tell idle worker to terminate
2535 //say("telling %s to terminate", idler);
2536 send(tid, true);
2537 toilers ~= idler;
2538 }
2539 else if (!Action.queue.empty) {
2540 // give idle worker an action to perform
2541 //say("giving %s an action", idler);
2542
2543 const Action next = Action.queue.front();
2544 Action.queue.popFront();
2545
2546 string targets;
2547 foreach (target; next.builds) {
2548 ensureParent(target.path);
2549 targets ~= "|" ~ target.path;
2550 }
2551 //say("issuing action %s", next.name);
2552 send(tid, next.name.idup, next.command.idup, targets.idup);
2553 toilers ~= idler;
2554 ++inflight;
2555 }
2556 else if (!inflight) {
2557 fatal("nothing to do and no inflight actions");
2558 }
2559 else {
2560 // nothing to do
2561 //say("nothing to do - waiting for results");
2562 break;
2563 }
2564 }
2565 foreach (toiler; toilers) idlers.remove(toiler);
2566
2567 // receive a completion or failure
2568 receive( (string worker, string action) { actionCompleted(worker, action); },
2569 (string worker) { workerTerminated(worker); } );
2570 }
2571
2572 if (!File.outstanding.length && success) {
2573 say("\n"
2574 "Total number of files: %s\n"
2575 "Number of target files: %s\n"
2576 "Number of activated target files: %s\n"
2577 "Number of files updated: %s\n",
2578 File.byPath.length, File.numBuilt, needed, File.numUpdated);
2579 return true;
2580 }
2581 return false;
2582 }
2583
2584
2585 //-----------------------------------------------------
2586 // Worker
2587 //-----------------------------------------------------
2588
2589 void doWork(bool printActions, uint index, Tid plannerTid) {
2590 bool success;
2591
2592 string myName = format("worker-%d", index);
2593 std.concurrency.register(myName, thisTid);
2594
2595 void perform(string action, string command, string targets) {
2596 if (printActions) { say("\n%s\n", command); }
2597 say("%s", action);
2598
2599 success = false;
2600 string results = buildPath(".bob", myName);
2601
2602 // launch child process to do the action
2603 string str = command ~ " 2>" ~ results;
2604 pid_t child = launcher.launch(str);
2605
2606 // wait for it to complete
2607 for (;;) {
2608 int status;
2609 pid_t wpid = core.sys.posix.sys.wait.waitpid(child, &status, 0);
2610 if (wpid == -1) {
2611 // error, possibly a signal - treat as failure
2612 break;
2613 }
2614 else if (wpid == 0) {
2615 // non-blocking return, which should not happen - treat as failure
2616 break;
2617 }
2618 else if ((status & 0x7f) == 0) {
2619 // child has terminated - might be success
2620 success = ((status & 0xff00) >> 8) == 0;
2621 break;
2622 }
2623 else {
2624 // child state has changed in some way other than termination - treat as failure
2625 break;
2626 }
2627 }
2628 launcher.completed(child);
2629 if (!success) {
2630 bool bailed = launcher.bail;
2631
2632 // delete built files
2633 foreach (target; split(targets, "|")) {
2634 if (exists(target)) {
2635 say(" Deleting %s", target);
2636 std.file.remove(target);
2637 }
2638 }
2639
2640 // print error message
2641 if (!bailed) {
2642 say("\n%s", readText(results));
2643 say("%s: FAILED\n%s", action, command);
2644 fatal("Aborting build due to action failure");
2645 }
2646 else {
2647 // just quietly throw
2648 throw new BailException();
2649 }
2650 }
2651 else {
2652 // tell planner the action succeeded
2653 send(plannerTid, myName, action);
2654 }
2655 }
2656
2657
2658 try {
2659 send(plannerTid, myName);
2660 bool done;
2661 while (!done) {
2662 receive( (string action, string command, string targets) { perform(action, command, targets); },
2663 (bool dummy) { done = true; });
2664 }
2665 }
2666 catch (BailException) {}
2667 catch (Exception ex) { say("Unexpected exception %s", ex); }
2668
2669 // tell planner we are terminating
2670 send(plannerTid, myName);
2671 //say("%s terminating", myName);
2672 }
2673
2674
2675 //--------------------------------------------------------------------------------------
2676 // main
2677 //
2678 // Assumes that the top-level source packages are all located in a src subdirectory,
2679 // and places build outputs in obj, priv and dist subdirectories.
2680 // The local source paths are necessary to minimise the length of actions,
2681 // and is usually achieved by a configure step setting up sym-links to the
2682 // actual source locations.
2683 //--------------------------------------------------------------------------------------
2684
2685 int main(string[] args) {
2686 try {
2687 bool printRules = false;
2688 bool printDeps = false;
2689 bool printDetails = false;
2690 bool printActions = false;
2691 bool help = false;
2692 uint numJobs = 1;
2693
2694 int returnValue = 0;
2695 try {
2696 getopt(args,
2697 std.getopt.config.caseSensitive,
2698 "rules", &printRules,
2699 "deps", &printDeps,
2700 "details", &printDetails,
2701 "actions", &printActions,
2702 "jobs|j", &numJobs,
2703 "help", &help);
2704 }
2705 catch (std.conv.ConvException ex) {
2706 returnValue = 2;
2707 say(ex.msg);
2708 }
2709 catch (object.Exception ex) {
2710 returnValue = 2;
2711 say(ex.msg);
2712 }
2713
2714 if (args.length != 1) {
2715 say("Option processing failed. There are %s unprocessed argument(s): ", args.length - 1);
2716 foreach (uint i, arg; args[1..args.length]) {
2717 say(" %s. \"%s\"", i + 1, arg);
2718 }
2719 returnValue = 2;
2720 }
2721 if (numJobs < 1) {
2722 returnValue = 2;
2723 say("Must allow at least one job!");
2724 }
2725 if (returnValue != 0 || help) {
2726 say("Usage: bob [options]\n"
2727 " --rules print rules\n"
2728 " --deps print dependencies\n"
2729 " --actions print actions\n"
2730 " --details print heaps of details\n"
2731 " --jobs=VALUE maximum number of simultaneous actions\n"
2732 " --help show this message\n"
2733 "target is everything contained in the project Bobfile and anything referred to.");
2734 return returnValue;
2735 }
2736
2737 if (printDetails) {
2738 printActions = true;
2739 printDeps = true;
2740 }
2741
2742
2743 // spawn the workers
2744 foreach (uint i; 0..numJobs) {
2745 //say("spawning worker %s", i);
2746 spawn(&doWork, printActions, i, thisTid);
2747 }
2748
2749 // build everything
2750 returnValue = doPlanning(numJobs, printRules, printDeps, printDetails) ? 0 : 1;
2751
2752 return returnValue;
2753 }
2754 catch (Exception ex) {
2755 say("got unexpected exception %s", ex);
2756 return 1;
2757 }
2758 }