better(?) "-I." and "-J."
[rgdc.git] / rgdc.d
blobc9a778a54d8ee512453b4709e6b57901b16a3147
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;
51 ////////////////////////////////////////////////////////////////////////////////
52 private void yap(size_t line=__LINE__, T...) (auto ref T stuff) {
53 if (!chatty) return;
54 debug stderr.writeln(line, ": ", stuff);
55 else stderr.writeln(stuff);
59 ////////////////////////////////////////////////////////////////////////////////
60 private string[] includeDirs; // -I
61 private string[] importDirs; // -J
62 private bool[string] systemDirs; // to ignore
65 private string expandString (string s) {
66 auto pos = s.indexOf("$(");
67 if (pos >= 0) {
68 string res;
69 while (pos >= 0) {
70 res ~= s[0..pos];
71 s = s[pos+2..$];
72 auto epos = s.indexOf(')');
73 if (epos < 1) throw new Exception("invalid envvar name");
74 auto v = environment.get(s[0..epos], "");
75 res ~= v;
76 s = s[epos+1..$];
77 pos = s.indexOf("$(");
79 res ~= s;
80 return res;
82 return s;
86 private void readRC (string fname) {
87 auto reader = File(fname);
88 scope(exit) collectException(reader.close()); // don't care for errors
89 foreach (string line; lines(reader)) {
90 if (line.startsWith("include=")) {
91 auto val = expandString(line[8..$].strip);
92 val = buildNormalizedPath(val);
93 if (val.length > 0) {
94 val = "-I"~val;
95 if (!includeDirs.canFind(val)) includeDirs ~= val;
97 } else if (line.startsWith("import=")) {
98 auto val = expandString(line[7..$].strip);
99 val = buildNormalizedPath(val);
100 if (val.length > 0) {
101 val = "-J"~val;
102 if (!importDirs.canFind(val)) importDirs ~= val;
104 } else if (line.startsWith("systemdir=")) {
105 auto val = expandString(line[10..$].strip);
106 val = buildNormalizedPath(val);
107 if (val.length > 0) systemDirs[val] = true;
113 ////////////////////////////////////////////////////////////////////////////////
114 private File lockFile;
116 private void lockWorkPath (string workPath) {
117 string lockFileName = buildPath(workPath, "rgdc.lock");
118 if (!dryRun) lockFile.open(lockFileName, "w");
119 yap("lock ", lockFile.name);
120 if (!dryRun) lockFile.lock();
124 private void unlockWorkPath () {
125 yap("unlock ", lockFile.name);
126 if (!dryRun) {
127 lockFile.unlock();
128 lockFile.close();
133 ////////////////////////////////////////////////////////////////////////////////
134 // Run a program optionally writing the command line first
135 // If "replace" is true and the OS supports it, replace the current process.
136 private int run (string[] args, string output=null, bool replace=false) {
137 import std.conv;
138 yap(replace ? "exec " : "spawn ", args.text);
139 if (optShowCommands) stderr.writeln("run: ", args.join(" ")); //TODO: proper quoting
140 if (dryRun) return 0;
141 if (replace && !output) {
142 import std.c.process;
143 auto argv = args.map!toStringz.chain(null.only).array;
144 return execv(argv[0], argv.ptr);
146 File outputFile;
147 if (output) {
148 outputFile = File(output, "wb");
149 } else {
150 outputFile = stdout;
152 auto process = spawnProcess(args, stdin, outputFile);
153 return process.wait();
157 private int runSilent (string[] args) {
158 import std.conv;
159 yap("spawn ", args.text);
160 if (optShowCommands) stderr.writeln("run: ", args.join(" ")); //TODO: proper quoting
161 if (dryRun) return 0;
162 auto process = spawnProcess(args);
163 return process.wait();
167 private int exec (string[] args) {
168 return run(args, null, true);
172 ////////////////////////////////////////////////////////////////////////////////
173 private @property string myOwnTmpDir () {
174 import core.sys.posix.unistd;
175 auto tmpRoot = format("/tmp/.rgdc-%d", getuid());
176 yap("mkdirRecurse ", tmpRoot);
177 if (!dryRun) mkdirRecurse(tmpRoot);
178 return tmpRoot;
182 private string getWorkPath (in string root, in string[] compilerFlags) {
183 static string workPath;
184 if (workPath) return workPath;
185 enum string[] irrelevantSwitches = ["--help", "-ignore", "-quiet", "-v"];
186 MD5 context;
187 context.start();
188 context.put(getcwd().representation);
189 context.put(root.representation);
190 foreach (flag; compilerFlags) {
191 if (irrelevantSwitches.canFind(flag)) continue;
192 context.put(flag.representation);
194 auto digest = context.finish();
195 string hash = toHexString(digest);
196 const tmpRoot = myOwnTmpDir;
197 workPath = buildPath(tmpRoot, "rgdc-"~baseName(root)~'-'~hash);
198 yap("mkdirRecurse ", workPath);
199 if (!dryRun) mkdirRecurse(workPath);
200 return workPath;
204 ////////////////////////////////////////////////////////////////////////////////
205 // Rebuild the executable fullExe starting from modules in myDeps
206 // passing the compiler flags compilerFlags. Generates one large
207 // object file.
208 private int rebuild (string root, string fullExe,
209 string workDir, string objDir, in string[string] myDeps,
210 string[] compilerFlags, bool addStubMain)
212 // Delete the old executable before we start building.
213 yap("stat ", fullExe);
214 if (!dryRun && exists(fullExe)) {
215 yap("rm ", fullExe);
216 try {
217 remove(fullExe);
218 } catch (FileException e) {
219 // This can occur on Windows if the executable is locked.
220 // Although we can't delete the file, we can still rename it.
221 auto oldExe = "%s.%s-%s.old".format(fullExe, Clock.currTime.stdTime, thisProcessID);
222 yap("mv ", fullExe, " ", oldExe);
223 rename(fullExe, oldExe);
226 auto fullExeTemp = fullExe~".tmp";
228 string[] buildTodo () {
229 string outExe = (fullExeTemp[0] != '/' ? objDir~"/"~fullExeTemp : fullExeTemp);
230 auto todo =
231 compilerFlags~
232 ["-o", outExe]~
233 //["-I"~dirName(root)]~
234 //["-J"~dirName(root)]~
235 includeDirs~
236 importDirs/*~
237 [root]*/;
238 foreach (k, objectFile; myDeps) if (objectFile !is null) todo ~= [k];
239 // Need to add void main(){}?
240 if (addStubMain) {
241 auto stubMain = buildPath(myOwnTmpDir, "stubmain.d");
242 std.file.write(stubMain, "void main(){}");
243 todo ~= [stubMain];
245 return todo;
248 auto todo = buildTodo();
249 auto commandLength = escapeShellCommand(todo).length;
250 string[] eflags2;
251 if (optDynamic) {
252 eflags2 ~= "-fPIC";
253 } else {
254 eflags2 ~= "-static-libphobos";
256 // get list of pragma(lib)s
257 string[] prlibs;
258 version(no_pragma_lib) {} else {
259 string prfname = objDir~"/"~fullExeTemp.baseName~".link";
260 //writeln(prfname);
261 collectException(remove(prfname));
262 string[] args =
263 [compilerBinary]~
264 compilerExtraFlags~
265 ["-c", "-o", "/dev/null"]~
266 ["-fwrite-pragma-libs="~prfname]~
267 todo;
268 auto af = args.filter!(a => !a.startsWith("-O") && !a.startsWith("-finline-"));
269 args.length = 0;
270 while (!af.empty) {
271 auto s = af.front;
272 af.popFront();
273 if (s == "-o") {
274 s = af.front;
275 af.popFront();
276 if (s == "/dev/null") args ~= ["-o", "/dev/null"];
277 } else {
278 args ~= s;
281 //args = array(args.filter!(a => !a.startsWith("-O") && !a.startsWith("-finline-")));
282 //writeln(" *** ", args);
283 immutable r1 = run(args);
284 try {
285 auto reader = File(prfname, "r");
286 foreach (string line; lines(reader)) {
287 auto s = line.strip;
288 if (!prlibs.canFind(s)) prlibs ~= s.idup;
290 } catch (Exception) {}
291 if (prlibs.length) {
292 try {
293 auto fo = File(prfname, "w");
294 foreach (s; prlibs) fo.writeln(s);
295 } catch (Exception) {
296 prfname = null;
298 } else {
299 prfname = null;
301 if (prfname.length > 0) {
302 prlibs = ["-Wl,@"~prfname];
303 } else {
304 prlibs.length = 0;
307 if (commandLength+compilerBinary.length+compilerExtraFlags.join(" ").length+eflags2.join(" ").length+
308 prlibs.join(" ").length > 32700) throw new Exception("SHIT!");
309 immutable result = run([compilerBinary]~compilerExtraFlags~eflags2~todo~prlibs);
310 if (result) {
311 // build failed
312 if (exists(fullExeTemp)) remove(fullExeTemp);
313 return result;
315 // clean up the dir containing the object file, just not in dry
316 // run mode because we haven't created any!
317 if (!dryRun) {
318 yap("stat ", objDir);
319 if (objDir.exists && objDir.startsWith(workDir)) {
320 yap("rmdirRecurse ", objDir);
321 // We swallow the exception because of a potential race: two
322 // concurrently-running scripts may attempt to remove this
323 // directory. One will fail.
324 collectException(rmdirRecurse(objDir));
326 yap("mv ", fullExeTemp, " ", fullExe);
327 rename(fullExeTemp, fullExe);
329 return 0;
333 ////////////////////////////////////////////////////////////////////////////////
334 // we don't need std exclusions anymore, we have 'systemdir' key in .rc file for that
335 //private string[] exclusions = ["std", "etc", "core", "tango"]; // packages that are to be excluded
336 private string[] exclusions = []; // packages that are to be excluded
339 // Given module rootModule, returns a mapping of all dependees .d
340 // source filenames to their corresponding .o files sitting in
341 // directory workDir. The mapping is obtained by running dmd -v against
342 // rootModule.
343 private string[string] getDependencies (string rootModule, string workDir, string objDir, string[] compilerFlags) {
344 immutable depsFilename = buildPath(workDir, "rgdc.deps");
346 string[string] readDepsFile (/*string depsFilename, string objDir="."*/) {
348 bool[string] sysDirCache; // cache all modules that are in systemdirs
350 bool inALibrary (string source, string object) {
351 if (object.endsWith(".di") || source == "object" || source == "gcstats") return true;
352 foreach (string exclusion; exclusions) if (source.startsWith(exclusion~'.')) return true;
353 return false;
356 bool isInSystemDir (string path) {
357 path = buildNormalizedPath(path);
358 if (path in sysDirCache) return true;
359 //writeln("isInSystemDir: path=", path, "; dirs=", systemDirs);
360 foreach (auto k; systemDirs.byKey) {
361 if (path.length > k.length && path.startsWith(k) && path[k.length] == '/') {
362 sysDirCache[path.idup] = true;
363 //writeln("in system dir: ", path);
364 return true;
367 return false;
370 string d2obj (string dfile) { return buildPath(objDir, dfile.baseName.chomp(".d")~objExt); }
372 string findLib (string libName) {
373 // This can't be 100% precise without knowing exactly where the linker
374 // will look for libraries (which requires, but is not limited to,
375 // parsing the linker's command line (as specified in dmd.conf/sc.ini).
376 // Go for best-effort instead.
377 string[] dirs = ["."];
378 foreach (envVar; ["LIB", "LIBRARY_PATH", "LD_LIBRARY_PATH"]) dirs ~= environment.get(envVar, "").split(pathSeparator);
379 string[] names = ["lib"~libName~".so", "lib"~libName~".a"];
380 dirs ~= ["/lib", "/usr/lib", "/usr/local/lib"];
381 foreach (dir; dirs) {
382 foreach (name; names) {
383 auto path = buildPath(dir, name);
384 if (path.exists) {
385 if (path.extension == ".so") return "-l"~path.baseName.stripExtension[3..$];
386 return absolutePath(path);
390 return null;
393 yap("read ", depsFilename);
394 auto depsReader = File(depsFilename);
395 scope(exit) collectException(depsReader.close()); // don't care for errors
396 // Fetch all dependencies and append them to myDeps
397 static auto modInfoRE = ctRegex!(r"^(.+?)\s+\(([^)]+)\)");
398 //std.stdio (/opt/gdc/include/d/4.9.1/std/stdio.d) : private : object (/opt/gdc/include/d/4.9.1/object.di)
399 static auto modImportRE = ctRegex!(r"^(.+?)\s+\(([^)]+)\)\s+:\s+string\s+:\s+[^(]+\(([^)]+)\)");
400 //hello (hello.d) : string : zm (/mnt/tigerclaw/D/prj/rgdc/rgdc_native/zm)
401 string[string] result;
402 foreach (string line; lines(depsReader)) {
403 string modName, modPath, filePath;
404 auto modImportMatch = match(line, modImportRE);
405 if (modImportMatch.empty) {
406 auto modInfoMatch = match(line, modInfoRE);
407 if (modInfoMatch.empty) continue;
408 auto modInfoCaps = modInfoMatch.captures;
409 // [1]: module name
410 // [2]: module path
411 modName = modInfoCaps[1];
412 modPath = modInfoCaps[2];
413 } else {
414 auto modImportCaps = modImportMatch.captures;
415 // [1]: module name
416 // [2]: module path
417 // [3]: file path
418 modName = modImportCaps[1];
419 modPath = modImportCaps[2];
420 filePath = modImportCaps[3];
422 if (filePath.length && !isInSystemDir(filePath)) result[filePath] = null;
423 //if (inALibrary(modName, modPath) || isInSystemDir(modPath)) continue;
424 if (isInSystemDir(modPath)) continue;
425 result[modPath] = d2obj(modPath);
427 return result;
430 // Check if the old dependency file is fine
431 if (!force) {
432 yap("stat ", depsFilename);
433 if (exists(depsFilename)) {
434 // See if the deps file is still in good shape
435 auto deps = readDepsFile();
436 auto allDeps = chain(rootModule.only, deps.byKey).array;
437 bool mustRebuildDeps = allDeps.anyNewerThan(timeLastModified(depsFilename));
438 if (!mustRebuildDeps) return deps; // Cool, we're in good shape
439 // Fall through to rebuilding the deps file
443 immutable rootDir = dirName(rootModule);
445 // Collect dependencies
446 auto depsGetter =
447 // "cd "~shellQuote(rootDir)~" && "
448 [compilerBinary]~compilerExtraFlags~compilerFlags~
450 "-c",
451 "-o", "/dev/null",
452 "-fdeps="~depsFilename,
453 rootModule,
454 //"-I"~rootDir,
455 //"-J"~rootDir
456 ]~includeDirs~importDirs;
458 scope(failure) {
459 // Delete the deps file on failure, we don't want to be fooled
460 // by it next time we try
461 collectException(std.file.remove(depsFilename));
464 immutable depsExitCode = runSilent(depsGetter);
465 if (depsExitCode) {
466 stderr.writefln("Failed: %s", depsGetter);
467 collectException(std.file.remove(depsFilename));
468 exit(depsExitCode);
471 return (dryRun ? null : readDepsFile());
475 ////////////////////////////////////////////////////////////////////////////////
476 // Is any file newer than the given file?
477 private bool anyNewerThan (in string[] files, in string file) {
478 yap("stat ", file);
479 return files.anyNewerThan(file.timeLastModified);
483 // Is any file newer than the given file?
484 private bool anyNewerThan (in string[] files, SysTime t) {
485 // Experimental: running newerThan in separate threads, one per file
486 if (false) {
487 foreach (source; files) if (source.newerThan(t)) return true;
488 return false;
489 } else {
490 bool result;
491 foreach (source; taskPool.parallel(files)) if (!result && source.newerThan(t)) result = true;
492 return result;
498 * If force is true, returns true. Otherwise, if source and target both
499 * exist, returns true iff source's timeLastModified is strictly greater
500 * than target's. Otherwise, returns true.
502 private bool newerThan (string source, string target) {
503 if (force) return true;
504 yap("stat ", target);
505 return source.newerThan(timeLastModified(target, SysTime(0)));
509 private bool newerThan (string source, SysTime target) {
510 if (force) return true;
511 try {
512 yap("stat ", source);
513 return DirEntry(source).timeLastModified > target;
514 } catch (Exception) {
515 // File not there, consider it newer
516 return true;
521 ////////////////////////////////////////////////////////////////////////////////
522 private @property string helpString () {
523 return
524 "rgdc build "~thisVersion~"
525 Usage: rgdc [RDMD AND DMD OPTIONS]... program [PROGRAM OPTIONS]...
526 Builds (with dependents) and runs a D program.
527 Example: rgdc -release myprog --myprogparm 5
529 Any option to be passed to the compiler must occur before the program name. In
530 addition to compiler options, rgdc recognizes the following options:
531 --build-only just build the executable, don't run it
532 --chatty write compiler commands to stdout before executing them
533 --compiler=comp use the specified compiler
534 --dry-run do not compile, just show what commands would be run (implies --chatty)
535 --exclude=package exclude a package from the build (multiple --exclude allowed)
536 --force force a rebuild even if apparently not necessary
537 --help this message
538 --main add a stub main program to the mix (e.g. for unittesting)
539 --shebang rgdc is in a shebang line (put as first argument)
544 ////////////////////////////////////////////////////////////////////////////////
545 private @property string thisVersion () {
546 enum d = __DATE__;
547 enum month = d[0..3],
548 day = d[4] == ' ' ? "0"~d[5] : d[4..6],
549 year = d[7..$];
550 enum monthNum
551 = month == "Jan" ? "01"
552 : month == "Feb" ? "02"
553 : month == "Mar" ? "03"
554 : month == "Apr" ? "04"
555 : month == "May" ? "05"
556 : month == "Jun" ? "06"
557 : month == "Jul" ? "07"
558 : month == "Aug" ? "08"
559 : month == "Sep" ? "09"
560 : month == "Oct" ? "10"
561 : month == "Nov" ? "11"
562 : month == "Dec" ? "12"
563 : "";
564 static assert(month != "", "Unknown month "~month);
565 return year[0]~year[1..$]~monthNum~day;
569 private string which (string path) {
570 yap("which ", path);
571 if (path.canFind(dirSeparator) || altDirSeparator != "" && path.canFind(altDirSeparator)) return path;
572 string[] extensions = [""];
573 foreach (extension; extensions) {
574 foreach (envPath; environment["PATH"].splitter(pathSeparator)) {
575 string absPath = buildPath(envPath, path~extension);
576 yap("stat ", absPath);
577 if (exists(absPath) && isFile(absPath)) return absPath;
580 throw new FileException(path, "File not found in PATH");
584 private size_t indexOfProgram (string[] args) {
585 foreach(i, arg; args[1..$]) {
586 if (!arg.startsWith('-', '@') && !arg.endsWith(".o", ".a")) return i+1;
588 return args.length;
592 int main(string[] args) {
594 string[] convertSomeOptions (string[] args, string root) {
595 string[string] simpleReplace;
596 simpleReplace["-unittest"] = "-funittest";
597 simpleReplace["-main"] = "--main";
598 simpleReplace["-O"] = "-O2";
599 simpleReplace["-boundscheck=off"] = "-fno-bounds-check";
600 simpleReplace["-boundscheck=on"] = "-fbounds-check";
602 bool hasIDot, hasJDot;
603 int idxI = -1, idxJ = -1;
604 string rootDir = root.dirName.buildNormalizedPath;
605 if (rootDir.length == 0) rootDir ~= ".";
606 string ourI = "-I"~rootDir;
607 string ourJ = "-J"~rootDir;
609 debug writeln("before: ", args);
610 string[] res;
611 res ~= args[0];
612 foreach (int i, arg; args[1..$]) {
613 auto sr = arg in simpleReplace;
614 if (sr !is null) { res ~= *sr; continue; }
615 if (arg.startsWith("-debug") ||
616 arg.startsWith("-version")) {
617 res ~= "-f"~arg[1..$];
618 continue;
620 if (arg == "-inline") { res ~= ["-finline-small-functions", "-finline-functions"]; continue; }
621 if (arg == ourI) hasIDot = true;
622 else if (arg == ourJ) hasJDot = true;
623 if (idxI < 0 && arg.startsWith("-I")) idxI = i;
624 else if (idxJ < 0 && arg.startsWith("-J")) idxJ = i;
625 res ~= arg;
627 if (!res.canFind("-g") && !res.canFind("-s")) res = res~"-s";
628 if (!hasIDot) {
629 if (idxI < 0) idxI = res.length;
630 res = res[0..idxI]~[ourI]~res[idxI..$];
632 if (!hasJDot) {
633 if (idxJ < 0) idxJ = res.length;
634 res = res[0..idxJ]~[ourJ]~res[idxJ..$];
636 debug writeln("after: ", res);
637 return res;
640 //writeln("Invoked with: ", args);
641 if (args.length > 1 && args[1].startsWith("--shebang ", "--shebang=")) {
642 // multiple options wrapped in one
643 auto a = args[1]["--shebang ".length..$];
644 args = args[0..1]~std.string.split(a)~args[2..$];
647 // Continue parsing the command line; now get rgdc's own arguments
648 auto programPos = indexOfProgram(args);
649 assert(programPos > 0);
650 auto argsBeforeProgram = convertSomeOptions(args[0..programPos], (programPos < args.length ? args[programPos] : null));
652 bool bailout; // bailout set by functions called in getopt if program should exit
653 bool addStubMain; // set by --main
654 getopt(argsBeforeProgram,
655 std.getopt.config.caseSensitive,
656 std.getopt.config.passThrough,
657 "build-only", &buildOnly,
658 "chatty", &chatty,
659 "compiler", &compilerBinary,
660 "dry-run", &dryRun,
661 "exclude", &exclusions,
662 "force", &force,
663 "help", { writeln(helpString); bailout = true; },
664 "main", &addStubMain,
665 "show-commands", &optShowCommands,
666 "dynamic", { optDynamic = true; },
667 "static", { optDynamic = false; },
669 if (bailout) return 0;
670 if (dryRun) chatty = true; // dry-run implies chatty
672 // no code on command line => require a source file
673 if (programPos == args.length) {
674 write(helpString);
675 return 1;
678 auto
679 root = args[programPos].chomp(".d")~".d",
680 exeBasename = root.baseName(".d"),
681 exeDirname = root.dirName,
682 programArgs = args[programPos+1..$];
684 assert(argsBeforeProgram.length >= 1);
685 auto compilerFlags = argsBeforeProgram[1..$];
687 //TODO
688 bool lib = compilerFlags.canFind("-lib");
689 string outExt = (lib ? libExt : binExt);
691 //collectException(readRC(thisExePath.setExtension(".rc")));
692 foreach (fname; ["./rgdc.rc", "$(HOME)/.rgdc.rc", "/etc/rgdc.rc", "!"]) {
693 if (fname == "!") fname = thisExePath.setExtension(".rc");
694 else fname = expandString(fname);
695 try {
696 //writeln("trying '", fname, "'");
697 readRC(fname);
698 break;
699 } catch (Exception e) {
700 //writeln(" FAILED! ", e.msg);
704 // --build-only implies the user would like a binary in the program's directory
705 if (buildOnly && !exe) exe = exeDirname~dirSeparator;
707 if (exe && exe.endsWith(dirSeparator)) {
708 // user specified a directory, complete it to a file
709 exe = buildPath(exe, exeBasename)~outExt;
712 // Compute the object directory and ensure it exists
713 immutable workDir = getWorkPath(root, compilerFlags);
714 lockWorkPath(workDir); // will be released by the OS on process exit
715 string objDir = buildPath(workDir, "objs");
716 yap("mkdirRecurse ", objDir);
717 if (!dryRun) mkdirRecurse(objDir);
719 if (lib) {
720 // When building libraries, DMD does not generate object files.
721 // Instead, it uses the -od parameter as the location for the library file.
722 // Thus, override objDir (which is normally a temporary directory)
723 // to be the target output directory.
724 objDir = exe.dirName;
727 // Fetch dependencies
728 const myDeps = getDependencies(root, workDir, objDir, compilerFlags);
730 // Compute executable name, check for freshness, rebuild
732 We need to be careful about using -o. Normally the generated
733 executable is hidden in the unique directory workDir. But if the
734 user forces generation in a specific place by using -od or -of,
735 the time of the binary can't be used to check for freshness
736 because the user may change e.g. the compile option from one run
737 to the next, yet the generated binary's datetime stays the
738 same. In those cases, we'll use a dedicated file called ".built"
739 and placed in workDir. Upon a successful build, ".built" will be
740 touched. See also
741 http://d.puremagic.com/issues/show_bug.cgi?id=4814
743 string buildWitness;
744 SysTime lastBuildTime = SysTime.min;
745 if (exe) {
746 // user-specified exe name
747 buildWitness = buildPath(workDir, ".built");
748 if (!exe.newerThan(buildWitness)) {
749 // Both exe and buildWitness exist, and exe is older than
750 // buildWitness. This is the only situation in which we
751 // may NOT need to recompile.
752 lastBuildTime = buildWitness.timeLastModified(SysTime.min);
754 } else {
755 exe = buildPath(workDir, exeBasename)~outExt;
756 buildWitness = exe;
757 lastBuildTime = buildWitness.timeLastModified(SysTime.min);
760 // Have at it
761 if (chain(root.only, myDeps.byKey).array.anyNewerThan(lastBuildTime)) {
762 immutable result = rebuild(root, exe, workDir, objDir, myDeps, compilerFlags, addStubMain);
763 if (result) return result;
764 // Touch the build witness to track the build time
765 if (buildWitness != exe) {
766 yap("touch ", buildWitness);
767 std.file.write(buildWitness, "");
771 if (buildOnly) {
772 // Pretty much done!
773 return 0;
776 // release lock on workDir before launching the user's program
777 unlockWorkPath();
779 // run
780 return exec(exe~programArgs);