134
|
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 }
|