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

Ported to bob2 !!!
author David Bryant <bagnose@gmail.com>
date Thu, 02 Aug 2012 17:20:52 +0930
parents
children be50d20643a1
comparison
equal deleted inserted replaced
133:9e1a313d8003 134:89e8b0d92f36
1 // Functions used by various configure scripts to set up a build environment
2 // suitable for building with bob and running/deploying.
3 //
4 // Expected to be called from a configure.d in a project directory with code like
5 // the following abbreviated example:
6 /*
7 void main(string args[]) {
8 auto data = initialise(args, "project-name");
9
10 usePackage(data, "libssh2", Constraint.AtLeast, "1.2");
11 useHeader( data, "gcrypt.h");
12 useLibrary(data, "libgcrypt.so");
13 useExecutable(data, "IMAGE_MAGICK_CONVERT", ["convert"]);
14
15 appendRunVar(data, "GST_PLUGIN_PATH", ["${DIST_PATH}/lib/plugins"]);
16 appendBobVar(data, "CCFLAGS", ["-DUSE_BATHY_CHARTING_RASTER_SOURCE"]);
17
18 finalise(data, ["open", "reuse"]); // all packages in this and specified other repos
19 }
20 */
21
22
23 module configure_functions;
24
25 import std.string;
26 import std.getopt;
27 import std.path;
28 import std.file;
29 import std.process;
30 import std.stdio;
31 import std.conv;
32
33 import core.stdc.stdlib;
34 import core.sys.posix.sys.stat;
35
36
37
38
39 private void setMode(string path, uint mode) {
40 chmod(toStringz(path), mode);
41 }
42
43
44 //
45 // Config - a data structure to accumulate configure information.
46 //
47
48 enum Priority { User, Env, Project, System } // highest priority first
49 enum Use { Inc, Bin, Lib, Pkg }
50
51 struct Config {
52 int verboseConfigure;
53 string buildLevel;
54 string productVersion;
55 string backgroundCopyright;
56 string foregroundCopyright;
57 string buildDir;
58 bool[string] architectures;
59 string[][Priority][Use] dirs;
60 string[][Use] prevDirs;
61 string[][string] bobVars;
62 string[][string] runVars;
63 string[][string] buildVars;
64 string reason;
65 string[] configureOptions;
66 string srcDir;
67 }
68
69 bool[string] barred;
70
71
72 //
73 // Return the paths that the linker (ld) searches for libraries.
74 //
75 string[] linkerSearchPaths() {
76 // Note 1: The method used is quite hacky and probably very non-portable.
77 // Note 2: An alternative method (no less hacky) would parse the linker script
78 // for SEARCH_DIR commands.
79 string improbable_name = "this_is_an_extremely_improbable_library_name";
80 string command_base = "LIBRARY_PATH= LD_LIBRARY_PATH= ";
81 command_base ~= "ld --verbose -lthis_is_an_extremely_improbable_library_name < /dev/null";
82 // First command is to check that ld is working and giving the expected error message
83 // Something about the ld command means the output must be piped through something (e.g. "cat -")
84 // otherwise std.process.shell() will throw 'Could not close pipe'.
85 string expected_message = "ld: cannot find -l" ~ improbable_name;
86 string command_1 = command_base ~ " 2>&1 > /dev/null | cat -";
87
88 // Second command is to get the output containing the paths that ld searches for libraries.
89 string good_prefix = "attempt to open ";
90 string good_suffix = "lib" ~ improbable_name ~ ".so failed";
91 string command_2 = command_base ~ " 2> /dev/null | grep '^" ~ good_prefix ~ ".*" ~ good_suffix ~ "$'";
92
93 string[] result;
94 try {
95 //writefln("command_1=%s\n", command_1);
96 string[] ld_lines = std.string.splitLines(std.process.shell(command_1));
97 //if (expected_message in ld_lines) {
98 bool success = false;
99 foreach (line; ld_lines) {
100 if (line == expected_message) { success = true; break; }
101 }
102 if (success) {
103 //writefln("command_2=%s\n", command_2);
104 ld_lines = std.string.splitLines(std.process.shell(command_2));
105 foreach (line; ld_lines) {
106 if (line.length > good_prefix.length + good_suffix.length + 1) {
107 if (line[0 .. good_prefix.length] == good_prefix &&
108 line[$-good_suffix.length .. $] == good_suffix) {
109 //writefln("Match: \"%s\"", line);
110 result ~= line[good_prefix.length .. $-good_suffix.length-1];
111 }
112 }
113 }
114 return result;
115 }
116 else {
117 writefln("Did not get expected output from ld...\ncommand=%s\noutput:", command_1);
118 foreach (line; ld_lines) { writefln(line); }
119 exit(1);
120 }
121 }
122 catch (Exception ex) {
123 writefln("Error running ld: %s", ex);
124 exit(1);
125 }
126 assert(0); // TODO Why aren't the above exit() calls adequate to satisfy the compiler?
127 }
128
129
130 //
131 // Append some tokens to the end of a bob, run or build variable,
132 // appending only if not already present and preserving order.
133 //
134 private void appendVar(ref string[] strings, string[] extra) {
135 foreach (string item; extra) {
136 if (item in barred) continue;
137 bool got = false;
138 foreach (string have; strings) {
139 if (item == have) {
140 got = true;
141 break;
142 }
143 }
144 if (!got) {
145 strings ~= item;
146 }
147 }
148 }
149 void appendBobVar(ref Config data, string var, string[] tokens) {
150 if (data.verboseConfigure >= 2) { writefln("appendBobVar: %s %s", var, tokens); }
151 if (var !in data.bobVars) {
152 data.bobVars[var] = null;
153 }
154 appendVar(data.bobVars[var], tokens);
155 }
156 void appendRunVar(ref Config data, string var, string[] tokens) {
157 if (data.verboseConfigure >= 2) { writefln("appendRunVar: %s %s", var, tokens); }
158 if (var !in data.runVars) {
159 data.runVars[var] = null;
160 }
161 appendVar(data.runVars[var], tokens);
162 }
163 void appendBuildVar(ref Config data, string var, string[] tokens) {
164 if (data.verboseConfigure >= 2) { writefln("appendBuildVar: %s %s", var, tokens); }
165 if (var !in data.buildVars) {
166 data.buildVars[var] = null;
167 }
168 appendVar(data.buildVars[var], tokens);
169 }
170
171
172 //
173 // Return the join of paths with extra
174 //
175 string[] joins(string[] paths, string extra) {
176 string[] result;
177 foreach (path; paths) {
178 result ~= buildPath(path, extra);
179 }
180 return result;
181 }
182
183
184 //
185 // Return a string array of tokens parsed from a number of environment variables, using ':' as delimiter.
186 // Duplicated are discarded.
187 //
188 string[] fromEnv(string[] variables) {
189 string[] result;
190 bool[string] present;
191 foreach (variable; variables) {
192 foreach (token; split(std.process.getenv(variable), ":")) {
193 if (token !in present) {
194 present[token] = true;
195 result ~= token;
196 }
197 }
198 }
199 return result;
200 }
201
202
203
204 //
205 // Return a string representing the given tokens as an environment variable declaration
206 //
207 string toEnv(string[][Priority] tokens, string name) {
208 string result;
209 foreach (string[] strings; tokens) {
210 foreach (string token; strings) {
211 result ~= ":" ~ token;
212 }
213 }
214 if (result && result[0] == ':') {
215 result = result[1..$];
216 }
217 if (result) {
218 result = name ~ "=\"" ~ result ~ "\"";
219 }
220 return result;
221 }
222
223
224 //
225 // Output (to console) the current search paths being used to locate dependencies.
226 //
227 void printSearchDirs(ref string[][Priority][Use] dirs) {
228 foreach (Use use, string[][Priority] v; dirs) {
229 writefln("%s", use);
230 foreach (Priority p, string[] a; v) {
231 writefln(" %s: %s", std.string.rightJustify(std.conv.to!string(p), 7), a);
232 }
233 }
234 }
235
236
237 //
238 // Set project-specific directories to look in for required files
239 //
240 void setProjectDirs(ref Config data, string[][Use] projectDirs) {
241 if (data.verboseConfigure >= 3) { writefln("Updating project search paths:"); }
242 foreach (Use use, string[] dirs; projectDirs) {
243 data.prevDirs[use] = dirs;
244 data.dirs[use][Priority.Project] = dirs;
245 if (data.verboseConfigure >= 3) { writefln("Project %s: %s", use, dirs); }
246 }
247 }
248
249 //
250 // Restore project-specific dirs - only one level of restoration available
251 //
252 void restoreProjectDirs(ref Config data) {
253 if (data.verboseConfigure >= 3) { writefln("Restoring project search paths:"); }
254 foreach (Use use, string[] dirs; data.prevDirs) {
255 data.prevDirs[use] = dirs;
256 data.dirs[use][Priority.Project] = dirs;
257 if (data.verboseConfigure >= 3) { writefln("Project %s: %s", use, dirs); }
258 }
259 }
260
261
262 //
263 // Locate an executable (which can have any of the specified names)
264 // in any of the dirs listed in data.dirs[Use.Bin], and:
265 // * set the executable name to bobVar id so bob can run it efficiently.
266 // * add the dir to runVar PATH, providing acces to other exes in the same dir.
267 //
268 string useExecutable(ref Config data, string id, string[] names) {
269 foreach (string[] dirs; data.dirs[Use.Bin]) {
270 foreach (string dir; dirs) {
271 foreach (name; names) {
272 if (exists(buildPath(dir, name))) {
273 appendBobVar(data, id, [buildPath(dir, name)]);
274 appendRunVar(data, "PATH", [dir]);
275 if (data.verboseConfigure >= 1) { writefln("Found exe %s as %s in %s", id, name, dir); }
276 return dir;
277 }
278 }
279 }
280 }
281 data.reason ~= format("Could not find executable %s by names %s\n", id, names);
282 return "";
283 }
284
285
286 //
287 // Locate an include file in any of data.dirs[Use.Inc], and:
288 // * add the header dir to bobVar HEADERS
289 //
290 string useHeader(ref Config data, string name) {
291 foreach (string[] dirs; data.dirs[Use.Inc]) {
292 foreach (string dir; dirs) {
293 if (exists(buildPath(dir, name))) {
294 appendBobVar(data, "HEADERS", [dir]);
295 if (data.verboseConfigure >= 1) { writefln("Found header <%s> in %s", name, dir); }
296 return dir;
297 }
298 }
299 }
300 data.reason ~= "Could not find header " ~ name ~ "\n";
301 return "";
302 }
303
304
305 //
306 // Locate a library file in any of data.dirs[Use.Lib], and:
307 // * add library dir to bobVar LINKFLAGS (with '-L' prefix)
308 // * Not add library dir to buildVar LIBRARY_PATH (Not necessary if adding -L dir to LINKFLAGS)
309 // * add library dir to runVar LD_LIBRARY_PATH
310 //
311 string useLibrary(ref Config data, string name) {
312 foreach (string[] dirs; data.dirs[Use.Lib]) {
313 foreach (string dir; dirs) {
314 if (exists(buildPath(dir, name))) {
315 appendBobVar(data, "LINKFLAGS", ["-L" ~ dir]);
316 appendRunVar(data, "LD_LIBRARY_PATH", [dir]);
317 if (data.verboseConfigure >= 1) { writefln("Found library %s in %s", name, dir); }
318 return dir;
319 }
320 }
321 }
322 data.reason ~= "Could not find library " ~ name ~ "\n";
323 return "";
324 }
325
326
327 //
328 // Locate a package .pc file in any of data.dirs[Use.Pkg], and
329 // use pkg-config to:
330 // * add library dir to bobVar LINKFLAGS
331 // * Not add library dir to buildVar LIBRARY_PATH (Not necessary if adding -L dir to LINKFLAGS)
332 // * add library dir to runVar LD_LIBRARY_PATH
333 //
334 enum Constraint { Exists, AtLeast, Exact, Max }
335 void usePackage(ref Config data,
336 string name,
337 Constraint constraint = Constraint.Exists,
338 string ver = "") {
339 string[] constraints = ["--exists", "--atleast-version=", "--exact-version=", "--max-version="];
340 try {
341 string prefix = toEnv(data.dirs[Use.Pkg], "PKG_CONFIG_PATH");
342
343 // disable use of "uninstalled" packages (which would otherwise silently be used in preference!)
344 if (prefix && prefix.length != 0) { prefix ~= " "; }
345 prefix ~= "PKG_CONFIG_DISABLE_UNINSTALLED=1";
346
347 //writefln("prefix=%s", prefix);
348
349 string command;
350
351 command = prefix ~ " pkg-config " ~ constraints[constraint] ~ ver ~ " " ~ name;
352 //writefln("command=%s", command);
353 shell(command);
354
355 command = prefix ~ " pkg-config --cflags " ~ name;
356 //writefln("command=%s", command);
357 string ccflags = shell(command);
358 foreach (flag; split(ccflags)) {
359 if (flag.length > 2 && flag[0..2] == "-I") {
360 appendBobVar(data, "HEADERS", [flag[2..$]]);
361 }
362 else {
363 appendBobVar(data, "CCFLAGS", [flag]);
364 }
365 }
366
367 command = prefix ~ " pkg-config --libs-only-L " ~ name;
368 //writefln("command=%s", command);
369 string linkflags = shell(command);
370 appendBobVar(data, "LINKFLAGS", split(linkflags));
371 foreach (flag; split(linkflags)) {
372 if (flag.length > 2 && flag[0..2] == "-L") {
373 //appendBuildVar(data, "LIBRARY_PATH", [flag[2..$]]); not necessary
374 appendRunVar(data, "LD_LIBRARY_PATH", [flag[2..$]]);
375 }
376 }
377
378 if (data.verboseConfigure >= 1) {
379 command = prefix ~ " pkg-config --modversion " ~ name;
380 //writefln("command=%s", command);
381 string modVersion = shell(command);
382 writefln("Found package %s (v%s)", name, chomp(modVersion));
383 }
384 }
385 catch (Exception ex) {
386 data.reason ~= "Could not find package " ~ constraints[constraint] ~ " " ~ ver ~ " " ~ name ~ "\n";
387 }
388 }
389
390
391 //
392 // Locate a corba-tao library in any of data.dirs[Use.Lib], provide access to
393 // required TAO utilities, and set required variables.
394 //
395 void useTao(ref Config data) {
396 string incdir = useHeader(data, "tao/corba.h");
397
398 useLibrary(data, "libTAO.so");
399
400 useExecutable(data, "TAO_IDL", ["tao_idl"]);
401 useExecutable(data, "TAO_NAMING_SERVICE", ["Naming_Service"]);
402 useExecutable(data, "TAO_EVENT_SERVICE", ["CosEvent_Service"]);
403 useExecutable(data, "TAO_INTERFACE_REPO_SERVICE", ["IFR_Service"]);
404 useExecutable(data, "TAO_INTERFACE_COMPILER", ["tao_ifr"]);
405
406 appendBobVar(data, "IDL_HEADERS", [incdir]);
407 appendBobVar(data, "GENERATE_EMPTY_SERVANT", ["false"]);
408 appendBobVar(data, "CCFLAGS", ["-DTAO_HAS_TYPED_EVENT_CHANNEL"]);
409 appendRunVar(data, "ACE_ROOT", [dirName(incdir)]);
410 appendRunVar(data, "TAO_ROOT", [buildPath(dirName(incdir), "TAO")]);
411 }
412
413
414 //
415 // Parse command-line arguments and return resultant Config data
416 //
417 Config initialise(string[] args, string projectPackage) {
418
419 // check that we are in the project directory
420 if (!exists("configure.d")) {
421 writefln("Configure must be run from the project directory, which contains configure.d");
422 exit(1);
423 }
424
425 // parse arguments
426
427 bool help;
428 int verboseConfigure;
429 string buildLevel = "release";
430 string productVersion = "development from " ~ getcwd;
431 string[] architectures;
432 string[] packagePrefixes;
433
434 immutable bool[string] validArchitectures = ["Ubuntu":true, "CentOS-4":true, "CentOS-5":true];
435
436 Config data;
437 auto argsCopy = args.dup; // remember the arguments
438
439 try {
440 getopt(args, std.getopt.config.caseSensitive,
441 "help|h", &help,
442 "verbose+", &verboseConfigure,
443 "build", &buildLevel,
444 "product|p", &productVersion,
445 "architecture|a", &architectures,
446 "package-prefix", &packagePrefixes);
447 }
448 catch (Exception ex) {
449 writefln("Invalid argument(s): %s", ex.msg);
450 help = true;
451 }
452
453 if (help || args.length < 2) {
454 writefln("Usage: configure [options] build-dir-path\n"
455 " --help display this message\n"
456 " --verbose display more configure messages (multiple)\n"
457 " --build=level build level: debug, integrate, release (default) or profile\n"
458 " --product=version sets product version\n"
459 " --architecture=arch sets architecture for conditional Bobfile rules (multiple)\n"
460 " --package-prefix=path looks for locally installed packages at path (multiple)\n");
461 exit(1);
462 }
463 foreach (arch; architectures) {
464 if (arch !in validArchitectures) {
465 writefln("%s is not one of these valid architectures: %s",
466 arch, validArchitectures.keys());
467 exit(1);
468 }
469 data.architectures[arch] = true;
470 }
471
472 string buildDir = args[1];
473
474
475 //
476 // populate and return config data
477 //
478
479 data.srcDir = std.file.getcwd();
480
481 foreach (arg; argsCopy[1..$]) {
482 if (arg != buildDir) {
483 data.configureOptions ~= arg;
484 }
485 }
486
487 //
488 // populate and return config data
489 //
490
491 // Populate data.dirs using packagePrefixes, environment variables, hard-coding, etc.
492 // The Pritority.Project elements are (re)populated via a call to setProjectDirs.
493
494 // add some "standard" user-specific prefixes to packagePrefixes to make life easier for users
495 packagePrefixes ~= ["/opt/acacia/tao", "/opt/acacia/ecw"];
496
497 // System - lowest priority
498 data.dirs[Use.Inc][Priority.System] = ["/include", "/usr/include"];
499 data.dirs[Use.Bin][Priority.System] = ["/bin", "/sbin", "/usr/bin", "/usr/sbin"];
500 data.dirs[Use.Lib][Priority.System] = linkerSearchPaths(); // ["/lib", "/usr/lib"];
501 data.dirs[Use.Pkg][Priority.System] = null; // /usr/lib/pkgconfig is automatically used
502
503 // Prevent System paths from being added to the output
504 foreach (Use use, string[][Priority] v; data.dirs) {
505 foreach (Priority p, string[] a; v) {
506 foreach (string b; a) {
507 barred[b] = true;
508 }
509 }
510 }
511 // Extra protection required for library paths
512 foreach (string v; data.dirs[Use.Lib][Priority.System]) {
513 barred["-L" ~ v] = true;
514 }
515
516 // Project - medium priority, set by call to setProjectDirs
517 data.dirs[Use.Inc][Priority.Project] = null;
518 data.dirs[Use.Bin][Priority.Project] = null;
519 data.dirs[Use.Lib][Priority.Project] = null;
520 data.dirs[Use.Pkg][Priority.Project] = null;
521
522 // Env - high priority
523 data.dirs[Use.Inc][Priority.Env] = fromEnv(["CPATH"]);
524 data.dirs[Use.Bin][Priority.Env] = fromEnv(["PATH"]);
525 data.dirs[Use.Lib][Priority.Env] = fromEnv(["LD_LIBRARY_PATH"/*, "LIBRARY_PATH"*/]);
526 data.dirs[Use.Pkg][Priority.Env] = fromEnv(["PKG_CONFIG_PATH"]);
527
528 // User - highest priority
529 data.dirs[Use.Inc][Priority.User] = joins(packagePrefixes, "include");
530 data.dirs[Use.Bin][Priority.User] = joins(packagePrefixes, "bin");
531 data.dirs[Use.Lib][Priority.User] = joins(packagePrefixes, "lib");
532 data.dirs[Use.Pkg][Priority.User] = joins(packagePrefixes, "lib/pkgconfig");
533
534 // Print the search paths (better to do this elsewhere/when)
535 if (verboseConfigure >= 3) { writefln("Initial search paths:"); printSearchDirs(data.dirs); }
536
537 // assorted variables
538
539 data.verboseConfigure = verboseConfigure;
540 data.buildLevel = buildLevel;
541 data.productVersion = productVersion;
542 // TODO? Automatically insert the current year?
543 data.backgroundCopyright =
544 "Part or all of this software is © Copyright 1995-2011 Acacia Research Pty Ltd. "
545 "All rights reserved.\\n"
546 "This software may utilise third party libraries from various sources.\\n"
547 "These libraries are copyrighted by their respective owners.";
548 data.foregroundCopyright =
549 "Parts of this software are foreground intellectual property. ";
550
551 data.buildDir = buildDir;
552
553 appendBobVar(data, "PROJECT-PACKAGE", [projectPackage]);
554
555 appendBobVar(data, "LINKFLAGS", ["-lstdc++", "-rdynamic"]);
556
557 appendBobVar(data, "DEXTERNALS", ["std", "core"]);
558
559 appendBobVar(data, "DFLAGS", ["-w", "-wi"]);
560
561 appendBobVar(data, "CCFLAGS",
562 ["-fPIC",
563 "-pedantic",
564 "-Werror",
565 "-Wall",
566 "-Wno-long-long",
567 "-Wundef",
568 "-Wredundant-decls"]);
569
570 if (data.buildLevel == "debug") {
571 appendBobVar(data, "DFLAGS", ["-gc"]);
572 appendBobVar(data, "CCFLAGS",
573 ["-O1",
574 "-DACRES_DEBUG=1",
575 "-DACRES_INTEGRATE=1",
576 "-fno-omit-frame-pointer",
577 "-ggdb3"]);
578 }
579 else if (data.buildLevel == "integrate") {
580 appendBobVar(data, "DFLAGS", ["-O"]);
581 appendBobVar(data, "CCFLAGS",
582 ["-O1",
583 "-DACRES_DEBUG=0",
584 "-DACRES_INTEGRATE=1",
585 "-DNDEBUG",
586 "-fno-omit-frame-pointer",
587 "-Wno-unused-variable"]);
588 }
589 else if (data.buildLevel == "profile") {
590 appendBobVar(data, "DFLAGS", ["-O"]);
591 appendBobVar(data, "CCFLAGS",
592 ["-O2",
593 "-DACRES_DEBUG=0",
594 "-DACRES_INTEGRATE=0",
595 "-DNDEBUG",
596 "-fno-omit-frame-pointer",
597 "-Wno-unused-variable",
598 "-ggdb3"]);
599 }
600 else if (data.buildLevel == "release") {
601 appendBobVar(data, "DFLAGS", ["-O", "-release"]);
602 appendBobVar(data, "CCFLAGS",
603 ["-O2",
604 "-DACRES_DEBUG=0",
605 "-DACRES_INTEGRATE=0",
606 "-DNDEBUG",
607 "-fno-omit-frame-pointer",
608 "-Wno-unused-variable"]);
609 }
610 else {
611 writefln("unsupported build level '%s'", data.buildLevel);
612 exit(1);
613 }
614
615 appendBobVar(data, "C++FLAGS",
616 ["-Woverloaded-virtual",
617 "-Wsign-promo",
618 "-Wctor-dtor-privacy",
619 "-Wnon-virtual-dtor"]);
620
621 appendBobVar(data, "VALID_ARCHITECTURES", validArchitectures.keys);
622 appendBobVar(data, "ARCHITECTURES", architectures);
623
624 useExecutable(data, "RST2HTML", ["rst2html.py", "rst2html", "docutils-rst2html.py"]);
625
626 appendRunVar(data, "LD_LIBRARY_PATH", [`${DIST_PATH}/lib`]);
627 appendRunVar(data, "LD_LIBRARY_PATH", [`${DIST_PATH}/lib/plugins`]);
628 appendRunVar(data, "SYSTEM_DATA_PATH", [`${DIST_PATH}/data`]);
629 appendRunVar(data, "PATH", [`${DIST_PATH}/bin`]);
630
631 return data;
632 }
633
634
635 // Write the content to the file if file doesn't already match (and optionally set executable).
636 // File is created if it doesn't exist.
637 void update(ref Config data, string name, string content, bool executable) {
638 string path = buildPath(data.buildDir, name);
639 bool clean = false;
640 if (exists(path)) {
641 string current = cast(string) std.file.read(path);
642 clean = (current == content);
643 }
644 if (!clean) {
645 if (data.verboseConfigure >= 2) { writefln("Setting content of %s", name); }
646 std.file.write(path, content);
647 }
648 if (executable) {
649 uint exeMode = octal!700;
650 uint attr = getAttributes(path);
651 if ((attr & exeMode) != exeMode) {
652 if (data.verboseConfigure >= 4) { writefln("Setting exe mode on %s", name); }
653 setMode(path, exeMode | attr);
654 }
655 }
656 }
657
658
659 //
660 // Set up build environment as specified by data, or issue error messages and bail
661 //
662 // repos are repository names in sibling directories to the directory containing
663 // the configure script.
664 //
665 void finalise(ref Config data, string[] otherRepos) {
666
667 // check that all is well, and bail with an explanation if not
668 if (data.reason.length) {
669 writefln("Configure FAILED because:\n%s\n", data.reason);
670 exit(1);
671 }
672
673 writefln("Configure checks completed ok - establishing build directory...");
674
675 // create build directory
676 if (!exists(data.buildDir)) {
677 mkdirRecurse(data.buildDir);
678 }
679 else if (!isDir(data.buildDir)) {
680 writefln("Configure FAILED because: %s is not a directory", data.buildDir);
681 exit(1);
682 }
683
684 // create Boboptions file from bobVars
685 string bobText;
686 foreach (string key, string[] tokens; data.bobVars) {
687 bobText ~= key ~ " = ";
688 if (key == "C++FLAGS") {
689 // C++FLAGS has all of CCFLAGS too
690 foreach (token; data.bobVars["CCFLAGS"]) {
691 bobText ~= token ~ " ";
692 }
693 }
694 foreach (token; tokens) {
695 bobText ~= token ~ " ";
696 }
697 bobText ~= ";\n";
698 }
699 update(data, "Boboptions", bobText, false);
700
701 // create version_info.h file
702 string versionText;
703 versionText ~= "#ifndef VERSION_INFO__H\n";
704 versionText ~= "#define VERSION_INFO__H\n";
705 versionText ~= "\n";
706 versionText ~= "#define PRODUCT_VERSION \"" ~ data.productVersion ~ "\"\n";
707 versionText ~= "#define FOREGROUND_IP_COPYRIGHT_NOTICE \"" ~ data.foregroundCopyright ~ "\"\n";
708 versionText ~= "#define BACKGROUND_IP_COPYRIGHT_NOTICE \"" ~ data.backgroundCopyright ~ "\"\n";
709 versionText ~= "\n";
710 versionText ~= "#endif /* VERSION_INFO__H */\n";
711 update(data, "version_info.h", versionText, false);
712
713 // set up string for a fix_env bash function
714 string fixText =
715 `# Remove duplicates and empty tokens from a string containing
716 # colon-separated tokens, preserving order.
717 function fix_env () {
718 local original="${1}"
719 local IFS=':'
720 local result=""
721 for item in ${original}; do
722 if [ -z "${item}" ]; then
723 continue
724 fi
725 #echo "item: \"${item}\"" >&2
726 local -i found_existing=0
727 for existing in ${result}; do
728 if [ "${item}" == "${existing}" ]; then
729 found_existing=1
730 break 1
731 fi
732 done
733 if [ ${found_existing} -eq 0 ]; then
734 result="${result:+${result}:}${item}"
735 fi
736 done
737 echo "${result}"
738 }
739 `;
740
741 // create environment-run file
742 string runEnvText;
743 runEnvText ~= "# set up the run environment variables\n\n";
744 runEnvText ~= fixText;
745 runEnvText ~= `if [ -z "${DIST_PATH}" ]; then` ~ "\n";
746 runEnvText ~= ` echo "DIST_PATH not set"` ~ "\n";
747 runEnvText ~= " return 1\n";
748 runEnvText ~= "fi\n";
749 runEnvText ~= "\n";
750 foreach (string key, string[] tokens; data.runVars) {
751 runEnvText ~= "export " ~ key ~ `="$(fix_env "`;
752 foreach (token; tokens) {
753 runEnvText ~= token ~ ":";
754 }
755 runEnvText ~= `${` ~ key ~ `}")"` ~ "\n";
756 }
757 runEnvText ~= "unset fix_env\n";
758 update(data, "environment-run", runEnvText, false);
759
760
761 // create environment-build file
762 string buildEnvText;
763 buildEnvText ~= "# set up the build environment variables\n\n";
764 buildEnvText ~= fixText;
765 buildEnvText ~=
766 `if [ ! -z "${DIST_PATH}" ]; then
767 echo "ERROR: DIST_PATH set when building"
768 return 1
769 fi
770 export DIST_PATH="${PWD}/dist"
771 `;
772 foreach (string key, string[] tokens; data.buildVars) {
773 buildEnvText ~= "export " ~ key ~ `="$(fix_env "`;
774 foreach (token; tokens) {
775 buildEnvText ~= token ~ ":";
776 }
777 buildEnvText ~= `${` ~ key ~ `}")"` ~ "\n";
778 }
779 buildEnvText ~= "unset fix_env\n";
780 buildEnvText ~= "# also pull in the run environment\n";
781 buildEnvText ~= "source ./environment-run\n";
782 update(data, "environment-build", buildEnvText, false);
783
784
785 // create build script
786 string buildText =
787 `#!/bin/bash
788
789 source ./environment-build
790
791 # Rebuild the bob executable if necessary
792 BOB_SRC="./src/build-tool/bob.d"
793 BOB_EXE="./.bob/bob"
794 if [ ! -e ${BOB_EXE} -o ${BOB_SRC} -nt ${BOB_EXE} ]; then
795 echo "Compiling build tool."
796 dmd -O -gc -w -wi ${BOB_SRC} -of${BOB_EXE}
797 if [ $? -ne 0 ]; then
798 echo "Failed to compile the build tool..."
799 exit 1
800 else
801 echo "Build tool compiled successfully."
802 fi
803 fi
804
805 # Test if we are running under eclipse
806 # Cause bob to echo commands passed to compiler to support eclipse auto discovery.
807 # Also change the include directives to those recognised by eclipse CDT.
808 if [ "$1" = "--eclipse" ] ; then
809 shift
810 echo "NOTE: What is displayed here on the console is not exactly what is executed by g++"
811
812 ${BOB_EXE} --actions "$@" 2>&1 | sed -re "s/-iquote|-isystem/-I/g"
813 else
814 ${BOB_EXE} "$@"
815 fi
816 `;
817 update(data, "build", buildText, true);
818
819
820 // create clean script
821 string cleanText =
822 `#!/bin/bash
823
824 if [ $# -eq 0 ]; then
825 rm -rf ./dist ./priv ./obj
826 else
827 echo "Failed: $(basename ${0}) does not accept arguments - it cleans everything."
828 exit 2
829 fi
830 `;
831 update(data, "clean", cleanText, true);
832
833
834 // strings containing common parts of run-like scripts
835 string runPrologText =
836 `#!/bin/bash
837
838 export DIST_PATH="${PWD}/dist"
839 source ./environment-run
840 exe=$(which "$1" 2> /dev/null)
841
842 if [ -z "${exe}" ]; then
843 echo "Couldn't find \"$1\"" >&2
844 exit 1
845 fi
846 export TMP_PATH="$(dirname ${exe})/tmp-$(basename ${exe})"
847 `;
848
849
850 // create (exuberant) ctags config file
851 string dotCtagsText =
852 `--langdef=IDL
853 --langmap=IDL:+.idl
854 --regex-IDL=/^[ \t]*module[ \t]+([a-zA-Z0-9_]+)/\1/n,module,Namespace/e
855 --regex-IDL=/^[ \t]*enum[ \t]+([a-zA-Z0-9_]+)/\1/g,enum/e
856 --regex-IDL=/^[ \t]*struct[ \t]+([a-zA-Z0-9_]+)/\1/c,struct/e
857 --regex-IDL=/^[ \t]*exception[ \t]+([a-zA-Z0-9_]+)/\1/c,exception/e
858 --regex-IDL=/^[ \t]*interface[ \t]+([a-zA-Z0-9_]+)/\1/c,interface/e
859 --regex-IDL=/^[ \t]*typedef[ \t]+[a-zA-Z0-9_:\*<> \t]+[ \t]+([a-zA-Z0-9_]+)[ \t]*;/\1/t,typedef/e
860 --regex-IDL=/^[ \t]*[a-zA-Z0-9_:]+[ \t]+([a-zA-Z0-9_]+)[ \t]*[;]/\1/v,variable/e
861 `;
862 update(data, ".ctags", dotCtagsText, false);
863
864
865 // create make-tags script
866 string makeCtagsText =
867 `#!/bin/bash
868
869 SOURCE_DIR="src"
870 TAGS_FILE="tags"
871
872 find -H "${SOURCE_DIR}"/* -xdev \( \( -type d -name \.svn \) -prune \
873 -o -name \*.cc -o -name \*.h -o -name \*.ccg -o -name \*.hg -o -name \*.hpp -o -name \*.cpp \
874 -o -name \*.inl -o -name \*.i \
875 -o -name \*.idl \) |
876 grep -v ".svn" |
877 # maybe add other grep commands here
878 ctags -f "${TAGS_FILE}" -h default --langmap="c++:+.hg.ccg.inl.i" --extra=+f+q --c++-kinds=+p --tag-relative=yes --totals=yes --fields=+i -L -
879 `;
880 update(data, "make-tags", makeCtagsText, true);
881
882
883 // create make-cooked-tags script
884 string makeCookedCtagsText =
885 `#!/bin/bash
886
887 SOURCE_DIR="obj"
888 TAGS_FILE="cooked-tags"
889
890 find -H "${SOURCE_DIR}"/* -xdev \( \( -type d -name \.svn \) -prune \
891 -o -name \*.cc -o -name \*.h -o -name \*.ccg -o -name \*.hg -o -name \*.hpp -o -name \*.cpp \
892 -o -name \*.idl \) |
893 grep -v ".svn" |
894 # maybe add other grep commands here
895 ctags -f "${TAGS_FILE}" -h default --langmap="c++:+.hg.ccg" --extra=+f+q --c++-kinds=+p --tag-relative=yes --totals=yes --fields=+i -L -
896 `;
897 update(data, "make-cooked-ctags", makeCookedCtagsText, true);
898
899
900 // create test script
901 string testText;
902 testText ~= runPrologText;
903 testText ~=
904 `
905 if [ $# -ne 1 ]; then
906 echo "The test script doesn't support arguments to test executable." >&2
907 echo "Given: ${@}" >&2
908 exit 2
909 fi
910 declare -i return_value=1
911
912 run_test() {
913 # remove results and run the test to make some more
914 set -o pipefail
915 rm -f ${exe}-*
916
917 # Ensure the result file is not zero-length (Bob depends on this)
918 echo ${exe} > ${exe}-result
919 ${exe} >> ${exe}-result 2>&1
920
921 # generate passed or failed file
922 if [ "$?" != "0" ]; then
923 mv ${exe}-result ${exe}-failed
924 echo "${exe}-failed:1: error: test failed"
925 cat ${exe}-failed
926 exit 1
927 else
928 mv ${exe}-result ${exe}-passed
929 rm -rf ${TMP_PATH}
930 fi
931 }
932
933 rm -rf ${TMP_PATH} && mkdir ${TMP_PATH} && run_test
934 `;
935 update(data, "test", testText, true);
936
937
938 // create run script
939 string runText;
940 runText ~= runPrologText;
941 runText ~= "rm -rf ${TMP_PATH} && mkdir ${TMP_PATH} && exec \"$@\"";
942 update(data, "run", runText, true);
943
944 if (data.buildLevel == "profile") {
945 // create perf script
946 string perfText;
947 perfText ~= runPrologText;
948 perfText ~= "echo after exiting, run 'perf report' to see the result\n";
949 perfText ~= "rm -rf ${TMP_PATH} && mkdir ${TMP_PATH} && exec perf record -g -f $@\n";
950 update(data, "perf", perfText, true);
951 }
952
953 if (data.buildLevel != "release") {
954 // create gdb script
955 string gdbText;
956 gdbText ~= runPrologText;
957 gdbText ~= "rm -rf ${TMP_PATH} && mkdir ${TMP_PATH} && exec gdb --args $@\n";
958 update(data, "gdb", gdbText, true);
959
960 // create nemiver script
961 string nemiverText;
962 nemiverText ~= runPrologText;
963 nemiverText ~= "rm -rf ${TMP_PATH} && mkdir ${TMP_PATH} && exec nemiver $@\n";
964 update(data, "nemiver", nemiverText, true);
965 }
966
967 // create valgrind script
968 string valgrindText;
969 valgrindText ~= runPrologText;
970 valgrindText ~= "rm -rf ${TMP_PATH} && mkdir ${TMP_PATH} && exec valgrind $@\n";
971 update(data, "valgrind", valgrindText, true);
972
973
974 //
975 // create src directory with symbolic links to all top-level packages in all
976 // specified repositories
977 //
978
979 // make src dir
980 string srcPath = buildPath(data.buildDir, "src");
981 if (!exists(srcPath)) {
982 mkdir(srcPath);
983 }
984
985 // make a symbolic link to each top-level package in this and other specified repos
986 string[string] pkgPaths; // package paths keyed on package name
987 string project = dirName(getcwd);
988 foreach (string repoName; otherRepos ~ baseName(getcwd)) {
989 string repoPath = buildPath(project, repoName);
990 if (isDir(repoPath)) {
991 //writefln("adding source links for packages in repo %s", repoName);
992 foreach (string path; dirEntries(repoPath, SpanMode.shallow)) {
993 string pkgName = baseName(path);
994 if (isDir(path) && pkgName[0] != '.') {
995 //writefln(" found top-level package %s", pkgName);
996 assert(pkgName !in pkgPaths,
997 format("Package %s found at %s and %s",
998 pkgName, pkgPaths[pkgName], path));
999 pkgPaths[pkgName] = path;
1000 }
1001 }
1002 }
1003 }
1004 foreach (name, path; pkgPaths) {
1005 string linkPath = buildPath(srcPath, name);
1006 system(format("rm -f %s; ln -sn %s %s", linkPath, path, linkPath));
1007 }
1008
1009 // print success
1010 writefln("Build environment in %s is ready to roll", data.buildDir);
1011 }
1012