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
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
;
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
) {
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("$(");
74 auto epos
= s
.indexOf(')');
75 if (epos
< 1) throw new Exception("invalid envvar name");
76 auto v
= environment
.get(s
[0..epos
], "");
79 pos
= s
.indexOf("$(");
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
);
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) {
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
);
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) {
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
);
150 outputFile
= File(output
, "wb");
154 auto process
= spawnProcess(args
, stdin
, outputFile
);
155 return process
.wait();
159 private int runSilent (string
[] args
) {
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
);
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"];
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
);
205 ////////////////////////////////////////////////////////////////////////////////
206 // Rebuild the executable fullExe starting from modules in myDeps
207 // passing the compiler flags compilerFlags. Generates one large
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
)) {
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);
234 //["-I"~dirName(root)]~
235 //["-J"~dirName(root)]~
239 foreach (k
, objectFile
; myDeps
) if (objectFile
!is null) todo
~= [k
];
240 // Need to add void main(){}?
242 auto stubMain
= buildPath(myOwnTmpDir
, "stubmain.d");
243 std
.file
.write(stubMain
, "void main(){}");
249 auto todo
= buildTodo();
250 auto commandLength
= escapeShellCommand(todo
).length
;
255 eflags2
~= "-static-libphobos";
257 // get list of pragma(lib)s
259 version(no_pragma_lib
) {} else {
260 string prfname
= objDir
~"/"~fullExeTemp
.baseName
~".link";
262 collectException(remove(prfname
));
266 ["-c", "-o", "/dev/null"]~
267 ["-fwrite-pragma-libs="~prfname
]~
269 auto af
= args
.filter
!(a
=> !a
.startsWith("-O") && !a
.startsWith("-finline-"));
277 if (s
== "/dev/null") args
~= ["-o", "/dev/null"];
282 //args = array(args.filter!(a => !a.startsWith("-O") && !a.startsWith("-finline-")));
283 //writeln(" *** ", args);
284 immutable r1
= run(args
);
286 auto reader
= File(prfname
, "r");
287 foreach (string line
; lines(reader
)) {
289 if (!prlibs
.canFind(s
)) prlibs
~= s
.idup
;
291 } catch (Exception
) {}
294 auto fo
= File(prfname
, "w");
295 foreach (s
; prlibs
) fo
.writeln(s
);
296 } catch (Exception
) {
302 if (prfname
.length
> 0) {
303 prlibs
= ["-Wl,@"~prfname
];
308 if (commandLength
+compilerBinary
.length
+compilerExtraFlags
.join(" ").length
+eflags2
.join(" ").length
+
309 prlibs
.join(" ").length
> 32700) throw new Exception("SHIT!");
312 immutable result
= run([compilerBinary
]~compilerExtraFlags
~eflags2
~todo
~prlibs
);
316 if (exists(fullExeTemp
)) remove(fullExeTemp
);
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!
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
);
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
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;
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);
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
);
390 if (path
.extension
== ".so") return "-l"~path
.baseName
.stripExtension
[3..$];
391 return absolutePath(path
);
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
;
416 modName
= modInfoCaps
[1];
417 modPath
= modInfoCaps
[2];
419 auto modImportCaps
= modImportMatch
.captures
;
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
);
435 // Check if the old dependency file is fine
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
452 // "cd "~shellQuote(rootDir)~" && "
453 [compilerBinary
]~compilerExtraFlags
~compilerFlags
~
457 "-fdeps="~depsFilename
,
461 ]~includeDirs
~importDirs
;
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
);
471 stderr
.writefln("Failed: %s", depsGetter
);
472 collectException(std
.file
.remove(depsFilename
));
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
) {
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
492 foreach (source
; files
) if (source
.newerThan(t
)) return true;
496 foreach (source
; taskPool
.parallel(files
)) if (!result
&& source
.newerThan(t
)) result
= true;
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;
517 yap("stat ", source
);
518 return DirEntry(source
).timeLastModified
> target
;
519 } catch (Exception
) {
520 // File not there, consider it newer
526 ////////////////////////////////////////////////////////////////////////////////
527 private @property string
helpString () {
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
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 () {
554 enum month
= d
[0..3],
555 day
= d
[4] == ' ' ?
"0"~d
[5] : d
[4..6],
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"
571 static assert(month
!= "", "Unknown month "~month
);
572 return year
[0]~year
[1..$]~monthNum
~day
;
576 private string
which (string 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;
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
);
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..$];
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
;
637 if (!res
.canFind("-g") && !res
.canFind("-s")) res
= res
~"-s";
639 if (idxI
< 0) idxI
= res
.length
;
640 res
= res
[0..idxI
]~[ourI
]~res
[idxI
..$];
643 if (idxJ
< 0) idxJ
= res
.length
;
644 res
= res
[0..idxJ
]~[ourJ
]~res
[idxJ
..$];
646 debug writeln("after: ", 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
,
669 "compiler", &compilerBinary
,
671 "exclude", &exclusions
,
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
) {
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..$];
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
);
714 //writeln("trying '", fname, "'");
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
);
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
759 http://d.puremagic.com/issues/show_bug.cgi?id=4814
762 SysTime lastBuildTime
= SysTime
.min
;
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
);
773 exe
= buildPath(workDir
, exeBasename
)~outExt
;
775 lastBuildTime
= buildWitness
.timeLastModified(SysTime
.min
);
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
, "");
794 // release lock on workDir before launching the user's program
798 if (!dryRun
&& optRunTiming
) {
801 int res
= run(exe
~programArgs
);
803 writeln("RGDC: execution time: ", rtm
);
806 return exec(exe
~programArgs
);
810 ////////////////////////////////////////////////////////////////////////////////
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 {
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
);
870 State mState
= State
.Stopped
;