Mercurial > projects > doodle
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 } |