ignore unknown pragmas by default; no gc-sections by default
[rgdc.git] / rgdc.d
blob9a273328818cea1237af7626d49c48b73c80530a
1 /*
2 * Copyright (C) 2008 by Andrei Alexandrescu
3 * Written by Andrei Alexandrescu, www.erdani.org
4 * Based on an idea by Georg Wrede
5 * Featuring improvements suggested by Christopher Wright
6 * Windows port using bug fixes and suggestions by Adam Ruppe
8 * Code uglification: Ketmar // Invisible Vector
10 * Distributed under the Boost Software License, Version 1.0.
11 * (See accompanying file LICENSE_1_0.txt or copy at
12 * http://www.boost.org/LICENSE_1_0.txt)
14 // -fversion=no_pragma_lib to disable non-standard -fwrite-pragma-lib arg
16 import
17 std.c.stdlib,
18 std.algorithm,
19 std.array,
20 std.datetime,
21 std.digest.md,
22 std.exception,
23 std.file,
24 std.getopt,
25 std.parallelism,
26 std.path,
27 std.process,
28 std.range,
29 std.regex,
30 std.stdio,
31 std.string,
32 std.typetuple;
35 private enum objExt = ".o";
36 private enum binExt = "";
37 private enum libExt = ".a";
38 private enum altDirSeparator = "";
40 private bool chatty, buildOnly, dryRun, force;
41 private string exe;
44 private string compilerBinary = "gdc";
45 private string[] compilerExtraFlags = ["-pipe"];
47 private bool optDynamic = false;
48 private bool optShowCommands = false;
49 private bool optRunTiming = false;
50 private bool optCompileTiming = false;
53 ////////////////////////////////////////////////////////////////////////////////
54 private void yap(size_t line=__LINE__, T...) (auto ref T stuff) {
55 if (!chatty) return;
56 debug stderr.writeln(line, ": ", stuff);
57 else stderr.writeln(stuff);
61 ////////////////////////////////////////////////////////////////////////////////
62 private string[] includeDirs; // -I
63 private string[] importDirs; // -J
64 private bool[string] systemDirs; // to ignore
67 private string expandString (string s) {
68 auto pos = s.indexOf("$(");
69 if (pos >= 0) {
70 string res;
71 while (pos >= 0) {
72 res ~= s[0..pos];
73 s = s[pos+2..$];
74 auto epos = s.indexOf(')');
75 if (epos < 1) throw new Exception("invalid envvar name");
76 auto v = environment.get(s[0..epos], "");
77 res ~= v;
78 s = s[epos+1..$];
79 pos = s.indexOf("$(");
81 res ~= s;
82 return res;
84 return s;
88 private void readRC (string fname) {
89 auto reader = File(fname);
90 scope(exit) collectException(reader.close()); // don't care for errors
91 foreach (string line; lines(reader)) {
92 if (line.startsWith("include=")) {
93 auto val = expandString(line[8..$].strip);
94 val = buildNormalizedPath(val);
95 if (val.length > 0) {
96 val = "-I"~val;
97 if (!includeDirs.canFind(val)) includeDirs ~= val;
99 } else if (line.startsWith("import=")) {
100 auto val = expandString(line[7..$].strip);
101 val = buildNormalizedPath(val);
102 if (val.length > 0) {
103 val = "-J"~val;
104 if (!importDirs.canFind(val)) importDirs ~= val;
106 } else if (line.startsWith("systemdir=")) {
107 auto val = expandString(line[10..$].strip);
108 val = buildNormalizedPath(val);
109 if (val.length > 0) systemDirs[val] = true;
115 ////////////////////////////////////////////////////////////////////////////////
116 private File lockFile;
118 private void lockWorkPath (string workPath) {
119 string lockFileName = buildPath(workPath, "rgdc.lock");
120 if (!dryRun) lockFile.open(lockFileName, "w");
121 yap("lock ", lockFile.name);
122 if (!dryRun) lockFile.lock();
126 private void unlockWorkPath () {
127 yap("unlock ", lockFile.name);
128 if (!dryRun) {
129 lockFile.unlock();
130 lockFile.close();
135 ////////////////////////////////////////////////////////////////////////////////
136 // Run a program optionally writing the command line first
137 // If "replace" is true and the OS supports it, replace the current process.
138 private int run (string[] args, string output=null, bool replace=false) {
139 import std.conv;
140 yap(replace ? "exec " : "spawn ", args.text);
141 if (optShowCommands) stderr.writeln("run: ", args.join(" ")); //TODO: proper quoting
142 if (dryRun) return 0;
143 if (replace && !output) {
144 import std.c.process;
145 auto argv = args.map!toStringz.chain(null.only).array;
146 return execv(argv[0], argv.ptr);
148 File outputFile;
149 if (output) {
150 outputFile = File(output, "wb");
151 } else {
152 outputFile = stdout;
154 auto process = spawnProcess(args, stdin, outputFile);
155 return process.wait();
159 private int runSilent (string[] args) {
160 import std.conv;
161 yap("spawn ", args.text);
162 if (optShowCommands) stderr.writeln("run: ", args.join(" ")); //TODO: proper quoting
163 if (dryRun) return 0;
164 auto process = spawnProcess(args);
165 return process.wait();
169 private int exec (string[] args) {
170 return run(args, null, true);
174 ////////////////////////////////////////////////////////////////////////////////
175 private @property string myOwnTmpDir () {
176 import core.sys.posix.unistd;
177 auto tmpRoot = format("/tmp/.rgdc-%d", getuid());
178 yap("mkdirRecurse ", tmpRoot);
179 if (!dryRun) mkdirRecurse(tmpRoot);
180 return tmpRoot;
184 private string getWorkPath (in string root, in string[] compilerFlags) {
185 static string workPath;
186 if (workPath) return workPath;
187 enum string[] irrelevantSwitches = ["--help", "-ignore", "-quiet", "-v"];
188 MD5 context;
189 context.start();
190 context.put(root.absolutePath().representation);
191 foreach (flag; compilerFlags) {
192 if (irrelevantSwitches.canFind(flag)) continue;
193 context.put(flag.representation);
195 auto digest = context.finish();
196 string hash = toHexString(digest);
197 const tmpRoot = myOwnTmpDir;
198 workPath = buildPath(tmpRoot, "rgdc-"~baseName(root)~'-'~hash);
199 yap("mkdirRecurse ", workPath);
200 if (!dryRun) mkdirRecurse(workPath);
201 return workPath;
205 ////////////////////////////////////////////////////////////////////////////////
206 // Rebuild the executable fullExe starting from modules in myDeps
207 // passing the compiler flags compilerFlags. Generates one large
208 // object file.
209 private int rebuild (string root, string fullExe,
210 string workDir, string objDir, in string[string] myDeps,
211 string[] compilerFlags, bool addStubMain)
213 // Delete the old executable before we start building.
214 yap("stat ", fullExe);
215 if (!dryRun && exists(fullExe)) {
216 yap("rm ", fullExe);
217 try {
218 remove(fullExe);
219 } catch (FileException e) {
220 // This can occur on Windows if the executable is locked.
221 // Although we can't delete the file, we can still rename it.
222 auto oldExe = "%s.%s-%s.old".format(fullExe, Clock.currTime.stdTime, thisProcessID);
223 yap("mv ", fullExe, " ", oldExe);
224 rename(fullExe, oldExe);
227 auto fullExeTemp = fullExe~".tmp";
229 string[] buildTodo () {
230 string outExe = fullExeTemp;//(fullExeTemp[0] != '/' ? objDir~"/"~fullExeTemp : fullExeTemp);
231 auto todo =
232 compilerFlags~
233 ["-o", outExe]~
234 //["-I"~dirName(root)]~
235 //["-J"~dirName(root)]~
236 includeDirs~
237 importDirs/*~
238 [root]*/;
239 foreach (k, objectFile; myDeps) if (objectFile !is null) todo ~= [k];
240 // Need to add void main(){}?
241 if (addStubMain) {
242 auto stubMain = buildPath(myOwnTmpDir, "stubmain.d");
243 std.file.write(stubMain, "void main(){}");
244 todo ~= [stubMain];
246 return todo;
249 auto todo = buildTodo();
250 auto commandLength = escapeShellCommand(todo).length;
251 string[] eflags2;
252 if (optDynamic) {
253 eflags2 ~= "-fPIC";
254 } else {
255 eflags2 ~= "-static-libphobos";
257 // get list of pragma(lib)s
258 string[] prlibs;
259 version(no_pragma_lib) {} else {
260 string prfname = objDir~"/"~fullExeTemp.baseName~".link";
261 //writeln(prfname);
262 collectException(remove(prfname));
263 string[] args =
264 [compilerBinary]~
265 compilerExtraFlags~
266 ["-c", "-o", "/dev/null"]~
267 ["-fwrite-pragma-libs="~prfname]~
268 todo;
269 auto af = args.filter!(a => !a.startsWith("-O") && !a.startsWith("-finline-"));
270 args.length = 0;
271 while (!af.empty) {
272 auto s = af.front;
273 af.popFront();
274 if (s == "-o") {
275 s = af.front;
276 af.popFront();
277 if (s == "/dev/null") args ~= ["-o", "/dev/null"];
278 } else {
279 args ~= s;
282 //args = array(args.filter!(a => !a.startsWith("-O") && !a.startsWith("-finline-")));
283 //writeln(" *** ", args);
284 immutable r1 = run(args);
285 try {
286 auto reader = File(prfname, "r");
287 foreach (string line; lines(reader)) {
288 auto s = line.strip;
289 if (!prlibs.canFind(s)) prlibs ~= s.idup;
291 } catch (Exception) {}
292 if (prlibs.length) {
293 try {
294 auto fo = File(prfname, "w");
295 foreach (s; prlibs) fo.writeln(s);
296 } catch (Exception) {
297 prfname = null;
299 } else {
300 prfname = null;
302 if (prfname.length > 0) {
303 prlibs = ["-Wl,@"~prfname];
304 } else {
305 prlibs.length = 0;
308 if (commandLength+compilerBinary.length+compilerExtraFlags.join(" ").length+eflags2.join(" ").length+
309 prlibs.join(" ").length > 32700) throw new Exception("SHIT!");
310 Timer btm;
311 btm.start();
312 immutable result = run([compilerBinary]~compilerExtraFlags~eflags2~todo~prlibs);
313 btm.stop();
314 if (result) {
315 // build failed
316 if (exists(fullExeTemp)) remove(fullExeTemp);
317 return result;
319 if (!dryRun && optCompileTiming) writeln("RGDC: compile time: ", btm);
320 // clean up the dir containing the object file, just not in dry
321 // run mode because we haven't created any!
322 if (!dryRun) {
323 yap("stat ", objDir);
324 if (objDir.exists && objDir.startsWith(workDir)) {
325 yap("rmdirRecurse ", objDir);
326 // We swallow the exception because of a potential race: two
327 // concurrently-running scripts may attempt to remove this
328 // directory. One will fail.
329 collectException(rmdirRecurse(objDir));
331 yap("mv ", fullExeTemp, " ", fullExe);
332 rename(fullExeTemp, fullExe);
334 return 0;
338 ////////////////////////////////////////////////////////////////////////////////
339 // we don't need std exclusions anymore, we have 'systemdir' key in .rc file for that
340 //private string[] exclusions = ["std", "etc", "core", "tango"]; // packages that are to be excluded
341 private string[] exclusions = []; // packages that are to be excluded
344 // Given module rootModule, returns a mapping of all dependees .d
345 // source filenames to their corresponding .o files sitting in
346 // directory workDir. The mapping is obtained by running dmd -v against
347 // rootModule.
348 private string[string] getDependencies (string rootModule, string workDir, string objDir, string[] compilerFlags) {
349 immutable depsFilename = buildPath(workDir, "rgdc.deps");
351 string[string] readDepsFile (/*string depsFilename, string objDir="."*/) {
353 bool[string] sysDirCache; // cache all modules that are in systemdirs
355 bool inALibrary (string source, string object) {
356 if (object.endsWith(".di") || source == "object" || source == "gcstats") return true;
357 foreach (string exclusion; exclusions) if (source.startsWith(exclusion~'.')) return true;
358 return false;
361 bool isInSystemDir (string path) {
362 path = buildNormalizedPath(path);
363 if (path in sysDirCache) return true;
364 //writeln("isInSystemDir: path=", path, "; dirs=", systemDirs);
365 foreach (auto k; systemDirs.byKey) {
366 if (path.length > k.length && path.startsWith(k) && path[k.length] == '/') {
367 sysDirCache[path.idup] = true;
368 //writeln("in system dir: ", path);
369 return true;
372 return false;
375 string d2obj (string dfile) { return buildPath(objDir, dfile.baseName.chomp(".d")~objExt); }
377 string findLib (string libName) {
378 // This can't be 100% precise without knowing exactly where the linker
379 // will look for libraries (which requires, but is not limited to,
380 // parsing the linker's command line (as specified in dmd.conf/sc.ini).
381 // Go for best-effort instead.
382 string[] dirs = ["."];
383 foreach (envVar; ["LIB", "LIBRARY_PATH", "LD_LIBRARY_PATH"]) dirs ~= environment.get(envVar, "").split(pathSeparator);
384 string[] names = ["lib"~libName~".so", "lib"~libName~".a"];
385 dirs ~= ["/lib", "/usr/lib", "/usr/local/lib"];
386 foreach (dir; dirs) {
387 foreach (name; names) {
388 auto path = buildPath(dir, name);
389 if (path.exists) {
390 if (path.extension == ".so") return "-l"~path.baseName.stripExtension[3..$];
391 return absolutePath(path);
395 return null;
398 yap("read ", depsFilename);
399 auto depsReader = File(depsFilename);
400 scope(exit) collectException(depsReader.close()); // don't care for errors
401 // Fetch all dependencies and append them to myDeps
402 static auto modInfoRE = ctRegex!(r"^(.+?)\s+\(([^)]+)\)");
403 //std.stdio (/opt/gdc/include/d/4.9.1/std/stdio.d) : private : object (/opt/gdc/include/d/4.9.1/object.di)
404 static auto modImportRE = ctRegex!(r"^(.+?)\s+\(([^)]+)\)\s+:\s+string\s+:\s+[^(]+\(([^)]+)\)");
405 //hello (hello.d) : string : zm (/mnt/tigerclaw/D/prj/rgdc/rgdc_native/zm)
406 string[string] result;
407 foreach (string line; lines(depsReader)) {
408 string modName, modPath, filePath;
409 auto modImportMatch = match(line, modImportRE);
410 if (modImportMatch.empty) {
411 auto modInfoMatch = match(line, modInfoRE);
412 if (modInfoMatch.empty) continue;
413 auto modInfoCaps = modInfoMatch.captures;
414 // [1]: module name
415 // [2]: module path
416 modName = modInfoCaps[1];
417 modPath = modInfoCaps[2];
418 } else {
419 auto modImportCaps = modImportMatch.captures;
420 // [1]: module name
421 // [2]: module path
422 // [3]: file path
423 modName = modImportCaps[1];
424 modPath = modImportCaps[2];
425 filePath = modImportCaps[3];
427 if (filePath.length && !isInSystemDir(filePath)) result[filePath] = null;
428 //if (inALibrary(modName, modPath) || isInSystemDir(modPath)) continue;
429 if (isInSystemDir(modPath)) continue;
430 result[modPath] = d2obj(modPath);
432 return result;
435 // Check if the old dependency file is fine
436 if (!force) {
437 yap("stat ", depsFilename);
438 if (exists(depsFilename)) {
439 // See if the deps file is still in good shape
440 auto deps = readDepsFile();
441 auto allDeps = chain(rootModule.only, deps.byKey).array;
442 bool mustRebuildDeps = allDeps.anyNewerThan(timeLastModified(depsFilename));
443 if (!mustRebuildDeps) return deps; // Cool, we're in good shape
444 // Fall through to rebuilding the deps file
448 immutable rootDir = dirName(rootModule);
450 // Collect dependencies
451 auto depsGetter =
452 // "cd "~shellQuote(rootDir)~" && "
453 [compilerBinary]~compilerExtraFlags~compilerFlags~
455 "-c",
456 "-o", "/dev/null",
457 "-fdeps="~depsFilename,
458 rootModule,
459 //"-I"~rootDir,
460 //"-J"~rootDir
461 ]~includeDirs~importDirs;
463 scope(failure) {
464 // Delete the deps file on failure, we don't want to be fooled
465 // by it next time we try
466 collectException(std.file.remove(depsFilename));
469 immutable depsExitCode = runSilent(depsGetter);
470 if (depsExitCode) {
471 stderr.writefln("Failed: %s", depsGetter);
472 collectException(std.file.remove(depsFilename));
473 exit(depsExitCode);
476 return (dryRun ? null : readDepsFile());
480 ////////////////////////////////////////////////////////////////////////////////
481 // Is any file newer than the given file?
482 private bool anyNewerThan (in string[] files, in string file) {
483 yap("stat ", file);
484 return files.anyNewerThan(file.timeLastModified);
488 // Is any file newer than the given file?
489 private bool anyNewerThan (in string[] files, SysTime t) {
490 // Experimental: running newerThan in separate threads, one per file
491 if (false) {
492 foreach (source; files) if (source.newerThan(t)) return true;
493 return false;
494 } else {
495 bool result;
496 foreach (source; taskPool.parallel(files)) if (!result && source.newerThan(t)) result = true;
497 return result;
503 * If force is true, returns true. Otherwise, if source and target both
504 * exist, returns true iff source's timeLastModified is strictly greater
505 * than target's. Otherwise, returns true.
507 private bool newerThan (string source, string target) {
508 if (force) return true;
509 yap("stat ", target);
510 return source.newerThan(timeLastModified(target, SysTime(0)));
514 private bool newerThan (string source, SysTime target) {
515 if (force) return true;
516 try {
517 yap("stat ", source);
518 return DirEntry(source).timeLastModified > target;
519 } catch (Exception) {
520 // File not there, consider it newer
521 return true;
526 ////////////////////////////////////////////////////////////////////////////////
527 private @property string helpString () {
528 return
529 "rgdc build "~thisVersion~"
530 Usage: rgdc [RDMD AND DMD OPTIONS]... program [PROGRAM OPTIONS]...
531 Builds (with dependents) and runs a D program.
532 Example: rgdc -release myprog --myprogparm 5
534 Any option to be passed to the compiler must occur before the program name. In
535 addition to compiler options, rgdc recognizes the following options:
536 --build-only just build the executable, don't run it
537 --chatty write compiler commands to stdout before executing them
538 --compiler=comp use the specified compiler
539 --dry-run do not compile, just show what commands would be run (implies --chatty)
540 --exclude=package exclude a package from the build (multiple --exclude allowed)
541 --force force a rebuild even if apparently not necessary
542 --help this message
543 --main add a stub main program to the mix (e.g. for unittesting)
544 --shebang rgdc is in a shebang line (put as first argument)
545 --time measure program executing time
546 --compile-time measure compile time
551 ////////////////////////////////////////////////////////////////////////////////
552 private @property string thisVersion () {
553 enum d = __DATE__;
554 enum month = d[0..3],
555 day = d[4] == ' ' ? "0"~d[5] : d[4..6],
556 year = d[7..$];
557 enum monthNum
558 = month == "Jan" ? "01"
559 : month == "Feb" ? "02"
560 : month == "Mar" ? "03"
561 : month == "Apr" ? "04"
562 : month == "May" ? "05"
563 : month == "Jun" ? "06"
564 : month == "Jul" ? "07"
565 : month == "Aug" ? "08"
566 : month == "Sep" ? "09"
567 : month == "Oct" ? "10"
568 : month == "Nov" ? "11"
569 : month == "Dec" ? "12"
570 : "";
571 static assert(month != "", "Unknown month "~month);
572 return year[0]~year[1..$]~monthNum~day;
576 private string which (string path) {
577 yap("which ", path);
578 if (path.canFind(dirSeparator) || altDirSeparator != "" && path.canFind(altDirSeparator)) return path;
579 string[] extensions = [""];
580 foreach (extension; extensions) {
581 foreach (envPath; environment["PATH"].splitter(pathSeparator)) {
582 string absPath = buildPath(envPath, path~extension);
583 yap("stat ", absPath);
584 if (exists(absPath) && isFile(absPath)) return absPath;
587 throw new FileException(path, "File not found in PATH");
591 private size_t indexOfProgram (string[] args) {
592 foreach(i, arg; args[1..$]) {
593 if (!arg.startsWith('-', '@') && !arg.endsWith(".o", ".a")) return i+1;
595 return args.length;
599 int main(string[] args) {
601 string[] convertSomeOptions (string[] args, string root) {
602 string[string] simpleReplace;
603 simpleReplace["-unittest"] = "-funittest";
604 simpleReplace["-main"] = "--main";
605 simpleReplace["-time"] = "--time";
606 simpleReplace["-compile-time"] = "--compile-time";
607 simpleReplace["-O"] = "-O2";
608 simpleReplace["-boundscheck=off"] = "-fno-bounds-check";
609 simpleReplace["-boundscheck=on"] = "-fbounds-check";
611 bool hasIDot, hasJDot;
612 int idxI = -1, idxJ = -1;
613 string rootDir = root.dirName.buildNormalizedPath;
614 if (rootDir.length == 0) rootDir ~= ".";
615 string ourI = "-I"~rootDir;
616 string ourJ = "-J"~rootDir;
618 debug writeln("before: ", args);
619 string[] res;
620 res ~= args[0];
621 res ~= "-fignore-unknown-pragmas";
622 foreach (arg; args[1..$]) {
623 auto sr = arg in simpleReplace;
624 if (sr !is null) { res ~= *sr; continue; }
625 if (arg.startsWith("-debug") ||
626 arg.startsWith("-version")) {
627 res ~= "-f"~arg[1..$];
628 continue;
630 if (arg == "-inline") { res ~= ["-finline-small-functions", "-finline-functions"]; continue; }
631 if (arg == ourI) hasIDot = true;
632 else if (arg == ourJ) hasJDot = true;
633 if (idxI < 0 && arg.startsWith("-I")) idxI = cast(int)res.length;
634 else if (idxJ < 0 && arg.startsWith("-J")) idxJ = cast(int)res.length;
635 res ~= arg;
637 if (!res.canFind("-g") && !res.canFind("-s")) res = res~"-s";
638 if (!hasIDot) {
639 if (idxI < 0) idxI = res.length;
640 res = res[0..idxI]~[ourI]~res[idxI..$];
642 if (!hasJDot) {
643 if (idxJ < 0) idxJ = res.length;
644 res = res[0..idxJ]~[ourJ]~res[idxJ..$];
646 debug writeln("after: ", res);
647 return res;
650 //writeln("Invoked with: ", args);
651 if (args.length > 1 && args[1].startsWith("--shebang ", "--shebang=")) {
652 // multiple options wrapped in one
653 auto a = args[1]["--shebang ".length..$];
654 args = args[0..1]~std.string.split(a)~args[2..$];
657 // Continue parsing the command line; now get rgdc's own arguments
658 auto programPos = indexOfProgram(args);
659 assert(programPos > 0);
660 auto argsBeforeProgram = convertSomeOptions(args[0..programPos], (programPos < args.length ? args[programPos] : null));
662 bool bailout; // bailout set by functions called in getopt if program should exit
663 bool addStubMain; // set by --main
664 getopt(argsBeforeProgram,
665 std.getopt.config.caseSensitive,
666 std.getopt.config.passThrough,
667 "build-only", &buildOnly,
668 "chatty", &chatty,
669 "compiler", &compilerBinary,
670 "dry-run", &dryRun,
671 "exclude", &exclusions,
672 "force", &force,
673 "help", { writeln(helpString); bailout = true; },
674 "main", &addStubMain,
675 "show-commands", &optShowCommands,
676 "dynamic", &optDynamic,
677 "static", { optDynamic = false; },
678 "time", &optRunTiming,
679 "compile-time", &optCompileTiming
681 if (bailout) return 0;
682 if (dryRun) chatty = true; // dry-run implies chatty
684 // no code on command line => require a source file
685 if (programPos == args.length) {
686 write(helpString);
687 return 1;
690 auto
691 root = args[programPos].chomp(".d")~".d",
692 exeBasename = root.baseName(".d"),
693 exeDirname = root.dirName,
694 programArgs = args[programPos+1..$];
696 assert(argsBeforeProgram.length >= 1);
697 auto compilerFlags = argsBeforeProgram[1..$];
699 //TODO
700 bool lib = compilerFlags.canFind("-lib");
701 string outExt = (lib ? libExt : binExt);
704 if (!optDynamic && compilerFlags.canFind("-Os") && !compilerFlags.canFind("-Wl,--gc-sections")) {
705 compilerFlags ~= "-Wl,--gc-sections";
709 //collectException(readRC(thisExePath.setExtension(".rc")));
710 foreach (fname; ["./rgdc.rc", "$(HOME)/.rgdc.rc", "/etc/rgdc.rc", "!"]) {
711 if (fname == "!") fname = thisExePath.setExtension(".rc");
712 else fname = expandString(fname);
713 try {
714 //writeln("trying '", fname, "'");
715 readRC(fname);
716 break;
717 } catch (Exception e) {
718 //writeln(" FAILED! ", e.msg);
722 // --build-only implies the user would like a binary in the program's directory
723 if (buildOnly && !exe) exe = exeDirname~dirSeparator;
725 if (exe && exe.endsWith(dirSeparator)) {
726 // user specified a directory, complete it to a file
727 exe = buildPath(exe, exeBasename)~outExt;
730 // Compute the object directory and ensure it exists
731 immutable workDir = getWorkPath(root, compilerFlags);
732 lockWorkPath(workDir); // will be released by the OS on process exit
733 string objDir = buildPath(workDir, "objs");
734 yap("mkdirRecurse ", objDir);
735 if (!dryRun) mkdirRecurse(objDir);
737 if (lib) {
738 // When building libraries, DMD does not generate object files.
739 // Instead, it uses the -od parameter as the location for the library file.
740 // Thus, override objDir (which is normally a temporary directory)
741 // to be the target output directory.
742 objDir = exe.dirName;
745 // Fetch dependencies
746 const myDeps = getDependencies(root, workDir, objDir, compilerFlags);
748 // Compute executable name, check for freshness, rebuild
750 We need to be careful about using -o. Normally the generated
751 executable is hidden in the unique directory workDir. But if the
752 user forces generation in a specific place by using -od or -of,
753 the time of the binary can't be used to check for freshness
754 because the user may change e.g. the compile option from one run
755 to the next, yet the generated binary's datetime stays the
756 same. In those cases, we'll use a dedicated file called ".built"
757 and placed in workDir. Upon a successful build, ".built" will be
758 touched. See also
759 http://d.puremagic.com/issues/show_bug.cgi?id=4814
761 string buildWitness;
762 SysTime lastBuildTime = SysTime.min;
763 if (exe) {
764 // user-specified exe name
765 buildWitness = buildPath(workDir, ".built");
766 if (!exe.newerThan(buildWitness)) {
767 // Both exe and buildWitness exist, and exe is older than
768 // buildWitness. This is the only situation in which we
769 // may NOT need to recompile.
770 lastBuildTime = buildWitness.timeLastModified(SysTime.min);
772 } else {
773 exe = buildPath(workDir, exeBasename)~outExt;
774 buildWitness = exe;
775 lastBuildTime = buildWitness.timeLastModified(SysTime.min);
778 // Have at it
779 if (chain(root.only, myDeps.byKey).array.anyNewerThan(lastBuildTime)) {
780 immutable result = rebuild(root, exe, workDir, objDir, myDeps, compilerFlags, addStubMain);
781 if (result) return result;
782 // Touch the build witness to track the build time
783 if (buildWitness != exe) {
784 yap("touch ", buildWitness);
785 std.file.write(buildWitness, "");
789 if (buildOnly) {
790 // Pretty much done!
791 return 0;
794 // release lock on workDir before launching the user's program
795 unlockWorkPath();
797 // run
798 if (!dryRun && optRunTiming) {
799 Timer rtm;
800 rtm.start();
801 int res = run(exe~programArgs);
802 rtm.stop();
803 writeln("RGDC: execution time: ", rtm);
804 return res;
806 return exec(exe~programArgs);
810 ////////////////////////////////////////////////////////////////////////////////
811 struct Timer {
812 import core.time;
814 enum State {
815 Stopped,
816 Running,
817 Paused
820 @property auto state () @safe const nothrow { return mState; }
822 @property bool stopped () @safe const nothrow { return (mState == State.Stopped); }
823 @property bool running () @safe const nothrow { return (mState == State.Running); }
824 @property bool paused () @safe const nothrow { return (mState == State.Paused); }
826 void reset () @trusted nothrow {
827 mAccum = Duration.zero;
828 mSTime = MonoTime.currTime;
831 void start () @trusted {
832 if (mState != State.Stopped) throw new Exception("Timer.start(): invalid timer state");
833 mAccum = Duration.zero;
834 mState = State.Running;
835 mSTime = MonoTime.currTime;
838 void stop () @trusted {
839 if (mState != State.Running) throw new Exception("Timer.stop(): invalid timer state");
840 mAccum += MonoTime.currTime-mSTime;
841 mState = State.Stopped;
844 void pause () @trusted {
845 if (mState != State.Running) throw new Exception("Timer.pause(): invalid timer state");
846 mAccum += MonoTime.currTime-mSTime;
847 mState = State.Paused;
850 void resume () @trusted {
851 if (mState != State.Paused) throw new Exception("Timer.resume(): invalid timer state");
852 mState = State.Running;
853 mSTime = MonoTime.currTime;
856 string toString () @trusted const {
857 import std.string;
858 Duration d;
859 final switch (mState) {
860 case State.Stopped: case State.Paused: d = mAccum; break;
861 case State.Running: d = mAccum+(MonoTime.currTime-mSTime); break;
863 auto tm = d.split!("hours", "minutes", "seconds", "msecs")();
864 if (tm.hours) return format("%s:%02d:%02d.%03d", tm.hours, tm.minutes, tm.seconds, tm.msecs);
865 if (tm.minutes) return format("%s:%02d.%03d", tm.minutes, tm.seconds, tm.msecs);
866 return format("%s.%03d", tm.seconds, tm.msecs);
869 private:
870 State mState = State.Stopped;
871 MonoTime mSTime;
872 Duration mAccum;