root/trunk/tools/rdmd.d

Revision 1126, 14.2 kB (checked in by andrei, 2 months ago)

Eliminated std.openrj

  • Property svn:executable set to *
Line 
1 // Written in the D programming language.
2
3 import std.date, std.getopt, std.string, std.process, std.stdio,
4     std.contracts, std.file,
5     std.algorithm, std.iterator, std.md5, std.path, std.regexp, std.getopt,
6     std.c.stdlib, std.process;
7
8 private bool chatty, buildOnly, dryRun, force;
9 private string exe, compiler = "dmd";
10
11 int main(string[] args)
12 {
13     //writeln("Invoked with: ", map!(q{a ~ ", "})(args));
14     if (args.length > 1 && std.algorithm.startsWith(args[1],
15                     "--shebang ", "--shebang="))
16     {
17         // multiple options wrapped in one
18         auto a = args[1]["--shebang ".length .. $];
19         args = args[0 .. 1] ~ split(a) ~ args[2 .. $];
20     }
21    
22     // Continue parsing the command line; now get rdmd's own arguments
23     // parse the -o option
24     void dashOh(string key, string value)
25     {
26         if (value[0] == 'f')
27         {
28             // -ofmyfile passed
29             exe = value[1 .. $];
30         }
31         else if (value[0] == 'd')
32         {
33             // -odmydir passed
34             // add a trailing path separator to clarify it's a dir
35             exe = std.path.join(value[1 .. $], "");
36             assert(std.string.endsWith(exe, std.path.sep));
37         }
38         else if (value[0] == '-')
39         {
40             // -o- passed
41             enforce(false, "Option -o- currently not supported by rdmd");
42         }
43         else
44         {
45             enforce(false, "Unrecognized option: "~key~value);
46         }
47     }
48    
49     // start the web browser on documentation page
50     void man()
51     {
52         foreach (b; [ std.process.getenv("BROWSER"), "firefox",
53                         "sensible-browser", "x-www-browser" ]) {
54             if (!b.length) continue;
55             if (!system(b~" http://www.digitalmars.com/d/2.0/rdmd.html"))
56                 return;
57         }
58     }
59
60     bool bailout;    // bailout set by functions called in getopt if
61                      // program should exit
62     bool loop;       // set by --loop
63     bool addStubMain;// set by --main
64     string[] eval;     // set by --eval
65     getopt(args,
66             std.getopt.config.caseSensitive,
67             std.getopt.config.passThrough,
68             std.getopt.config.stopOnFirstNonOption,
69             "build-only", &buildOnly,
70             "chatty", &chatty,
71             "dry-run", &dryRun,
72             "force", &force,
73             "help", (string) { writeln(helpString); bailout = true; },
74             "main", &addStubMain,
75             "man", (string) { man; bailout = true; },
76             "eval", &eval,
77             "loop", &loop,
78             "o", &dashOh,
79             "compiler", &compiler);
80     if (bailout) return 0;
81     if (dryRun) chatty = true; // dry-run implies chatty
82
83     if (eval)
84     {
85         // Just evaluate this program!
86         if (loop)
87         {
88             return .eval(importWorld ~ "void main(char[][] args) { "
89                 ~ "foreach (line; stdin.byLine()) {\n" ~ join(eval, "\n")
90                     ~ ";\n} }");
91         }
92         else
93         {
94             return .eval(importWorld ~ "void main(char[][] args) {\n"
95                     ~ join(eval, "\n") ~ ";\n}");
96         }
97     }
98    
99     // Parse the program line - first find the program to run
100     uint programPos = 1;
101     for (;; ++programPos)
102     {
103         if (programPos == args.length)
104         {
105             write(helpString);
106             return 1;
107         }
108         if (args[programPos].length && args[programPos][0] != '-') break;
109     }
110     const
111         root = /*rel2abs*/(chomp(args[programPos], ".d") ~ ".d"),
112         exeBasename = basename(root, ".d"),
113         programArgs = args[programPos + 1 .. $];
114     args = args[0 .. programPos];
115     const compilerFlags = args[1 .. programPos];
116
117     // Compute the object directory and ensure it exists
118     invariant objDir = getObjPath(root, compilerFlags);
119     if (!dryRun)        // only make a fuss about objDir on a real run
120     {
121         exists(objDir)
122             ? enforce(isdir(objDir),
123                     "Entry `"~objDir~"' exists but is not a directory.")
124             : mkdir(objDir);
125     }
126    
127     // Fetch dependencies
128     const myModules = getDependencies(root, objDir, compilerFlags);
129
130     // Compute executable name, check for freshness, rebuild
131     if (exe)
132     {
133         // user-specified exe name
134         if (std.string.endsWith(exe, std.path.sep))
135         {
136             // user specified a directory, complete it to a file
137             exe = std.path.join(exe, exeBasename);
138         }
139     }
140     else
141     {
142         //exe = exeBasename ~ '.' ~ hash(root, compilerFlags);
143         version (Posix)
144             exe = join(myOwnTmpDir, rel2abs(root)[1 .. $])
145                 ~ '.' ~ hash(root, compilerFlags);
146         else version (Windows)
147             exe = join(myOwnTmpDir, root)
148                 ~ '.' ~ hash(root, compilerFlags);
149         else
150             assert(0);
151     }
152
153     // Have at it
154     if (isNewer(root, exe) ||
155             find!((string a) {return isNewer(a, exe);})(myModules.keys).length)
156     {
157         invariant result = rebuild(root, exe, objDir, myModules, compilerFlags,
158                                    addStubMain);
159         if (result) return result;
160     }
161
162     // run
163     return buildOnly ? 0 : execv(exe, [ exe ] ~ programArgs);
164 }
165
166 bool inALibrary(in string source, in string object)
167 {
168     // Heuristics: if source starts with "std.", it's in a library
169     return std.string.startsWith(source, "std.")
170         || std.string.startsWith(source, "core.")
171         || source == "object" || source == "gcstats";
172     // another crude heuristic: if a module's path is absolute, it's
173     // considered to be compiled in a separate library. Otherwise,
174     // it's a source module.
175     //return isabs(mod);
176 }
177
178 private string myOwnTmpDir()
179 {
180     version (linux)
181     {
182         enum tmpRoot = "/tmp/.rdmd";
183     }
184     else version (Windows)
185     {
186         auto tmpRoot = std.process.getenv("TEMP");
187         if (!tmpRoot)
188         {
189             tmpRoot = std.process.getenv("TMP");
190         }
191         if (!tmpRoot) tmpRoot = "./.rdmd";
192         else tmpRoot ~= "/.rdmd";
193     }
194     exists(tmpRoot) && isdir(tmpRoot) || mkdirRecurse(tmpRoot);
195     return tmpRoot;
196 }
197
198 private string hash(in string root, in string[] compilerFlags)
199 {
200     enum string[] irrelevantSwitches = [
201         "--help", "-ignore", "-quiet", "-v" ];
202     MD5_CTX context;
203     context.start();
204     context.update(getcwd);
205     context.update(root);
206     foreach (flag; compilerFlags) {
207         if (find(irrelevantSwitches, flag).length) continue;
208         context.update(flag);
209     }
210     ubyte digest[16];
211     context.finish(digest);
212     return digestToString(digest);
213 }
214
215 private string getObjPath(in string root, in string[] compilerFlags)
216 {
217     const tmpRoot = myOwnTmpDir;
218     return std.path.join(tmpRoot,
219             "rdmd-" ~ basename(root) ~ '-' ~ hash(root, compilerFlags));
220 }
221
222 // Rebuild the executable fullExe starting from modules myModules
223 // passing the compiler flags compilerFlags. Generates one large
224 // object file.
225
226 private int rebuild(string root, string fullExe,
227         string objDir, in string[string] myModules,
228         in string[] compilerFlags, bool addStubMain)
229 {
230     auto todo = compiler~" "~join(compilerFlags, " ")
231         ~" -of"~shellQuote(fullExe)
232         ~" -od"~shellQuote(objDir)
233         ~" "~shellQuote(root)~" ";
234     foreach (k; map!(shellQuote)(myModules.keys)) {
235         todo ~= k ~ " ";
236     }
237
238     // Need to add the pesky void main(){}?
239     if (addStubMain)
240     {
241         auto stubMain = std.path.join(myOwnTmpDir, "stubmain.d");
242         std.file.write(stubMain, "void main(){}");
243         todo ~= stubMain;
244     }
245    
246     invariant result = run(todo);
247     if (result)
248     {
249         // build failed
250         return result;
251     }
252     // clean up the object file, not needed anymore
253     //remove(std.path.join(objDir, basename(root, ".d")~".o"));
254     // clean up the dir containing the object file
255     rmdirRecurse(objDir);
256     return 0;
257 }
258
259 // Run a program optionally writing the command line first
260
261 private int run(string todo)
262 {
263     if (chatty) writeln(todo);
264     if (dryRun) return 0;
265     return system(todo);
266 }
267
268 // Given module rootModule, returns a mapping of all dependees .d
269 // source filenames to their corresponding .o files sitting in
270 // directory objDir. The mapping is obtained by running dmd -v against
271 // rootModule.
272
273 private string[string] getDependencies(string rootModule, string objDir,
274         in string[] compilerFlags)
275 {
276     string d2obj(string dfile) {
277         return std.path.join(objDir, chomp(basename(dfile), ".d")~".o");
278     }
279    
280     // myModules maps module source paths to corresponding .o names
281     string[string] myModules;// = [ rootModule : d2obj(rootModule) ];
282     // Must collect dependencies
283     invariant depsGetter = compiler~" "~join(compilerFlags, " ")
284         ~" -v -o- "~shellQuote(rootModule);
285     if (chatty) writeln(depsGetter);
286     File depsReader;
287     depsReader.popen(depsGetter);
288     scope(exit) collectException(depsReader.close); // we don't care for errors
289
290     // Fetch all dependent modules and append them to myModules
291     auto pattern = new RegExp(r"^import\s+(\S+)\s+\((\S+)\)\s*$");
292     foreach (string line; lines(depsReader))
293     {
294         if (!pattern.test(line)) continue;
295         invariant moduleName = pattern[1], moduleSrc = pattern[2];
296         if (inALibrary(moduleName, moduleSrc)) continue;
297         invariant moduleObj = d2obj(moduleSrc);
298         myModules[/*rel2abs*/(moduleSrc)] = moduleObj;
299     }
300
301     return myModules;
302 }
303
304 /*private*/ string shellQuote(string filename)
305 {
306     // This may have to change under windows
307     version (Windows) enum quotechar = '"';
308     else enum quotechar = '\'';
309     return quotechar ~ filename ~ quotechar;
310 }
311
312 private bool isNewer(string source, string target)
313 {
314     return force || lastModified(source) >= lastModified(target, d_time.min);
315 }
316
317 private string helpString()
318 {
319     return
320 "rdmd build "~thisVersion~"
321 Usage: rdmd [RDMD AND DMD OPTIONS]... program [PROGRAM OPTIONS]...
322 Builds (with dependents) and runs a D program.
323 Example: rdmd -release myprog --myprogparm 5
324
325 Any option to be passed to dmd must occur before the program name. In addition
326 to dmd options, rdmd recognizes the following options:
327   --build-only      just build the executable, don't run it
328   --chatty          write dmd commands to stdout before executing them
329   --compiler=comp   use the specified compiler (e.g. gdmd) instead of dmd
330   --dry-run         do not compile, just show what commands would be run
331                       (implies --chatty)
332   --eval=code       evaluate code \u00E0 la perl -e (multiple --eval allowed)
333   --force           force a rebuild even if apparently not necessary
334   --help            this message
335   --loop            assume \"foreach (line; stdin.byLine()) { ... }\" for eval
336   --main            add a stub main program to the mix (e.g. for unittesting)
337   --man             open web browser on manual page
338   --shebang         rdmd is in a shebang line (put as first argument)
339 ";
340 }
341
342 // For --eval
343 immutable string importWorld = "
344 module temporary;
345 import std.stdio, std.algorithm, std.array, std.atomics, std.base64,
346     std.bigint, /*std.bind, std.bitarray,*/ std.bitmanip, std.boxer,
347     std.compiler, std.complex, std.contracts, std.conv, std.cpuid, std.cstream,
348     std.ctype, std.date, std.dateparse, std.demangle, std.encoding, std.file,
349     std.format, std.functional, std.getopt, std.intrinsic, std.iterator,
350     /*std.loader,*/ std.math, std.md5, std.metastrings, std.mmfile,
351     std.numeric, std.outbuffer, std.path, std.perf, std.process,
352     std.random, std.range, std.regex, std.regexp, std.signals, std.socket,
353     std.socketstream, std.stdint, std.stdio, std.stdiobase, std.stream,
354     std.string, std.syserror, std.system, std.traits, std.typecons,
355     std.typetuple, std.uni, std.uri, std.utf, std.variant, std.xml, std.zip,
356     std.zlib;
357 ";
358
359 int eval(string todo)
360 {
361     MD5_CTX context;
362     context.start();
363     context.update(todo);
364     ubyte digest[16];
365     context.finish(digest);
366     auto pathname = myOwnTmpDir;
367     auto progname = std.path.join(pathname,
368             "eval." ~ digestToString(digest));
369
370     if (exists(progname) ||
371             // Compile it
372             (std.file.write(progname~".d", todo),
373                     run("dmd " ~ progname ~ ".d -of" ~ progname) == 0))
374     {
375         // It's there, just run it
376         run(progname);
377     }
378
379     // Clean pathname
380     enum lifetimeInHours = 24;
381     auto cutoff = getUTCtime - 60 * 60 * lifetimeInHours * ticksPerSecond;
382     foreach (DirEntry d; dirEntries(pathname, SpanMode.shallow))
383     {
384         if (d.lastWriteTime < cutoff)
385         {
386             std.file.remove(d.name);
387             //break; // only one per call so we don't waste time
388         }
389     }
390    
391     return 0;
392 }
393
394 string thisVersion()
395 {
396     enum d = __DATE__;
397     enum month = d[0 .. 3],
398         day = d[4] == ' ' ? "0"~d[5] : d[4 .. 6],
399         year = d[7 .. $];
400     enum monthNum
401         = month == "Jan" ? "01"
402         : month == "Feb" ? "02"
403         : month == "Mar" ? "03"
404         : month == "Apr" ? "04"
405         : month == "May" ? "05"
406         : month == "Jun" ? "06"
407         : month == "Jul" ? "07"
408         : month == "Aug" ? "08"
409         : month == "Sep" ? "09"
410         : month == "Oct" ? "10"
411         : month == "Nov" ? "11"
412         : month == "Dec" ? "12"
413         : "";
414     static assert(month != "", "Unknown month "~month);
415     return year[0]~year[1 .. $]~monthNum~day;
416 }
417
418 /*
419  *  Copyright (C) 2008 by Andrei Alexandrescu
420  *  Written by Andrei Alexandrescu, www.erdani.org
421  *  Based on an idea by Georg Wrede
422  *  Featuring improvements suggested by Christopher Wright
423  *
424  *  This software is provided 'as-is', without any express or implied
425  *  warranty. In no event will the authors be held liable for any damages
426  *  arising from the use of this software.
427  *
428  *  Permission is granted to anyone to use this software for any purpose,
429  *  including commercial applications, and to alter it and redistribute it
430  *  freely, subject to the following restrictions:
431  *
432  *  o  The origin of this software must not be misrepresented; you must not
433  *     claim that you wrote the original software. If you use this software
434  *     in a product, an acknowledgment in the product documentation would be
435  *     appreciated but is not required.
436  *  o  Altered source versions must be plainly marked as such, and must not
437  *     be misrepresented as being the original software.
438  *  o  This notice may not be removed or altered from any source
439  *     distribution.
440  */
Note: See TracBrowser for help on using the browser.