root/trunk/sss/conf.d

Revision 929, 40.7 kB (checked in by Gregor, 3 months ago)

sss/conf.d: defaulttargets with / now work properly on Windows.

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 import sss.platform;
42 import sss.system;
43
44 import hcf.env;
45 import hcf.path;
46 import hcf.process;
47
48 import util.booltype;
49 import util.fdt;
50 import util.str;
51
52 version (Windows) {
53     import bcd.windows.windows;
54 }
55
56 alias std.process.system system;
57 alias std.string.find find;
58 alias std.string.iswhite iswhite;
59
60 extern (C) int access(char*, int);
61 enum accessRights : int {
62         F_OK = 0,
63         X_OK,
64         W_OK,
65         R_OK
66 };
67
68 /** The default config file name */
69 const char[] configFName = "dsss.conf";
70
71 /** The lastbuild config file name */
72 const char[] configLBName = "dsss.last";
73
74 /** The dsss_build line */
75 char[] dsss_build;
76
77 /** Options added to dsss_build */
78 char[] dsss_buildOptions;
79
80 /** Is DSSS installed, or is it being run out of the source directory? */
81 bool inSourceDir;
82
83 /** The prefix to which DSSS was installed */
84 char[] installPrefix;
85
86 /** The provided prefix */
87 char[] forcePrefix;
88
89 /** The prefix to which other binaries should be installed */
90 char[] binPrefix;
91
92 /** The prefix to which libraries are installed */
93 char[] libPrefix;
94
95 /** The prefix to which includes are installed */
96 char[] includePrefix;
97
98 /** The prefix to which documentation is installed */
99 char[] docPrefix;
100
101 /** The prefix to which manifests are installed */
102 char[] manifestPrefix;
103
104 /** The prefix to which configuration files are installed */
105 char[] etcPrefix;
106
107 /** The prefix to which the source list is downloaded */
108 char[] srcListPrefix;
109
110 /** The location of candydoc.tar.gz */
111 char[] candyDocPrefix;
112
113 /** The location of dsss_lib_test.d */
114 char[] dsssLibTestDPrefix;
115
116 /** Are we doing documentation? */
117 bool doDocs = false;
118
119 /** Are we doing documentation for binaries? */
120 bool doDocBinaries = false;
121
122 /** Should we delete response files? */
123 bool deleteRFiles = true;
124
125 /** Should we generate test binaries? */
126 bool testLibs = false;
127
128 /** Should we build debug versions? */
129 bool buildDebug = false;
130
131 /** Should we be verbose? */
132 bool verboseMode = false;
133
134 /** The prefix for scratch work */
135 char[] scratchPrefix;
136
137 /** The location of stub.d (used to make stub D libraries) */
138 char[] stubDLoc;
139
140 /** The location of dsssdll.d (used to make DLLs from any library) */
141 char[] dsssDllLoc;
142
143 /** Usedirs (dirs to import both includes and libs from */
144 char[][] useDirs;
145
146 /** Tested versions (for the target) */
147 bool[char[]] versions;
148    
149 /* It's often useful to know whether we're using GNU and/or Posix, as GNU on
150  * Windows tends to do some things Posixly. */
151 version (build) {
152     version (GNU) {
153         pragma(export_version, "GNU_or_Posix");
154     } else version (Posix) {
155         pragma(export_version, "GNU_or_Posix");
156     }
157 }
158
159 /** Set prefixes automatically, given argv[0] */
160 void getPrefix(char[] argvz)
161 {
162     char[] bname;
163     if (!whereAmI(argvz, installPrefix, bname)) {
164         writefln("Failed to determine DSSS' installed prefix.");
165         exit(1);
166     }
167    
168     installPrefix = canonPath(installPrefix);
169    
170     // get some default usedirs
171     useDirs ~= canonPath(installPrefix ~ std.path.sep ~ "..");
172     version (Posix) {
173         char[] home = getEnvVar("HOME");
174         if (home != "") {
175             useDirs ~= canonPath(home ~ std.path.sep ~ "d");
176         }
177     }
178    
179     // set the prefix to actually install things to
180    
181     // using this directory, find include and library directories
182     if (exists(installPrefix ~ std.path.sep ~ "sss" ~ std.path.sep ~ "main.d")) {
183         // this is probably the build prefix
184         inSourceDir = true;
185        
186         if (forcePrefix == "") {
187             forcePrefix = installPrefix ~ std.path.sep ~ "inst";
188         } else {
189             forcePrefix = canonPath(forcePrefix);
190         }
191        
192         char[] sssBaseLoc = installPrefix ~ std.path.sep ~ "sss" ~ std.path.sep;
193         stubDLoc = sssBaseLoc ~ "stub.d";
194         dsssDllLoc = sssBaseLoc ~ "dssdll.d";
195        
196         // get rebuild environment variable
197         version (Posix) {
198             dsss_build = installPrefix ~
199                 std.path.sep ~ "rebuild" ~
200                 std.path.sep ~ "rebuild";
201         } else version (Windows) {
202             dsss_build = installPrefix ~
203                 std.path.sep ~ "rebuild" ~
204                 std.path.sep ~ "rebuild.exe";
205         } else {
206             static assert(0);
207         }
208        
209         setEnvVar("DSSS_BUILD", dsss_build);
210        
211         if (!candyDocPrefix.length)
212             candyDocPrefix = canonPath(
213                 installPrefix ~ std.path.sep ~
214                 "candydoc.tar.gz");
215         if (!dsssLibTestDPrefix.length)
216             dsssLibTestDPrefix = canonPath(
217                 installPrefix ~ std.path.sep ~
218                 "sss" ~ std.path.sep ~
219                 "dsss_lib_test.d");
220     } else {
221         inSourceDir = false;
222        
223         // slightly more complicated for a real install
224         if (forcePrefix == "") {
225             forcePrefix = getDirName(installPrefix);
226            
227             // if this is inaccessible, we need to make a better decision
228             version (Posix) {
229                 if (access((forcePrefix ~ '\0').ptr, accessRights.W_OK) != 0) {
230                     // choose $HOME/d
231                     if (home != "") {
232                         char[] newPrefix = canonPath(home ~ "/d");
233                         writefln("Default prefix %s is unwritable, using %s instead.",
234                                  forcePrefix, newPrefix);
235                         forcePrefix = newPrefix;
236                     }
237                 }
238             }
239            
240         } else {
241             forcePrefix = canonPath(forcePrefix);
242         }
243        
244         char[] sssBaseLoc = forcePrefix ~ std.path.sep ~
245             "include" ~ std.path.sep ~
246             "d" ~ std.path.sep ~
247             "sss" ~ std.path.sep;
248         stubDLoc = sssBaseLoc ~ "stub.d";
249         dsssDllLoc = sssBaseLoc ~ "dsssdll.d";
250        
251         // set build environment variable
252         version (Posix) {
253             dsss_build = installPrefix ~
254                  std.path.sep ~ "rebuild";
255         } else version (Windows) {
256             dsss_build = installPrefix ~
257                 std.path.sep ~ "rebuild.exe";
258         } else {
259             static assert(0);
260         }
261        
262         // if we don't have rebuild next to us, try to use it without a path
263         if (!std.file.exists(dsss_build)) {
264             version (Posix) {
265                 dsss_build = "rebuild";
266             } else version (Windows) {
267                 dsss_build = "rebuild.exe";
268             } else {
269                 static assert(0);
270             }
271         }
272        
273         setEnvVar("DSSS_BUILD", dsss_build);
274        
275         if (!candyDocPrefix.length)
276             candyDocPrefix = canonPath(
277                 installPrefix ~ std.path.sep ~
278                 ".." ~ std.path.sep ~
279                 "share" ~ std.path.sep ~
280                 "dsss" ~ std.path.sep ~
281                 "candydoc.tar.gz");
282         if (!dsssLibTestDPrefix.length)
283             dsssLibTestDPrefix = canonPath(
284                 installPrefix ~ std.path.sep ~
285                 ".." ~ std.path.sep ~
286                 "share" ~ std.path.sep ~
287                 "dsss" ~ std.path.sep ~
288                 "dsss_lib_test.d");
289     }
290    
291     if (!binPrefix.length)
292         binPrefix = forcePrefix ~ std.path.sep ~ "bin";
293     if (!libPrefix.length)
294         libPrefix = forcePrefix ~ std.path.sep ~ "lib";
295     if (!includePrefix.length)
296         includePrefix = forcePrefix ~ std.path.sep ~
297              "include" ~ std.path.sep ~
298              "d";
299     if (!docPrefix.length)
300         docPrefix = forcePrefix ~ std.path.sep ~
301             "share" ~ std.path.sep ~
302             "doc";
303     if (!manifestPrefix.length)
304         manifestPrefix = forcePrefix ~ std.path.sep ~
305             "share" ~ std.path.sep ~
306             "dsss" ~ std.path.sep ~
307             "manifest";
308     if (!etcPrefix.length) {
309         if (forcePrefix == "/usr") {
310             // in the case of /usr, use /etc instead of /usr/etc
311             etcPrefix = "/etc";
312         } else {
313             etcPrefix = forcePrefix ~ std.path.sep ~
314                 "etc";
315         }
316     }
317     if (!srcListPrefix.length)
318         srcListPrefix = canonPath(
319             forcePrefix ~ std.path.sep ~
320             "share" ~ std.path.sep ~
321             "dsss" ~ std.path.sep ~
322             "sources");
323    
324     // set the scratch prefix and some some environment variables
325     version (Posix) {
326         scratchPrefix = "/tmp";
327        
328         setEnvVar("DSSS", installPrefix ~ std.path.sep ~ bname);
329         setEnvVar("PREFIX", forcePrefix);
330         setEnvVar("BIN_PREFIX", binPrefix);
331         setEnvVar("LIB_PREFIX", libPrefix);
332         setEnvVar("INCLUDE_PREFIX", includePrefix);
333         setEnvVar("DOC_PREFIX", docPrefix);
334         setEnvVar("ETC_PREFIX", etcPrefix);
335         setEnvVar("EXE_EXT", "");
336        
337         // make sure components run with libraries, etc
338         setEnvVar("PATH", binPrefix ~ ":" ~ getEnvVar("PATH"));
339         char[] ldlibp = getEnvVar("LD_LIBRARY_PATH");
340         if (ldlibp == "") {
341             ldlibp = libPrefix;
342         } else {
343             ldlibp = libPrefix ~ ":" ~ ldlibp;
344         }
345         setEnvVar("LD_LIBRARY_PATH", ldlibp);
346     } else version (Windows) {
347         scratchPrefix = canonPath(installPrefix ~ std.path.sep ~
348                                   ".." ~ std.path.sep ~
349                                   "tmp");
350        
351         setEnvVar("DSSS", installPrefix ~ std.path.sep ~ bname);
352         setEnvVar("PREFIX", forcePrefix);
353         setEnvVar("BIN_PREFIX", binPrefix);
354         setEnvVar("LIB_PREFIX", libPrefix);
355         setEnvVar("INCLUDE_PREFIX", includePrefix);
356         setEnvVar("DOC_PREFIX", docPrefix);
357         setEnvVar("ETC_PREFIX", etcPrefix);
358         setEnvVar("EXE_EXT", ".exe");
359        
360         // path for both bin and lib
361         setEnvVar("PATH", binPrefix ~ ";" ~ libPrefix ~ ";" ~ getEnvVar("PATH"));
362     } else {
363         static assert(0);
364     }
365    
366     dsss_build ~= " " ~ dsss_buildOptions ~
367         " -Idsss_imports" ~ std.path.sep ~
368         " -I. -S." ~ std.path.sep ~
369         " -I" ~ includePrefix ~ " -S" ~ libPrefix ~ std.path.sep ~ " ";
370 }
371
372 /** DSSS configuration information - simply a list of sections, then an array
373  * of settings for those sections */
374 class DSSSConf {
375     /// Configurable sections
376     char[][] sections;
377    
378     /// Settings per section
379     char[][char[]][char[]] settings;
380 }
381
382 /** Generate a DSSSConf from dsss.conf, or generate a dsss.conf from buildElems */
383 DSSSConf readConfig(char[][] buildElems, bool genconfig = false, char[] configF = configFName)
384 {
385     /* config file format: every line is precisely one section, setting, block
386      * opener or block closer. The only valid block opener is 'version' */
387    
388     /** A function to tokenize a single config file line */
389     char[][] tokLine(char[] line)
390     {
391         /** All tokens read thusfar */
392         char[][] tokens;
393        
394         /** Current token */
395         char[] tok;
396        
397         /** Add the current token */
398         void addToken()
399         {
400             if (tok != "") {
401                 tokens ~= tok;
402                 tok = "";
403             }
404         }
405        
406         for (int i = 0; i < line.length; i++) {
407             if (isalnum(line[i]) ||
408                 line[i] == '_') {
409                 tok ~= line[i..i+1];
410             } else if (iswhite(line[i])) {
411                 addToken();
412             } else if (line[i] == '=' ||
413                        line[i] == ':') {
414                 // the rest is all one token
415                 addToken();
416                 tok ~= line[i];
417                 addToken();
418                
419                 tok ~= line[(i + 1) .. $];
420                
421                 // trim whitespace of the setting
422                 while (tok.length && iswhite(tok[0])) tok = tok[1..$];
423                 while (tok.length && iswhite(tok[$-1])) tok = tok[0..($-1)];
424                
425                 if (tok.length) addToken();
426                 break;
427             } else {
428                 addToken();
429                 tok ~= line[i..i+1];
430                 addToken();
431             }
432         }
433         addToken();
434        
435         return tokens;
436     }
437    
438     /// The actual configuration store
439     DSSSConf conf = new DSSSConf();
440    
441     /// The data from the config file
442     char[] confFile;
443    
444     if (exists(configFName)) {
445         if (genconfig) {
446             // this makes no sense
447             writefln("Will not generate a config file when a config file already exists.");
448             exit(1);
449         }
450        
451         // before reading the config, distclean if it's changed
452         if (configF == configFName) {
453             if (exists(configLBName)) {
454                 if (fileNewer(configFName, configLBName)) {
455                     // our config has changed
456                     distclean(readConfig(null, false, configLBName));
457                 }
458             }
459        
460             // copy in our new dsss.lastbuild
461             std.file.copy(configFName, configLBName);
462         }
463        
464         // Read the config file
465         confFile = cast(char[]) std.file.read(configFName);
466     } else {
467         if (!genconfig && buildElems.length == 0) {
468             // this makes no sense
469             writefln("No config file found and no targets explicitly specified.");
470             exit(1);
471         }
472
473         // Generate the config file
474         if (buildElems.length == 0) {
475             // from nothing - just make every directory into a library
476             char[][] dires = listdir(".");
477             foreach (dire; dires) {
478                 if (isdir(dire) &&
479                     dire[0] != '.') {
480                     confFile ~= "[" ~ dire ~ "]\n";
481                 }
482             }
483            
484         } else {
485             // from a list
486             foreach (build; buildElems) {
487                 if (!exists(build)) {
488                     writefln("File %s not found!", build);
489                 } else {
490                     confFile ~= "[" ~ build ~ "]\n";
491                 }
492             }
493         }
494        
495         if (genconfig) {
496             // write it
497             std.file.write(configFName, confFile);
498         }
499        
500     }
501    
502    
503     // Normalize it
504     confFile = replace(confFile, "\r", "");
505    
506     // Split it by lines
507     char[][] lines = split(confFile, "\n");
508    
509     /// Current section
510     char[] section;
511    
512     // set up the defaults for the top-level section
513     conf.settings[""] = null;
514     conf.settings[""]["name"] = getBaseName(getcwd());
515     conf.settings[""]["version"] = "latest";
516    
517     // parse line-by-line
518     for (int i = 0; i < lines.length; i++) {
519         char[] line = lines[i];
520            
521         /** A function to close the current scope */
522         void closeScope(bool ignoreElse = false)
523         {
524             int depth = 1;
525             for (i++; i < lines.length; i++) {
526                 char[][] ntokens = tokLine(lines[i]);
527                 if (ntokens.length == 0) continue;
528                    
529                 // possibly change the depth
530                 if (ntokens[0] == "}") {
531                     // check for else
532                     if (ntokens.length >= 3 &&
533                         ntokens[1] == "else") {
534                         if (!ignoreElse) {
535                             // rewrite this line for later parsing
536                             lines[i] = std.string.join(ntokens[2 .. $], " ");
537                             depth--;
538                             i--;
539                         } else if (ntokens[$ - 1] != "{") {
540                             // drop the depth even though we won't reparse it
541                             depth--;
542                         }
543                        
544                     } else {
545                         depth--;
546                     }
547                    
548                     if (depth == 0) {
549                         return; // done! :)
550                     }
551                 } else if (ntokens[$ - 1] == "{") {
552                     depth++;
553                 }
554             }
555             // didn't close!
556             writefln("DSSS config error: unclosed scope.");
557             exit(1);
558         }
559        
560         // combine lines
561         while (i < lines.length - 1 &&
562                line.length &&
563                line[$ - 1] == '\\') {
564             i++;
565             line = line[0 .. ($ - 1)] ~ lines[i];
566         }
567        
568         // then parse it
569         char[][] tokens = tokLine(line);
570         if (tokens.length == 0) continue;
571        
572         // then do something with it
573         if (tokens[0] == "[" &&
574             tokens[$ - 1] == "]") {
575             // a section header
576             char[] path = std.string.join(tokens[1 .. ($ - 1)], "");
577             // allow \'s for badly-written conf files
578             path = std.string.replace(path, "\\", "/");
579            
580             section = canonPath(path);
581             conf.settings[section] = null;
582                
583             // need to have some default settings: target and type
584             if (section == "*") {
585                 // "global" section, no target/type
586             } else if (section == "") {
587                 // top-level section
588             } else if (section.length > 0 &&
589                        section[0] == '+') {
590                 // special section
591                 conf.sections ~= section;
592                 conf.settings[section]["type"] = "special";
593                 conf.settings[section]["target"] = section[1..$];
594
595             } else if (find(section, '+') != -1) {
596                 int ploc = find(section, '+'); // FIXME
597                 // auxiliary section
598                 conf.sections ~= section;
599                 conf.settings[section]["type"] = "binary";
600                 conf.settings[section]["target"] = section[1..$];
601                
6