Skip to content

Commit

Permalink
Use "-i" to prevent rdmd from having to invoke compiler twice.
Browse files Browse the repository at this point in the history
  • Loading branch information
marler8997 committed Dec 16, 2017
1 parent 40b1efb commit d30eb1d
Showing 1 changed file with 140 additions and 147 deletions.
287 changes: 140 additions & 147 deletions rdmd.d
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ else
private bool chatty, buildOnly, dryRun, force, preserveOutputPaths;
private string exe, userTempDir;
immutable string[] defaultExclusions = ["std", "etc", "core"];
private string[] exclusions = defaultExclusions; // packages that are to be excluded
private string[] exclusions; // packages that are to be excluded
private string[] includes;
private string[] extraFiles = [];

version (DigitalMars)
Expand Down Expand Up @@ -139,7 +140,7 @@ int main(string[] args)
"eval", &eval,
"loop", &loop,
"exclude", &exclusions,
"include", (string opt, string p) { exclusions = exclusions.filter!(ex => ex != p).array(); },
"include", &includes,
"extra-file", &extraFiles,
"force", &force,
"help", { writeln(helpString); bailout = true; },
Expand Down Expand Up @@ -250,23 +251,8 @@ int main(string[] args)
}

// Fetch dependencies
const myDeps = getDependencies(root, workDir, objDir, compilerFlags);

// --makedepend mode. Just print dependencies and exit.
if (makeDepend)
{
writeDeps(exe, root, myDeps, stdout);
return 0;
}

// --makedepfile mode. Print dependencies to a file and continue.
// This is similar to GCC's -MF option, very useful to update the
// dependencies file and compile in one go:
// -include .deps.mak
// prog:
// rdmd --makedepfile=.deps.mak --build-only prog.d
if (makeDepFile !is null)
writeDeps(exe, root, myDeps, File(makeDepFile, "w"));
const depsFilename = buildPath(workDir, "rdmd.deps");
const initialDeps = tryGetDependencies(root, objDir, compilerFlags, depsFilename);

// Compute executable name, check for freshness, rebuild
/*
Expand All @@ -287,7 +273,7 @@ int main(string[] args)
{
// user-specified exe name
buildWitness = buildPath(workDir, ".built");
if (!exe.newerThan(buildWitness))
if (initialDeps !is null && !exe.newerThan(buildWitness))
{
// Both exe and buildWitness exist, and exe is older than
// buildWitness. This is the only situation in which we
Expand All @@ -300,18 +286,22 @@ int main(string[] args)
{
exe = buildPath(workDir, exeBasename) ~ outExt;
buildWitness = exe;
yap("stat ", buildWitness);
lastBuildTime = buildWitness.timeLastModified(SysTime.min);
if(initialDeps !is null)
{
yap("stat ", buildWitness);
lastBuildTime = buildWitness.timeLastModified(SysTime.min);
}
}

// Have at it
if (chain(root.only, myDeps.byKey).anyNewerThan(lastBuildTime))
if (initialDeps is null || chain(root.only, initialDeps.byKey).anyNewerThan(lastBuildTime))
{
immutable result = rebuild(root, exe, workDir, objDir,
myDeps, compilerFlags, addStubMain);
compilerFlags, depsFilename, addStubMain);
if (result)
return result;

// TODO: only touch build witness if build actually created output
// Touch the build witness to track the build time
if (buildWitness != exe)
{
Expand All @@ -321,6 +311,28 @@ int main(string[] args)
}
}

if(makeDepend || makeDepFile !is null)
{
auto resolvedDeps = (initialDeps is null) ? cast(const(string[string]))readDepsFile(objDir, depsFilename) : initialDeps;
assert(resolvedDeps !is null, "code bug: dependencies were not properly generated from the build");

// --makedepend mode. Just print dependencies and exit.
if (makeDepend)
{
writeDeps(exe, root, resolvedDeps, stdout);
return true; // exit program
}

// --makedepfile mode. Print dependencies to a file and continue.
// This is similar to GCC's -MF option, very useful to update the
// dependencies file and compile in one go:
// -include .deps.mak
// prog:
// rdmd --makedepfile=.deps.mak --build-only prog.d
if (makeDepFile !is null)
writeDeps(exe, root, resolvedDeps, File(makeDepFile, "w"));
}

if (buildOnly)
{
// Pretty much done!
Expand Down Expand Up @@ -459,8 +471,8 @@ private void unlockWorkPath()
// object file.

private int rebuild(string root, string fullExe,
string workDir, string objDir, in string[string] myDeps,
string[] compilerFlags, bool addStubMain)
string workDir, string objDir,
string[] compilerFlags, string depsFilename, bool addStubMain)
{
version (Windows)
fullExe = fullExe.defaultExtension(".exe");
Expand Down Expand Up @@ -495,10 +507,20 @@ private int rebuild(string root, string fullExe,
~ [ "-of" ~ fullExeTemp ]
~ [ "-od" ~ objDir ]
~ [ "-I" ~ dirName(root) ]
~ [ "-i" ]
~ [ "-v" ]
~ [ root ];
foreach (k, objectFile; myDeps) {
if(objectFile !is null)
todo ~= [ k ];
foreach(exclusion; exclusions)
{
todo ~= ("-i=-" ~ exclusion);
}
foreach(inclusion; includes)
{
todo ~= ("-i=" ~ inclusion);
}
foreach(extraFile; extraFiles)
{
todo ~= extraFile;
}
// Need to add void main(){}?
if (addStubMain)
Expand Down Expand Up @@ -526,7 +548,7 @@ private int rebuild(string root, string fullExe,
todo = [ "@" ~ rspName ];
}

immutable result = run([ compiler ] ~ todo);
immutable result = run([ compiler ] ~ todo, depsFilename);
if (result)
{
// build failed
Expand Down Expand Up @@ -588,106 +610,104 @@ private int exec(string[] args)
return run(args, null, true);
}

// Given module rootModule, returns a mapping of all dependees .d
// source filenames to their corresponding .o files sitting in
// directory workDir. The mapping is obtained by running dmd -v against
// rootModule.

private string[string] getDependencies(string rootModule, string workDir,
string objDir, string[] compilerFlags)
string[string] readDepsFile(string objDir, string depsFilename)
{
immutable depsFilename = buildPath(workDir, "rdmd.deps");

string[string] readDepsFile()
string d2obj(string dfile)
{
return buildPath(objDir, dfile.baseName.chomp(".d") ~ objExt);
}
string findLib(string libName)
{
string d2obj(string dfile)
// This can't be 100% precise without knowing exactly where the linker
// will look for libraries (which requires, but is not limited to,
// parsing the linker's command line (as specified in dmd.conf/sc.ini).
// Go for best-effort instead.
string[] dirs = ["."];
foreach (envVar; ["LIB", "LIBRARY_PATH", "LD_LIBRARY_PATH"])
dirs ~= environment.get(envVar, "").split(pathSeparator);
version (Windows)
string[] names = [libName ~ ".lib"];
else
{
return buildPath(objDir, dfile.baseName.chomp(".d") ~ objExt);
string[] names = ["lib" ~ libName ~ ".a", "lib" ~ libName ~ ".so"];
dirs ~= ["/lib", "/usr/lib"];
}
string findLib(string libName)
{
// This can't be 100% precise without knowing exactly where the linker
// will look for libraries (which requires, but is not limited to,
// parsing the linker's command line (as specified in dmd.conf/sc.ini).
// Go for best-effort instead.
string[] dirs = ["."];
foreach (envVar; ["LIB", "LIBRARY_PATH", "LD_LIBRARY_PATH"])
dirs ~= environment.get(envVar, "").split(pathSeparator);
version (Windows)
string[] names = [libName ~ ".lib"];
else
foreach (dir; dirs)
foreach (name; names)
{
string[] names = ["lib" ~ libName ~ ".a", "lib" ~ libName ~ ".so"];
dirs ~= ["/lib", "/usr/lib"];
auto path = buildPath(dir, name);
if (path.exists)
return absolutePath(path);
}
foreach (dir; dirs)
foreach (name; names)
{
auto path = buildPath(dir, name);
if (path.exists)
return absolutePath(path);
}
return null;
}
yap("read ", depsFilename);
auto depsReader = File(depsFilename);
scope(exit) collectException(depsReader.close()); // don't care for errors

// Fetch all dependencies and append them to myDeps
auto pattern = ctRegex!(r"^(import|file|binary|config|library)\s+([^\(]+)\(?([^\)]*)\)?\s*$");
string[string] result;
foreach (string line; lines(depsReader))
return null;
}
yap("read ", depsFilename);
auto depsReader = File(depsFilename);
scope(exit) collectException(depsReader.close()); // don't care for errors

// Fetch all dependencies and append them to myDeps
auto pattern = ctRegex!(r"^(import|file|binary|config|library)\s+([^\(]+)\(?([^\)]*)\)?\s*$");
string[string] result;
foreach (string line; lines(depsReader))
{
auto regexMatch = match(line, pattern);
if (regexMatch.empty) continue;
auto captures = regexMatch.captures;
switch(captures[1])
{
auto regexMatch = match(line, pattern);
if (regexMatch.empty) continue;
auto captures = regexMatch.captures;
switch(captures[1])
case "import":
immutable moduleName = captures[2].strip(), moduleSrc = captures[3].strip();
if (inALibrary(moduleName, moduleSrc)) continue;
immutable moduleObj = d2obj(moduleSrc);
result[moduleSrc] = moduleObj;
break;

case "file":
result[captures[3].strip()] = null;
break;

case "binary":
result[which(captures[2].strip())] = null;
break;

case "config":
auto confFile = captures[2].strip;
// The config file is special: if missing, that's fine too. So
// add it as a dependency only if it actually exists.
yap("stat ", confFile);
if (confFile.exists)
{
case "import":
immutable moduleName = captures[2].strip(), moduleSrc = captures[3].strip();
if (inALibrary(moduleName, moduleSrc)) continue;
immutable moduleObj = d2obj(moduleSrc);
result[moduleSrc] = moduleObj;
break;

case "file":
result[captures[3].strip()] = null;
break;

case "binary":
result[which(captures[2].strip())] = null;
break;

case "config":
auto confFile = captures[2].strip;
// The config file is special: if missing, that's fine too. So
// add it as a dependency only if it actually exists.
yap("stat ", confFile);
if (confFile.exists)
{
result[confFile] = null;
}
break;

case "library":
immutable libName = captures[2].strip();
immutable libPath = findLib(libName);
if (libPath.ptr)
{
yap("library ", libName, " ", libPath);
result[libPath] = null;
}
break;
result[confFile] = null;
}
break;

default: assert(0);
case "library":
immutable libName = captures[2].strip();
immutable libPath = findLib(libName);
if (libPath.ptr)
{
yap("library ", libName, " ", libPath);
result[libPath] = null;
}
break;

default: assert(0);
}
// All dependencies specified through --extra-file
foreach (immutable moduleSrc; extraFiles)
result[moduleSrc] = d2obj(moduleSrc);
return result;
}
// All dependencies specified through --extra-file
foreach (immutable moduleSrc; extraFiles)
result[moduleSrc] = d2obj(moduleSrc);
return result;
}

// Given module rootModule, returns a mapping of all dependees .d
// source filenames to their corresponding .o files sitting in
// directory workDir. The mapping is obtained by reading the 'rdmd.deps' file
// that would have been generated on a previous build.

private string[string] tryGetDependencies(string rootModule,
string objDir, string[] compilerFlags, string depsFilename)
{
// Check if the old dependency file is fine
if (!force)
{
Expand All @@ -696,45 +716,18 @@ private string[string] getDependencies(string rootModule, string workDir,
if (depsT > SysTime.min)
{
// See if the deps file is still in good shape
auto deps = readDepsFile();
auto deps = readDepsFile(objDir, depsFilename);
auto allDeps = chain(rootModule.only, deps.byKey);
bool mustRebuildDeps = allDeps.anyNewerThan(depsT);
if (!mustRebuildDeps)
{
// Cool, we're in good shape
// Dependencies are up to date, return them
return deps;
}
// Fall through to rebuilding the deps file
}
}

immutable rootDir = dirName(rootModule);

// Filter out -lib. With -o-, it will create an empty library file.
compilerFlags = compilerFlags.filter!(flag => flag != "-lib").array();

// Collect dependencies
auto depsGetter =
// "cd " ~ shellQuote(rootDir) ~ " && "
[ compiler ] ~ compilerFlags ~
["-v", "-o-", rootModule, "-I" ~ rootDir];

scope(failure)
{
// Delete the deps file on failure, we don't want to be fooled
// by it next time we try
collectException(std.file.remove(depsFilename));
}

immutable depsExitCode = run(depsGetter, depsFilename);
if (depsExitCode)
{
stderr.writefln("Failed: %s", depsGetter);
collectException(std.file.remove(depsFilename));
exit(depsExitCode);
}

return dryRun ? null : readDepsFile();
return null; // dependendies are stale or have not been evaluated
}

// Is any file newer than the given file?
Expand Down

0 comments on commit d30eb1d

Please sign in to comment.