root/branches/bud/sss/conf.d

Revision 248, 32.8 kB (checked in by Gregor, 2 years ago)

sss/build.d, sss/clean.d, sss/conf.d, sss/install.d, docs/README.software_engineers: Support for eval, set and add.

sss/build.d: Carry over buildflags in subdir builds.

Line 
1 /**
2  * DSSS configuration stuff
3  *
4  * Authors:
5  *  Gregor Richards
6  *
7  * License:
8  *  Copyright (c) 2006, 2007  Gregor Richards
9  * 
10  *  Permission is hereby granted, free of charge, to any person obtaining a
11  *  copy of this software and associated documentation files (the "Software"),
12  *  to deal in the Software without restriction, including without limitation
13  *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  *  and/or sell copies of the Software, and to permit persons to whom the
15  *  Software is furnished to do so, subject to the following conditions:
16  * 
17  *  The above copyright notice and this permission notice shall be included in
18  *  all copies or substantial portions of the Software.
19  * 
20  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  *  DEALINGS IN THE SOFTWARE.
27  */
28
29 module sss.conf;
30
31 import std.ctype;
32 import std.file;
33 import std.process;
34 import std.stdio;
35 import std.stream;
36 import std.string;
37
38 import std.c.stdlib;
39
40 import sss.clean;
41
42 import hcf.env;
43 import hcf.path;
44 import hcf.process;
45
46 import util.booltype;
47 import util.fdt;
48 import util.str;
49
50 version (Windows) {
51     import bcd.windows.windows;
52 }
53
54 alias std.string.find find;
55 alias std.string.iswhite iswhite;
56
57 /** The default config file name */
58 const char[] configFName = "dsss.conf";
59
60 /** The lastbuild config file name */
61 const char[] configLBName = "dsss.last";
62
63 /** The dsss_build line */
64 char[] dsss_build;
65
66 /** Options added to dsss_build */
67 char[] dsss_buildOptions;
68
69 /** Is DSSS installed, or is it being run out of the source directory? */
70 bool inSourceDir;
71
72 /** The prefix to which DSSS was installed */
73 char[] installPrefix;
74
75 /** The provided prefix */
76 char[] forcePrefix;
77
78 /** The prefix to which other binaries should be installed */
79 char[] binPrefix;
80
81 /** The prefix to which libraries are installed */
82 char[] libPrefix;
83
84 /** The prefix to which includes are installed */
85 char[] includePrefix;
86
87 /** The prefix to which manifests are installed */
88 char[] manifestPrefix;
89
90 /** The prefix to which configuration files are installed */
91 char[] etcPrefix;
92
93 /** The prefix to which the source list is downloaded */
94 char[] srcListPrefix;
95
96 /** The prefix for scratch work */
97 char[] scratchPrefix;
98
99 /** The location of stub.d (used to make stub D libraries) */
100 char[] stubDLoc;
101
102 /** The location of dsssdll.d (used to make DLLs from any library) */
103 char[] dsssDllLoc;
104
105 /** Usedirs (dirs to import both includes and libs from */
106 char[][] useDirs;
107    
108 /* It's often useful to know whether we're using GNU and/or Posix, as GNU on
109  * Windows tends to do some things Posixly. */
110 version (build) {
111     version (GNU) {
112         pragma(export_version, "GNU_or_Posix");
113     } else version (Posix) {
114         pragma(export_version, "GNU_or_Posix");
115     }
116 }
117
118 /** Set prefixes automatically, given argv[0] */
119 void getPrefix(char[] argvz)
120 {
121     char[] bname;
122     if (!whereAmI(argvz, installPrefix, bname)) {
123         writefln("Failed to determine DSSS' installed prefix.");
124         exit(1);
125     }
126    
127     installPrefix = canonPath(installPrefix);
128    
129     // set the prefix to actually install things to
130    
131     // using this directory, find include and library directories
132     if (exists(installPrefix ~ std.path.sep ~ "sss" ~ std.path.sep ~ "main.d")) {
133         // this is probably the build prefix
134         inSourceDir = true;
135        
136         if (forcePrefix == "") {
137             forcePrefix = installPrefix ~ std.path.sep ~ "inst";
138         } else {
139             forcePrefix = canonPath(forcePrefix);
140         }
141        
142         char[] sssBaseLoc = installPrefix ~ std.path.sep ~ "sss" ~ std.path.sep;
143         stubDLoc = sssBaseLoc ~ "stub.d";
144         dsssDllLoc = sssBaseLoc ~ "dssdll.d";
145        
146         // set build environment variable
147         version (Posix) {
148             dsss_build = installPrefix ~
149                 std.path.sep ~ "dsss_build" ~
150                 std.path.sep ~ "dsss_build";
151             setEnvVar("DSSS_BUILD", dsss_build);
152         } else version (Windows) {
153             dsss_build = installPrefix ~
154                 std.path.sep ~ "dsss_build" ~
155                 std.path.sep ~ "dsss_build.exe";
156             setEnvVar("DSSS_BUILD", dsss_build);
157         } else {
158             static assert(0);
159         }
160     } else {
161         inSourceDir = false;
162        
163         // slightly more complicated for a real install
164         if (forcePrefix == "") {
165             forcePrefix = getDirName(installPrefix);
166         }
167        
168         char[] sssBaseLoc = forcePrefix ~ std.path.sep ~
169             "include" ~ std.path.sep ~
170             "d" ~ std.path.sep ~
171             "sss" ~ std.path.sep;
172         stubDLoc = sssBaseLoc ~ "stub.d";
173         dsssDllLoc = sssBaseLoc ~ "dsssdll.d";
174        
175         // set build environment variable
176         version (Posix) {
177             dsss_build = installPrefix ~
178                  std.path.sep ~ "dsss_build";
179             setEnvVar("DSSS_BUILD", dsss_build);
180         } else version (Windows) {
181             dsss_build = installPrefix ~
182                 std.path.sep ~ "dsss_build.exe";
183             setEnvVar("DSSS_BUILD", dsss_build);
184         } else {
185             static assert(0);
186         }
187     }
188    
189     binPrefix = forcePrefix ~ std.path.sep ~ "bin";
190     libPrefix = forcePrefix ~ std.path.sep ~ "lib";
191     includePrefix = forcePrefix ~ std.path.sep ~
192         "include" ~ std.path.sep ~
193         "d";
194     manifestPrefix = forcePrefix ~ std.path.sep ~
195         "share" ~ std.path.sep ~
196         "dsss" ~ std.path.sep ~
197         "manifest";
198     etcPrefix = forcePrefix ~ std.path.sep ~
199         "etc";
200     srcListPrefix = canonPath(
201         installPrefix ~ std.path.sep ~
202         ".." ~ std.path.sep ~
203         "share" ~ std.path.sep ~
204         "dsss" ~ std.path.sep ~
205         "sources");
206    
207     // set the scratch prefix and some some environment variables
208     version (Posix) {
209         scratchPrefix = "/tmp";
210        
211         setEnvVar("DSSS", installPrefix ~ std.path.sep ~ bname);
212         setEnvVar("PREFIX", forcePrefix);
213         setEnvVar("BIN_PREFIX", binPrefix);
214         setEnvVar("LIB_PREFIX", libPrefix);
215         setEnvVar("INCLUDE_PREFIX", includePrefix);
216         setEnvVar("ETC_PREFIX", etcPrefix);
217         setEnvVar("EXE_EXT", "");
218        
219         // make sure components run with libraries, etc
220         setEnvVar("PATH", binPrefix ~ ":" ~ getEnvVar("PATH"));
221         char[] ldlibp = getEnvVar("LD_LIBRARY_PATH");
222         if (ldlibp == "") {
223             ldlibp = libPrefix;
224         } else {
225             ldlibp = libPrefix ~ ":" ~ ldlibp;
226         }
227         setEnvVar("LD_LIBRARY_PATH", ldlibp);
228     } else version (Windows) {
229         scratchPrefix = canonPath(installPrefix ~ std.path.sep ~
230                                   ".." ~ std.path.sep ~
231                                   "tmp");
232        
233         setEnvVar("DSSS", installPrefix ~ std.path.sep ~ bname);
234         setEnvVar("PREFIX", forcePrefix);
235         setEnvVar("BIN_PREFIX", binPrefix);
236         setEnvVar("LIB_PREFIX", libPrefix);
237         setEnvVar("INCLUDE_PREFIX", includePrefix);
238         setEnvVar("ETC_PREFIX", etcPrefix);
239         setEnvVar("EXE_EXT", ".exe");
240        
241         // path for both bin and lib
242         setEnvVar("PATH", binPrefix ~ ";" ~ libPrefix ~ ";" ~ getEnvVar("PATH"));
243     } else {
244         static assert(0);
245     }
246    
247     dsss_build ~= " -I" ~ includePrefix ~ " -LIBPATH=" ~ libPrefix ~ std.path.sep ~
248         " -LIBPATH=." ~ std.path.sep ~ " " ~
249         dsss_buildOptions ~ " ";
250 }
251
252 /** DSSS configuration information - simply a list of sections, then an array
253  * of settings for those sections */
254 class DSSSConf {
255     /// Configurable sections
256     char[][] sections;
257    
258     /// Settings per section
259     char[][char[]][char[]] settings;
260 }
261
262 /** Generate a DSSSConf from dsss.conf, or generate a dsss.conf from buildElems */
263 DSSSConf readConfig(char[][] buildElems, bool genconfig = false, char[] configF = configFName)
264 {
265     /* config file format: every line is precisely one section, setting, block
266      * opener or block closer. The only valid block opener is 'version' */
267    
268     /** A function to tokenize a single config file line */
269     char[][] tokLine(char[] line)
270     {
271         /** All tokens read thusfar */
272         char[][] tokens;
273        
274         /** Current token */
275         char[] tok;
276        
277         /** Add the current token */
278         void addToken()
279         {
280             if (tok != "") {
281                 tokens ~= tok;
282                 tok = "";
283             }
284         }
285        
286         for (int i = 0; i < line.length; i++) {
287             if (isalnum(line[i]) ||
288                 line[i] == '_') {
289                 tok ~= line[i..i+1];
290             } else if (iswhite(line[i])) {
291                 addToken();
292             } else if (line[i] == '=' ||
293                        line[i] == ':') {
294                 // the rest is all one token
295                 addToken();
296                 tok ~= line[i];
297                 addToken();
298                
299                 tok ~= line[(i + 1) .. $];
300                
301                 // trim whitespace of the setting
302                 while (tok.length && iswhite(tok[0])) tok = tok[1..$];
303                
304                 if (tok.length) addToken();
305                 break;
306             } else {
307                 addToken();
308                 tok ~= line[i..i+1];
309                 addToken();
310             }
311         }
312         addToken();
313        
314         return tokens;
315     }
316    
317     /// The actual configuration store
318     DSSSConf conf = new DSSSConf();
319    
320     /// The data from the config file
321     char[] confFile;
322    
323     if (exists(configFName)) {
324         if (genconfig) {
325             // this makes no sense
326             writefln("Will not generate a config file when a config file already exists.");
327             exit(1);
328         }
329        
330         // before reading the config, distclean if it's changed
331         if (configF == configFName) {
332             if (exists(configLBName)) {
333                 if (fileNewer(configFName, configLBName)) {
334                     // our config has changed
335                     distclean(readConfig(null, false, configLBName));
336                 }
337             }
338        
339             // copy in our new dsss.lastbuild
340             std.file.copy(configFName, configLBName);
341         }
342        
343         // Read the config file
344         confFile = cast(char[]) std.file.read(configFName);
345     } else {
346         // Generate the config file
347         if (buildElems.length == 0) {
348             // from nothing - just make every directory into a library
349             char[][] dires = listdir(".");
350             foreach (dire; dires) {
351                 if (isdir(dire) &&
352                     dire[0] != '.') {
353                     confFile ~= "[" ~ dire ~ "]\n";
354                 }
355             }
356            
357         } else {
358             // from a list
359             foreach (build; buildElems) {
360                 if (!exists(build)) {
361                     writefln("File %s not found!", build);
362                 } else {
363                     confFile ~= "[" ~ build ~ "]\n";
364                 }
365             }
366         }
367        
368         if (genconfig) {
369             // write it
370             std.file.write(configFName, confFile);
371         }
372        
373     }
374    
375    
376     // Normalize it
377     confFile = replace(confFile, "\r", "");
378        
379     // Split it by lines
380     char[][] lines = split(confFile, "\n");
381        
382     /// Current section
383     char[] section;
384        
385     /// Tested versions
386     bool[char[]] versions;
387    
388     // set up the defaults for the top-level section
389     conf.settings[""] = null;
390     conf.settings[""]["name"] = getBaseName(getcwd());
391     conf.settings[""]["version"] = "latest";
392    
393     // parse line-by-line
394     for (int i = 0; i < lines.length; i++) {
395         char[] line = lines[i];
396            
397         /** A function to close the current scope */
398         void closeScope(bool ignoreElse = false)
399         {
400             int depth = 1;
401             for (i++; i < lines.length; i++) {
402                 char[][] ntokens = tokLine(lines[i]);
403                 if (ntokens.length == 0) continue;
404                    
405                 // possibly change the depth
406                 if (ntokens[0] == "}") {
407                     // check for else
408                     if (ntokens.length >= 3 &&
409                         ntokens[1] == "else") {
410                         if (!ignoreElse) {
411                             // rewrite this line for later parsing
412                             lines[i] = std.string.join(ntokens[2 .. $], " ");
413                             depth--;
414                             i--;
415                         } else if (ntokens[$ - 1] != "{") {
416                             // drop the depth even though we won't reparse it
417                             depth--;
418                         }
419                        
420                     } else {
421                         depth--;
422                     }
423                    
424                     if (depth == 0) {
425                         return; // done! :)
426                     }
427                 } else if (ntokens[$ - 1] == "{") {
428                     depth++;
429                 }
430             }
431             // didn't close!
432             writefln("DSSS config error: unclosed scope.");
433             exit(1);
434         }
435        
436         // combine lines
437         while (i < lines.length - 1 &&
438                line.length &&
439                line[$ - 1] == '\\') {
440             i++;
441             line = line[0 .. ($ - 1)] ~ lines[i];
442         }
443        
444         // then parse it
445         char[][] tokens = tokLine(line);
446         if (tokens.length == 0) continue;
447        
448         // then do something with it
449         if (tokens[0] == "[" &&
450             tokens[$ - 1] == "]") {
451             // a section header
452             char[] path = std.string.join(tokens[1 .. ($ - 1)], "");
453             // allow \'s for badly-written conf files
454             path = std.string.replace(path, "\\", "/");
455            
456             section = canonPath(path);
457             conf.settings[section] = null;
458                
459             // need to have some default settings: target and type
460             if (section == "*") {
461                 // "global" section, no target/type
462             } else if (section == "") {
463                 // top-level section
464             } else if (section.length > 0 &&
465                        section[0] == '+') {
466                 // special section
467                 conf.sections ~= section;
468                 conf.settings[section]["type"] = "special";
469                 conf.settings[section]["target"] = section[1..$];
470                
471             } else if (!exists(section)) {
472                 writefln("WARNING: Section for nonexistant file %s.", section);
473             } else {
474                 conf.sections ~= section;
475                
476                 if (isdir(section)) {
477                     conf.settings[section]["type"] = "library";
478                    
479                     // target according to the library naming convention
480                     char[] pkg = std.string.replace(canonPath(section),
481                                                     "\\", "/");
482                    
483                     // LNC:
484                     // D<compiler>-<package-with-hyphens>
485                    
486                     // D
487                     char[] lname = "D";
488                    
489                     // <compiler>
490                     // FIXME: this should check with dsss_build
491                     version (GNU) {
492                         lname ~= "G";
493                     } else version (DigitalMars) {
494                         lname ~= "D";
495                     } else {
496                         static assert(0);
497                     }
498                     lname ~= "-";
499                        
500                     // <package>
501                     // swap out /'s
502                     pkg =
503                         std.string.replace(pkg, "/", "-");
504                     // name it
505                     conf.settings[section]["target"] =
506                         lname ~ pkg;
507                        
508                 } else {
509                     conf.settings[section]["type"] = "binary";
510                     conf.settings[section]["target"] = std.path.getName(section);
511                 }
512             }
513                
514             // FIXME: guarantee that sections aren't repeated
515                
516         } else if (tokens.length == 3 &&
517                    tokens[1] == ":") {
518             // a command
519             if (tokens[0] == "warn") {
520                 // a warning
521                 writefln("WARNING: %s", tokens[2]);
522             } else if (tokens[0] == "error") {
523                 // an error
524                 writefln("ERROR: %s", tokens[2]);
525             }
526            
527         } else if (tokens.length == 3 &&
528                    tokens[1] == "=") {
529             // a setting
530             conf.settings[section][std.string.tolower(tokens[0])] = expandEnvVars(tokens[2]);
531                
532         } else if (tokens.length == 1 &&
533                    isalnum(tokens[0][0])) {
534             // a setting with no value
535             conf.settings[section][std.string.tolower(tokens[0])] = "";
536            
537         } else if (tokens.length == 4 &&
538                    tokens[1] == "+" &&
539                    tokens[2] == "=") {
540             // append to a setting
541             char[] setting = std.string.tolower(tokens[0]);
542             if (setting in conf.settings[section]) {
543                 conf.settings[section][setting] ~= " " ~ expandEnvVars(tokens[3]);
544             } else {
545                 conf.settings[section][setting] = expandEnvVars(tokens[3]);
546             }
547                
548         } else if (tokens[0] == "version") {
549             /* a version statement, must be of one form:
550              *  * version(version) {
551              *  * version(!version) {
552              */
553            
554             if ((tokens.length != 5 ||
555                  tokens[1] != "(" ||
556                  tokens[3] != ")" ||
557                  tokens[4] != "{") &&
558                 (tokens.length != 6 ||
559                  tokens[1] != "(" ||
560                  tokens[2] != "!" ||
561                  tokens[4] != ")" ||
562                  tokens[5] != "{")) {
563                 writefln("DSSS config error: malformed version line.");
564                 exit(1);
565             }
566            
567             // whether the comparison is valid
568             bool valid = false;
569             char[] vertok;
570             if (tokens[2] == "!") {
571                 // assume valid
572                 valid = true;
573                 vertok = tokens[3];
574             } else {
575                 vertok = tokens[2];
576             }
577            
578             if (!(vertok in versions)) {
579                 /* now check if this version is defined by making a .d file and
580                &n