diff --git a/source/dub/internal/utils.d b/source/dub/internal/utils.d index b55b54b..bf25745 100644 --- a/source/dub/internal/utils.d +++ b/source/dub/internal/utils.d @@ -93,6 +93,22 @@ f.writePrettyJsonString(json); } +/// Performs a write->delete->rename sequence to atomically "overwrite" the destination file +void atomicWriteJsonFile(Path path, Json json) +{ + import std.random : uniform; + auto tmppath = path[0 .. $-1] ~ format("%s.%s.tmp", path.head, uniform(0, int.max)); + auto f = openFile(tmppath, FileMode.createTrunc); + scope (failure) { + f.close(); + removeFile(tmppath); + } + f.writePrettyJsonString(json); + f.close(); + if (existsFile(path)) removeFile(path); + moveFile(tmppath, path); +} + bool isPathFromZip(string p) { enforce(p.length > 0); return p[$-1] == '/'; diff --git a/source/dub/package_.d b/source/dub/package_.d index 1a808d2..0c1fb7c 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -598,6 +598,54 @@ private string determineVersionFromSCM(Path path) { + // On Windows, which is slow at running external processes, + // cache the version numbers that are determined using + // GIT to speed up the initialization phase. + version (Windows) { + import std.file : exists, readText; + + // quickly determine head commit without invoking GIT + string head_commit; + auto hpath = (path ~ ".git/HEAD").toNativeString(); + if (exists(hpath)) { + auto head_ref = readText(hpath).strip(); + if (head_ref.startsWith("ref: ")) { + auto rpath = (path ~ (".git/"~head_ref[5 .. $])).toNativeString(); + if (exists(rpath)) + head_commit = readText(rpath).strip(); + } + } + + // return the last determined version for that commit + // not that this is not always correct, most notably when + // a tag gets added/removed/changed and changes the outcome + // of the full version detection computation + auto vcachepath = path ~ ".dub/version.json"; + if (existsFile(vcachepath)) { + auto ver = jsonFromFile(vcachepath); + if (head_commit == ver["commit"].opt!string) + return ver["version"].get!string; + } + } + + // if no cache file or the HEAD commit changed, perform full detection + auto ret = determineVersionWithGIT(path); + + version (Windows) { + // update version cache file + if (head_commit.length) { + if (!existsFile(path ~".dub")) createDirectory(path ~ ".dub"); + atomicWriteJsonFile(vcachepath, Json(["commit": Json(head_commit), "version": Json(ret)])); + } + } + + return ret; +} + +// determines the version of a package that is stored in a GIT working copy +// by invoking the "git" executable +private string determineVersionWithGIT(Path path) +{ import std.process; import dub.semver;