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