diff --git a/rdmd.d b/rdmd.d index 98c2ef0db4..4ca39b380f 100755 --- a/rdmd.d +++ b/rdmd.d @@ -15,10 +15,13 @@ // Written in the D programming language. import std.algorithm, std.array, core.stdc.stdlib, std.datetime, - std.digest.md, std.exception, std.file, std.getopt, + std.digest.md, std.exception, std.getopt, std.parallelism, std.path, std.process, std.range, std.regex, std.stdio, std.string, std.typecons, std.typetuple; +// Globally import types and functions that don't need to be logged +import std.file : FileException, DirEntry, SpanMode, thisExePath, tempDir; + version (Posix) { enum objExt = ".o"; @@ -62,7 +65,7 @@ int main(string[] args) // Look for the D compiler rdmd invokes automatically in the same directory as rdmd // and fall back to using the one in your path otherwise. string compilerPath = buildPath(dirName(thisExePath()), defaultCompiler); - if (compilerPath.exists && compilerPath.isFile) + if (Filesystem.existsAsFile(compilerPath)) compiler = compilerPath; if (args.length > 1 && args[1].startsWith("--shebang ", "--shebang=")) @@ -229,9 +232,7 @@ int main(string[] args) immutable workDir = getWorkPath(root, compilerFlags); lockWorkPath(workDir); // will be released by the OS on process exit string objDir = buildPath(workDir, "objs"); - yap("mkdirRecurse ", objDir); - if (!dryRun) - mkdirRecurse(objDir); + Filesystem.mkdirRecurseIfLive(objDir); if (lib) { @@ -288,16 +289,14 @@ int main(string[] args) // Both exe and buildWitness exist, and exe is older than // buildWitness. This is the only situation in which we // may NOT need to recompile. - yap("stat ", buildWitness); - lastBuildTime = buildWitness.timeLastModified(SysTime.min); + lastBuildTime = Filesystem.timeLastModified(buildWitness, SysTime.min); } } else { exe = buildPath(workDir, exeBasename) ~ outExt; buildWitness = exe; - yap("stat ", buildWitness); - lastBuildTime = buildWitness.timeLastModified(SysTime.min); + lastBuildTime = Filesystem.timeLastModified(buildWitness, SysTime.min); } // Have at it @@ -310,11 +309,7 @@ int main(string[] args) // Touch the build witness to track the build time if (buildWitness != exe) - { - yap("touch ", buildWitness); - if (!dryRun) - std.file.write(buildWitness, ""); - } + Filesystem.touchEmptyFileIfLive(buildWitness); } if (buildOnly) @@ -391,9 +386,7 @@ private @property string myOwnTmpDir() else tmpRoot = tmpRoot.replace("/", dirSeparator).buildPath(".rdmd"); - yap("mkdirRecurse ", tmpRoot); - if (!dryRun) - mkdirRecurse(tmpRoot); + Filesystem.mkdirRecurseIfLive(tmpRoot); return tmpRoot; } @@ -423,9 +416,7 @@ private string getWorkPath(in string root, in string[] compilerFlags) workPath = buildPath(tmpRoot, "rdmd-" ~ baseName(root) ~ '-' ~ hash); - yap("mkdirRecurse ", workPath); - if (!dryRun) - mkdirRecurse(workPath); + Filesystem.mkdirRecurseIfLive(workPath); return workPath; } @@ -462,24 +453,18 @@ private int rebuild(string root, string fullExe, fullExe = fullExe.defaultExtension(".exe"); // Delete the old executable before we start building. - yap("stat ", fullExe); - if (exists(fullExe)) + if (Filesystem.exists(fullExe)) { - enforce(isFile(fullExe), fullExe ~ " is not a regular file"); - yap("rm ", fullExe); - if (!dryRun) + enforce(Filesystem.isFile(fullExe), fullExe ~ " is not a regular file"); + try + Filesystem.removeIfLive(fullExe); + catch (FileException) { - try - remove(fullExe); - catch (FileException e) - { - // This can occur on Windows if the executable is locked. - // Although we can't delete the file, we can still rename it. - auto oldExe = "%s.%s-%s.old".format(fullExe, - Clock.currTime.stdTime, thisProcessID); - yap("mv ", fullExe, " ", oldExe); - rename(fullExe, oldExe); - } + // This can occur on Windows if the executable is locked. + // Although we can't delete the file, we can still rename it. + auto oldExe = "%s.%s-%s.old".format(fullExe, + Clock.currTime.stdTime, thisProcessID); + Filesystem.rename(fullExe, oldExe); } } @@ -500,7 +485,7 @@ private int rebuild(string root, string fullExe, if (addStubMain) { auto stubMain = buildPath(myOwnTmpDir, "stubmain.d"); - std.file.write(stubMain, "void main(){}"); + Filesystem.write(stubMain, "void main(){}"); todo ~= [ stubMain ]; } return todo; @@ -517,7 +502,7 @@ private int rebuild(string root, string fullExe, // DMD uses Windows-style command-line parsing in response files // regardless of the operating system it's running on. - std.file.write(rspName, array(map!escapeWindowsArgument(todo)).join(" ")); + Filesystem.write(rspName, array(map!escapeWindowsArgument(todo)).join(" ")); todo = [ "@" ~ rspName ]; } @@ -526,25 +511,23 @@ private int rebuild(string root, string fullExe, if (result) { // build failed - if (exists(fullExeTemp)) - remove(fullExeTemp); + if (Filesystem.exists(fullExeTemp)) + Filesystem.remove(fullExeTemp); + return result; } // clean up the dir containing the object file, just not in dry // run mode because we haven't created any! if (!dryRun) { - yap("stat ", objDir); - if (objDir.exists && objDir.startsWith(workDir)) + if (Filesystem.exists(objDir) && objDir.startsWith(workDir)) { - yap("rmdirRecurse ", objDir); // We swallow the exception because of a potential race: two // concurrently-running scripts may attempt to remove this // directory. One will fail. - collectException(rmdirRecurse(objDir)); + collectException(Filesystem.rmdirRecurse(objDir)); } - yap("mv ", fullExeTemp, " ", fullExe); - rename(fullExeTemp, fullExe); + Filesystem.rename(fullExeTemp, fullExe); } return 0; } @@ -620,7 +603,7 @@ private string[string] getDependencies(string rootModule, string workDir, foreach (name; names) { auto path = buildPath(dir, name); - if (path.exists) + if (Filesystem.exists(path)) return absolutePath(path); } return null; @@ -658,8 +641,7 @@ private string[string] getDependencies(string rootModule, string workDir, 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) + if (Filesystem.exists(confFile)) { result[confFile] = null; } @@ -687,8 +669,7 @@ private string[string] getDependencies(string rootModule, string workDir, // Check if the old dependency file is fine if (!force) { - yap("stat ", depsFilename); - auto depsT = depsFilename.timeLastModified(SysTime.min); + auto depsT = Filesystem.timeLastModified(depsFilename, SysTime.min); if (depsT > SysTime.min) { // See if the deps file is still in good shape @@ -719,14 +700,14 @@ private string[string] getDependencies(string rootModule, string workDir, { // Delete the deps file on failure, we don't want to be fooled // by it next time we try - collectException(std.file.remove(depsFilename)); + collectException(Filesystem.remove(depsFilename)); } immutable depsExitCode = run(depsGetter, depsFilename); if (depsExitCode) { stderr.writefln("Failed: %s", depsGetter); - collectException(std.file.remove(depsFilename)); + collectException(Filesystem.remove(depsFilename)); exit(depsExitCode); } @@ -736,8 +717,7 @@ private string[string] getDependencies(string rootModule, string workDir, // Is any file newer than the given file? bool anyNewerThan(T)(T files, in string file) { - yap("stat ", file); - return files.anyNewerThan(file.timeLastModified); + return files.anyNewerThan(Filesystem.timeLastModified(file)); } // Is any file newer than the given file? @@ -763,8 +743,7 @@ than target's. Otherwise, returns true. private bool newerThan(string source, string target) { if (force) return true; - yap("stat ", target); - return source.newerThan(target.timeLastModified(SysTime.min)); + return source.newerThan(Filesystem.timeLastModified(target, SysTime.min)); } private bool newerThan(string source, SysTime target) @@ -772,8 +751,7 @@ private bool newerThan(string source, SysTime target) if (force) return true; try { - yap("stat ", source); - return DirEntry(source).timeLastModified > target; + return Filesystem.timeLastModified(DirEntry(source)) > target; } catch (Exception) { @@ -936,21 +914,19 @@ string makeEvalFile(string todo) auto srcfile = buildPath(pathname, "eval." ~ todo.md5Of.toHexString ~ ".d"); - if (force || !exists(srcfile)) + if (force || !Filesystem.exists(srcfile)) { - std.file.write(srcfile, todo); + Filesystem.write(srcfile, todo); } // Clean pathname enum lifetimeInHours = 24; auto cutoff = Clock.currTime() - dur!"hours"(lifetimeInHours); - yap("dirEntries ", pathname); - foreach (DirEntry d; dirEntries(pathname, SpanMode.shallow)) + foreach (DirEntry d; Filesystem.dirEntries(pathname, SpanMode.shallow)) { - yap("stat ", d.name); - if (d.timeLastModified < cutoff) + if (Filesystem.timeLastModified(d) < cutoff) { - collectException(std.file.remove(d.name)); + collectException(Filesystem.remove(d.name)); //break; // only one per call so we don't waste time } } @@ -993,8 +969,7 @@ string which(string path) foreach (extension; extensions) { string absPath = buildPath(envPath, path ~ extension); - yap("stat ", absPath); - if (exists(absPath) && isFile(absPath)) + if (Filesystem.existsAsFile(absPath)) return absPath; } } @@ -1007,3 +982,71 @@ void yap(size_t line = __LINE__, T...)(auto ref T stuff) debug stderr.writeln(line, ": ", stuff); else stderr.writeln(stuff); } + +/** +Used to wrap filesystem operations that should also be logged with the +yap function or affected by dryRun. Append the string "IfLive" to the end +of the function for it to be skipped during a dry run. + +These functions allow the filename to be given once so the log statement +always matches the operation. Using it also guarantees you won't forget to +include a `yap` alongside any file operation you want to be logged. +*/ +struct Filesystem +{ +static: + static auto opDispatch(string func, T...)(T args) + { + static if (func.endsWith("IfLive")) + { + enum fileFunc = func[0 .. $ - "IfLive".length]; + enum skipOnDryRun = true; + } + else + { + enum fileFunc = func; + enum skipOnDryRun = false; + } + + static if (fileFunc.among("exists", "timeLastModified", "isFile", "isDir", "existsAsFile")) + yap("stat ", args[0]); + else static if (fileFunc == "rename") + yap("mv ", args[0], " ", args[1]); + else static if (fileFunc.among("remove", "mkdirRecurse", "rmdirRecurse", "dirEntries", "write", "touchEmptyFile")) + yap(fileFunc, " ", args[0]); + else static assert(0, "Filesystem.opDispatch has not implemented " ~ fileFunc); + + static if (skipOnDryRun) + { + if (dryRun) + return; + } + mixin("return DirectFilesystem." ~ fileFunc ~ "(args);"); + } + + /** + Operates on the file system without logging its operations or being + affected by dryRun. + */ + static struct DirectFilesystem + { + static: + import file = std.file; + alias file this; + + /** + Update an empty file's timestamp. + */ + static void touchEmptyFile(string name) + { + file.write(name, ""); + } + /** + Returns true if name exists and is a file. + */ + static bool existsAsFile(string name) + { + return file.exists(name) && file.isFile(name); + } + } +}