root/branches/tango/sss/conf.d

Revision 796, 41.1 kB (checked in by Gregor, 1 year ago)

MERGE: trunk r795

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