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