bugfix in adding '-I' and '-J'
[rgdc.git] / rgdc.d
blob7ee4dc4ab62a3ce9d158b4111d2e103b60f291ec
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(getcwd().representation);
191 context.put(root.representation);
192 foreach (flag; compilerFlags) {
193 if (irrelevantSwitches.canFind(flag)) continue;
194 context.put(flag.representation);
196 auto digest = context.finish();
197 string hash = toHexString(digest);
198 const tmpRoot = myOwnTmpDir;
199 workPath = buildPath(tmpRoot, "rgdc-"~baseName(root)~'-'~hash);
200 yap("mkdirRecurse ", workPath);
201 if (!dryRun) mkdirRecurse(workPath);
202 return workPath;
206 ////////////////////////////////////////////////////////////////////////////////
207 // Rebuild the executable fullExe starting from modules in myDeps
208 // passing the compiler flags compilerFlags. Generates one large
209 // object file.
210 private int rebuild (string root, string fullExe,
211 string workDir, string objDir, in string[string] myDeps,
212 string[] compilerFlags, bool addStubMain)
214 // Delete the old executable before we start building.
215 yap("stat ", fullExe);
216 if (!dryRun && exists(fullExe)) {
217 yap("rm ", fullExe);
218 try {
219 remove(fullExe);
220 } catch (FileException e) {
221 // This can occur on Windows if the executable is locked.
222 // Although we can't delete the file, we can still rename it.
223 auto oldExe = "%s.%s-%s.old".format(fullExe, Clock.currTime.stdTime, thisProcessID);
224 yap("mv ", fullExe, " ", oldExe);
225 rename(fullExe, oldExe);
228 auto fullExeTemp = fullExe~".tmp";
230 string[] buildTodo () {
231 string outExe = (fullExeTemp[0] != '/' ? objDir~"/"~fullExeTemp : fullExeTemp);
232 auto todo =
233 compilerFlags~
234 ["-o", outExe]~
235 //["-I"~dirName(root)]~
236 //["-J"~dirName(root)]~
237 includeDirs~
238 importDirs/*~
239 [root]*/;
240 foreach (k, objectFile; myDeps) if (objectFile !is null) todo ~= [k];
241 // Need to add void main(){}?
242 if (addStubMain) {
243 auto stubMain = buildPath(myOwnTmpDir, "stubmain.d");
244 std.file.write(stubMain, "void main(){}");
245 todo ~= [stubMain];
247 return todo;
250 auto todo = buildTodo();
251 auto commandLength = escapeShellCommand(todo).length;
252 string[] eflags2;
253 if (optDynamic) {
254 eflags2 ~= "-fPIC";
255 } else {
256 eflags2 ~= "-static-libphobos";
258 // get list of pragma(lib)s
259 string[] prlibs;
260 version(no_pragma_lib) {} else {
261 string prfname = objDir~"/"~fullExeTemp.baseName~".link";
262 //writeln(prfname);
263 collectException(remove(prfname));
264 string[] args =
265 [compilerBinary]~
266 compilerExtraFlags~
267 ["-c", "-o", "/dev/null"]~
268 ["-fwrite-pragma-libs="~prfname]~
269 todo;
270 auto af = args.filter!(a => !a.startsWith("-O") && !a.startsWith("-finline-"));
271 args.length = 0;
272 while (!af.empty) {
273 auto s = af.front;
274 af.popFront();
275 if (s == "-o") {
276 s = af.front;
277 af.popFront();
278 if (s == "/dev/null") args ~= ["-o", "/dev/null"];
279 } else {
280 args ~= s;
283 //args = array(args.filter!(a => !a.startsWith("-O") && !a.startsWith("-finline-")));
284 //writeln(" *** ", args);
285 immutable r1 = run(args);
286 try {
287 auto reader = File(prfname, "r");
288 foreach (string line; lines(reader)) {
289 auto s = line.strip;
290 if (!prlibs.canFind(s)) prlibs ~= s.idup;
292 } catch (Exception) {}
293 if (prlibs.length) {
294 try {
295 auto fo = File(prfname, "w");
296 foreach (s; prlibs) fo.writeln(s);
297 } catch (Exception) {
298 prfname = null;
300 } else {
301 prfname = null;
303 if (prfname.length > 0) {
304 prlibs = ["-Wl,@"~prfname];
305 } else {
306 prlibs.length = 0;
309 if (commandLength+compilerBinary.length+compilerExtraFlags.join(" ").length+eflags2.join(" ").length+
310 prlibs.join(" ").length > 32700) throw new Exception("SHIT!");
311 Timer btm;
312 btm.start();
313 immutable result = run([compilerBinary]~compilerExtraFlags~eflags2~todo~prlibs);
314 btm.stop();
315 if (result) {
316 // build failed
317 if (exists(fullExeTemp)) remove(fullExeTemp);
318 return result;
320 if (!dryRun && optCompileTiming) writeln("RGDC: compile time: ", btm);
321 // clean up the dir containing the object file, just not in dry
322 // run mode because we haven't created any!
323 if (!dryRun) {
324 yap("stat ", objDir);
325 if (objDir.exists && objDir.startsWith(workDir)) {
326 yap("rmdirRecurse ", objDir);
327 // We swallow the exception because of a potential race: two
328 // concurrently-running scripts may attempt to remove this
329 // directory. One will fail.
330 collectException(rmdirRecurse(objDir));
332 yap("mv ", fullExeTemp, " ", fullExe);
333 rename(fullExeTemp, fullExe);
335 return 0;
339 ////////////////////////////////////////////////////////////////////////////////
340 // we don't need std exclusions anymore, we have 'systemdir' key in .rc file for that
341 //private string[] exclusions = ["std", "etc", "core", "tango"]; // packages that are to be excluded
342 private string[] exclusions = []; // packages that are to be excluded
345 // Given module rootModule, returns a mapping of all dependees .d
346 // source filenames to their corresponding .o files sitting in
347 // directory workDir. The mapping is obtained by running dmd -v against
348 // rootModule.
349 private string[string] getDependencies (string rootModule, string workDir, string objDir, string[] compilerFlags) {
350 immutable depsFilename = buildPath(workDir, "rgdc.deps");
352 string[string] readDepsFile (/*string depsFilename, string objDir="."*/) {
354 bool[string] sysDirCache; // cache all modules that are in systemdirs
356 bool inALibrary (string source, string object) {
357 if (object.endsWith(".di") || source == "object" || source == "gcstats") return true;
358 foreach (string exclusion; exclusions) if (source.startsWith(exclusion~'.')) return true;
359 return false;
362 bool isInSystemDir (string path) {
363 path = buildNormalizedPath(path);
364 if (path in sysDirCache) return true;
365 //writeln("isInSystemDir: path=", path, "; dirs=", systemDirs);
366 foreach (auto k; systemDirs.byKey) {
367 if (path.length > k.length && path.startsWith(k) && path[k.length] == '/') {
368 sysDirCache[path.idup] = true;
369 //writeln("in system dir: ", path);
370 return true;
373 return false;
376 string d2obj (string dfile) { return buildPath(objDir, dfile.baseName.chomp(".d")~objExt); }
378 string findLib (string libName) {
379 // This can't be 100% precise without knowing exactly where the linker
380 // will look for libraries (which requires, but is not limited to,
381 // parsing the linker's command line (as specified in dmd.conf/sc.ini).
382 // Go for best-effort instead.
383 string[] dirs = ["."];
384 foreach (envVar; ["LIB", "LIBRARY_PATH", "LD_LIBRARY_PATH"]) dirs ~= environment.get(envVar, "").split(pathSeparator);
385 string[] names = ["lib"~libName~".so", "lib"~libName~".a"];
386 dirs ~= ["/lib", "/usr/lib", "/usr/local/lib"];
387 foreach (dir; dirs) {
388 foreach (name; names) {
389 auto path = buildPath(dir, name);
390 if (path.exists) {
391 if (path.extension == ".so") return "-l"~path.baseName.stripExtension[3..$];
392 return absolutePath(path);
396 return null;
399 yap("read ", depsFilename);
400 auto depsReader = File(depsFilename);
401 scope(exit) collectException(depsReader.close()); // don't care for errors
402 // Fetch all dependencies and append them to myDeps
403 static auto modInfoRE = ctRegex!(r"^(.+?)\s+\(([^)]+)\)");
404 //std.stdio (/opt/gdc/include/d/4.9.1/std/stdio.d) : private : object (/opt/gdc/include/d/4.9.1/object.di)
405 static auto modImportRE = ctRegex!(r"^(.+?)\s+\(([^)]+)\)\s+:\s+string\s+:\s+[^(]+\(([^)]+)\)");
406 //hello (hello.d) : string : zm (/mnt/tigerclaw/D/prj/rgdc/rgdc_native/zm)
407 string[string] result;
408 foreach (string line; lines(depsReader)) {
409 string modName, modPath, filePath;
410 auto modImportMatch = match(line, modImportRE);
411 if (modImportMatch.empty) {
412 auto modInfoMatch = match(line, modInfoRE);
413 if (modInfoMatch.empty) continue;
414 auto modInfoCaps = modInfoMatch.captures;
415 // [1]: module name
416 // [2]: module path
417 modName = modInfoCaps[1];
418 modPath = modInfoCaps[2];
419 } else {
420 auto modImportCaps = modImportMatch.captures;
421 // [1]: module name
422 // [2]: module path
423 // [3]: file path
424 modName = modImportCaps[1];
425 modPath = modImportCaps[2];
426 filePath = modImportCaps[3];
428 if (filePath.length && !isInSystemDir(filePath)) result[filePath] = null;
429 //if (inALibrary(modName, modPath) || isInSystemDir(modPath)) continue;
430 if (isInSystemDir(modPath)) continue;
431 result[modPath] = d2obj(modPath);
433 return result;
436 // Check if the old dependency file is fine
437 if (!force) {
438 yap("stat ", depsFilename);
439 if (exists(depsFilename)) {
440 // See if the deps file is still in good shape
441 auto deps = readDepsFile();
442 auto allDeps = chain(rootModule.only, deps.byKey).array;
443 bool mustRebuildDeps = allDeps.anyNewerThan(timeLastModified(depsFilename));
444 if (!mustRebuildDeps) return deps; // Cool, we're in good shape
445 // Fall through to rebuilding the deps file
449 immutable rootDir = dirName(rootModule);
451 // Collect dependencies
452 auto depsGetter =
453 // "cd "~shellQuote(rootDir)~" && "
454 [compilerBinary]~compilerExtraFlags~compilerFlags~
456 "-c",
457 "-o", "/dev/null",
458 "-fdeps="~depsFilename,
459 rootModule,
460 //"-I"~rootDir,
461 //"-J"~rootDir
462 ]~includeDirs~importDirs;
464 scope(failure) {
465 // Delete the deps file on failure, we don't want to be fooled
466 // by it next time we try
467 collectException(std.file.remove(depsFilename));
470 immutable depsExitCode = runSilent(depsGetter);
471 if (depsExitCode) {
472 stderr.writefln("Failed: %s", depsGetter);
473 collectException(std.file.remove(depsFilename));
474 exit(depsExitCode);
477 return (dryRun ? null : readDepsFile());
481 ////////////////////////////////////////////////////////////////////////////////
482 // Is any file newer than the given file?
483 private bool anyNewerThan (in string[] files, in string file) {
484 yap("stat ", file);
485 return files.anyNewerThan(file.timeLastModified);
489 // Is any file newer than the given file?
490 private bool anyNewerThan (in string[] files, SysTime t) {
491 // Experimental: running newerThan in separate threads, one per file
492 if (false) {
493 foreach (source; files) if (source.newerThan(t)) return true;
494 return false;
495 } else {
496 bool result;
497 foreach (source; taskPool.parallel(files)) if (!result && source.newerThan(t)) result = true;
498 return result;
504 * If force is true, returns true. Otherwise, if source and target both
505 * exist, returns true iff source's timeLastModified is strictly greater
506 * than target's. Otherwise, returns true.
508 private bool newerThan (string source, string target) {
509 if (force) return true;
510 yap("stat ", target);
511 return source.newerThan(timeLastModified(target, SysTime(0)));
515 private bool newerThan (string source, SysTime target) {
516 if (force) return true;
517 try {
518 yap("stat ", source);
519 return DirEntry(source).timeLastModified > target;
520 } catch (Exception) {
521 // File not there, consider it newer
522 return true;
527 ////////////////////////////////////////////////////////////////////////////////
528 private @property string helpString () {
529 return
530 "rgdc build "~thisVersion~"
531 Usage: rgdc [RDMD AND DMD OPTIONS]... program [PROGRAM OPTIONS]...
532 Builds (with dependents) and runs a D program.
533 Example: rgdc -release myprog --myprogparm 5
535 Any option to be passed to the compiler must occur before the program name. In
536 addition to compiler options, rgdc recognizes the following options:
537 --build-only just build the executable, don't run it
538 --chatty write compiler commands to stdout before executing them
539 --compiler=comp use the specified compiler
540 --dry-run do not compile, just show what commands would be run (implies --chatty)
541 --exclude=package exclude a package from the build (multiple --exclude allowed)
542 --force force a rebuild even if apparently not necessary
543 --help this message
544 --main add a stub main program to the mix (e.g. for unittesting)
545 --shebang rgdc is in a shebang line (put as first argument)
546 --time measure program executing time
547 --compile-time measure compile time
552 ////////////////////////////////////////////////////////////////////////////////
553 private @property string thisVersion () {
554 enum d = __DATE__;
555 enum month = d[0..3],
556 day = d[4] == ' ' ? "0"~d[5] : d[4..6],
557 year = d[7..$];
558 enum monthNum
559 = month == "Jan" ? "01"
560 : month == "Feb" ? "02"
561 : month == "Mar" ? "03"
562 : month == "Apr" ? "04"
563 : month == "May" ? "05"
564 : month == "Jun" ? "06"
565 : month == "Jul" ? "07"
566 : month == "Aug" ? "08"
567 : month == "Sep" ? "09"
568 : month == "Oct" ? "10"
569 : month == "Nov" ? "11"
570 : month == "Dec" ? "12"
571 : "";
572 static assert(month != "", "Unknown month "~month);
573 return year[0]~year[1..$]~monthNum~day;
577 private string which (string path) {
578 yap("which ", path);
579 if (path.canFind(dirSeparator) || altDirSeparator != "" && path.canFind(altDirSeparator)) return path;
580 string[] extensions = [""];
581 foreach (extension; extensions) {
582 foreach (envPath; environment["PATH"].splitter(pathSeparator)) {
583 string absPath = buildPath(envPath, path~extension);
584 yap("stat ", absPath);
585 if (exists(absPath) && isFile(absPath)) return absPath;
588 throw new FileException(path, "File not found in PATH");
592 private size_t indexOfProgram (string[] args) {
593 foreach(i, arg; args[1..$]) {
594 if (!arg.startsWith('-', '@') && !arg.endsWith(".o", ".a")) return i+1;
596 return args.length;
600 int main(string[] args) {
602 string[] convertSomeOptions (string[] args, string root) {
603 string[string] simpleReplace;
604 simpleReplace["-unittest"] = "-funittest";
605 simpleReplace["-main"] = "--main";
606 simpleReplace["-time"] = "--time";
607 simpleReplace["-compile-time"] = "--compile-time";
608 simpleReplace["-O"] = "-O2";
609 simpleReplace["-boundscheck=off"] = "-fno-bounds-check";
610 simpleReplace["-boundscheck=on"] = "-fbounds-check";
612 bool hasIDot, hasJDot;
613 int idxI = -1, idxJ = -1;
614 string rootDir = root.dirName.buildNormalizedPath;
615 if (rootDir.length == 0) rootDir ~= ".";
616 string ourI = "-I"~rootDir;
617 string ourJ = "-J"~rootDir;
619 debug writeln("before: ", args);
620 string[] res;
621 res ~= args[0];
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);
703 //collectException(readRC(thisExePath.setExtension(".rc")));
704 foreach (fname; ["./rgdc.rc", "$(HOME)/.rgdc.rc", "/etc/rgdc.rc", "!"]) {
705 if (fname == "!") fname = thisExePath.setExtension(".rc");
706 else fname = expandString(fname);
707 try {
708 //writeln("trying '", fname, "'");
709 readRC(fname);
710 break;
711 } catch (Exception e) {
712 //writeln(" FAILED! ", e.msg);
716 // --build-only implies the user would like a binary in the program's directory
717 if (buildOnly && !exe) exe = exeDirname~dirSeparator;
719 if (exe && exe.endsWith(dirSeparator)) {
720 // user specified a directory, complete it to a file
721 exe = buildPath(exe, exeBasename)~outExt;
724 // Compute the object directory and ensure it exists
725 immutable workDir = getWorkPath(root, compilerFlags);
726 lockWorkPath(workDir); // will be released by the OS on process exit
727 string objDir = buildPath(workDir, "objs");
728 yap("mkdirRecurse ", objDir);
729 if (!dryRun) mkdirRecurse(objDir);
731 if (lib) {
732 // When building libraries, DMD does not generate object files.
733 // Instead, it uses the -od parameter as the location for the library file.
734 // Thus, override objDir (which is normally a temporary directory)
735 // to be the target output directory.
736 objDir = exe.dirName;
739 // Fetch dependencies
740 const myDeps = getDependencies(root, workDir, objDir, compilerFlags);
742 // Compute executable name, check for freshness, rebuild
744 We need to be careful about using -o. Normally the generated
745 executable is hidden in the unique directory workDir. But if the
746 user forces generation in a specific place by using -od or -of,
747 the time of the binary can't be used to check for freshness
748 because the user may change e.g. the compile option from one run
749 to the next, yet the generated binary's datetime stays the
750 same. In those cases, we'll use a dedicated file called ".built"
751 and placed in workDir. Upon a successful build, ".built" will be
752 touched. See also
753 http://d.puremagic.com/issues/show_bug.cgi?id=4814
755 string buildWitness;
756 SysTime lastBuildTime = SysTime.min;
757 if (exe) {
758 // user-specified exe name
759 buildWitness = buildPath(workDir, ".built");
760 if (!exe.newerThan(buildWitness)) {
761 // Both exe and buildWitness exist, and exe is older than
762 // buildWitness. This is the only situation in which we
763 // may NOT need to recompile.
764 lastBuildTime = buildWitness.timeLastModified(SysTime.min);
766 } else {
767 exe = buildPath(workDir, exeBasename)~outExt;
768 buildWitness = exe;
769 lastBuildTime = buildWitness.timeLastModified(SysTime.min);
772 // Have at it
773 if (chain(root.only, myDeps.byKey).array.anyNewerThan(lastBuildTime)) {
774 immutable result = rebuild(root, exe, workDir, objDir, myDeps, compilerFlags, addStubMain);
775 if (result) return result;
776 // Touch the build witness to track the build time
777 if (buildWitness != exe) {
778 yap("touch ", buildWitness);
779 std.file.write(buildWitness, "");
783 if (buildOnly) {
784 // Pretty much done!
785 return 0;
788 // release lock on workDir before launching the user's program
789 unlockWorkPath();
791 // run
792 if (!dryRun && optRunTiming) {
793 Timer rtm;
794 rtm.start();
795 int res = run(exe~programArgs);
796 rtm.stop();
797 writeln("RGDC: execution time: ", rtm);
798 return res;
800 return exec(exe~programArgs);
804 ////////////////////////////////////////////////////////////////////////////////
805 struct Timer {
806 import core.time;
808 enum State {
809 Stopped,
810 Running,
811 Paused
814 @property auto state () @safe const nothrow { return mState; }
816 @property bool stopped () @safe const nothrow { return (mState == State.Stopped); }
817 @property bool running () @safe const nothrow { return (mState == State.Running); }
818 @property bool paused () @safe const nothrow { return (mState == State.Paused); }
820 void reset () @trusted nothrow {
821 mAccum = Duration.zero;
822 mSTime = MonoTime.currTime;
825 void start () @trusted {
826 if (mState != State.Stopped) throw new Exception("Timer.start(): invalid timer state");
827 mAccum = Duration.zero;
828 mState = State.Running;
829 mSTime = MonoTime.currTime;
832 void stop () @trusted {
833 if (mState != State.Running) throw new Exception("Timer.stop(): invalid timer state");
834 mAccum += MonoTime.currTime-mSTime;
835 mState = State.Stopped;
838 void pause () @trusted {
839 if (mState != State.Running) throw new Exception("Timer.pause(): invalid timer state");
840 mAccum += MonoTime.currTime-mSTime;
841 mState = State.Paused;
844 void resume () @trusted {
845 if (mState != State.Paused) throw new Exception("Timer.resume(): invalid timer state");
846 mState = State.Running;
847 mSTime = MonoTime.currTime;
850 string toString () @trusted const {
851 import std.string;
852 Duration d;
853 final switch (mState) {
854 case State.Stopped: case State.Paused: d = mAccum; break;
855 case State.Running: d = mAccum+(MonoTime.currTime-mSTime); break;
857 auto tm = d.split!("hours", "minutes", "seconds", "msecs")();
858 if (tm.hours) return format("%s:%02d:%02d.%03d", tm.hours, tm.minutes, tm.seconds, tm.msecs);
859 if (tm.minutes) return format("%s:%02d.%03d", tm.minutes, tm.seconds, tm.msecs);
860 return format("%s.%03d", tm.seconds, tm.msecs);
863 private:
864 State mState = State.Stopped;
865 MonoTime mSTime;
866 Duration mAccum;