diff --git a/source/dub/dub.d b/source/dub/dub.d index 28ada243e..87a78bc9f 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -1883,6 +1883,11 @@ private struct SpecialDirs { * project directory, but this led to issues with packages stored on * read-only file system / location, and lingering artifacts scattered * through the file system. + * + * Dub writes in the cache directory some Json description files + * of the available artifacts. These files are intended to be read by + * 3rd party software (e.g. Meson). The default cache location specified + * in this function should therefore not change across future Dub versions. */ NativePath cache; diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index 8583eab84..73fe18b52 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -285,12 +285,68 @@ class BuildGenerator : ProjectGenerator { buildWithCompiler(settings, cbuildsettings); target_binary_path = getTargetPath(cbuildsettings, settings); - if (!settings.tempBuild) + if (!settings.tempBuild) { copyTargetFile(target_path, buildsettings, settings); + updateCacheDatabase(settings, cbuildsettings, pack, config, build_id, target_binary_path.toNativeString()); + } return false; } + private void updateCacheDatabase(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, + string build_id, string target_binary_path) + { + import dub.internal.vibecompat.data.json; + import core.time : seconds; + + // Generate a `db.json` in the package version cache directory. + // This is read by 3rd party software (e.g. Meson) in order to find + // relevant build artifacts in Dub's cache. + + enum jsonFileName = "db.json"; + enum lockFileName = "db.lock"; + + const pkgCacheDir = packageCache(settings.cache, pack); + auto lock = lockFile((pkgCacheDir ~ lockFileName).toNativeString(), 3.seconds); + + const dbPath = pkgCacheDir ~ jsonFileName; + const dbPathStr = dbPath.toNativeString(); + Json db; + if (exists(dbPathStr)) { + const text = stripUTF8Bom(cast(string)readFile(dbPath)); + db = parseJsonString(text, dbPathStr); + enforce(db.type == Json.Type.array, "Expected a JSON array in " ~ dbPathStr); + } + else { + db = Json.emptyArray; + } + + foreach_reverse (entry; db) { + if (entry["buildId"].get!string == build_id) { + // duplicate + return; + } + } + + Json entry = Json.emptyObject; + + entry["architecture"] = serializeToJson(settings.platform.architecture); + entry["buildId"] = build_id; + entry["buildType"] = settings.buildType; + entry["compiler"] = settings.platform.compiler; + entry["compilerBinary"] = settings.platform.compilerBinary; + entry["compilerVersion"] = settings.platform.compilerVersion; + entry["configuration"] = config; + entry["package"] = pack.name; + entry["platform"] = serializeToJson(settings.platform.platform); + entry["targetBinaryPath"] = target_binary_path; + entry["version"] = pack.version_.toString(); + + db ~= entry; + + writeFile(dbPath, representation(db.toPrettyString())); + } + private void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path) { auto cwd = settings.toolWorkingDirectory; diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index 8ff77d0ec..3e0914b70 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -771,9 +771,15 @@ class ProjectGenerator /** * Compute and returns the path were artifacts are stored for a given package * - * Artifacts are usually stored in: + * Artifacts are stored in: * `$DUB_HOME/cache/$PKG_NAME/$PKG_VERSION[/+$SUB_PKG_NAME]/` * Note that the leading `+` in the sub-package name is to avoid any ambiguity. + * + * Dub writes in the returned path a Json description file of the available + * artifacts in this cache location. This Json file is read by 3rd party + * software (e.g. Meson). Returned path should therefore not change across + * future Dub versions. + * * Build artifacts are usually stored in a sub-folder named "build", * as their names are based on user-supplied values. * diff --git a/test/pr2642-cache-db/.gitignore b/test/pr2642-cache-db/.gitignore new file mode 100644 index 000000000..da01cd648 --- /dev/null +++ b/test/pr2642-cache-db/.gitignore @@ -0,0 +1,2 @@ +dubhome/ +pr2642-cache-db diff --git a/test/pr2642-cache-db/.no_test b/test/pr2642-cache-db/.no_test new file mode 100644 index 000000000..e69de29bb diff --git a/test/pr2642-cache-db/dub.sdl b/test/pr2642-cache-db/dub.sdl new file mode 100644 index 000000000..f9fca89bc --- /dev/null +++ b/test/pr2642-cache-db/dub.sdl @@ -0,0 +1,2 @@ +name "pr2642-cache-db"; +targetType "executable"; diff --git a/test/pr2642-cache-db/source/test_cache_db.d b/test/pr2642-cache-db/source/test_cache_db.d new file mode 100644 index 000000000..28a2438ac --- /dev/null +++ b/test/pr2642-cache-db/source/test_cache_db.d @@ -0,0 +1,108 @@ +module test_cache_db; + +import std.path; +import std.file; +import std.process; +import std.stdio; +import std.json; + +void main() +{ + const dubhome = __FILE_FULL_PATH__.dirName().dirName().buildNormalizedPath("dubhome"); + if (exists(dubhome)) + { + rmdirRecurse(dubhome); + } + + const string[string] env = [ + "DUB_HOME": dubhome, + ]; + const fetchProgram = [ + environment["DUB"], + "fetch", + "gitcompatibledubpackage@1.0.4", + ]; + auto dubFetch = spawnProcess(fetchProgram, stdin, stdout, stderr, env); + wait(dubFetch); + + const buildProgramLib = [ + environment["DUB"], + "build", + "--build=debug", + "--config=lib", + "gitcompatibledubpackage@1.0.4", + ]; + auto dubBuild = spawnProcess(buildProgramLib, stdin, stdout, stderr, env); + wait(dubBuild); + + const buildProgramExe = [ + environment["DUB"], + "build", + "--build=debug", + "--config=exe", + "gitcompatibledubpackage@1.0.4", + ]; + dubBuild = spawnProcess(buildProgramExe, stdin, stdout, stderr, env); + wait(dubBuild); + + scope (success) + { + // leave dubhome in the tree for analysis in case of failure + rmdirRecurse(dubhome); + } + + const buildDbPath = buildNormalizedPath(dubhome, "cache", "gitcompatibledubpackage", "1.0.4", "db.json"); + assert(exists(buildDbPath), buildDbPath ~ " should exist"); + const buildDbStr = readText(buildDbPath); + auto json = parseJSON(buildDbStr); + assert(json.type == JSONType.array, "build db should be an array"); + assert(json.array.length == 2, "build db should have 2 entries"); + + auto db = json.array[0].object; + + void assertArray(string field) + { + assert(field in db, "db.json should have an array field " ~ field); + assert(db[field].type == JSONType.array, "expected field " ~ field ~ " to be an array"); + } + + void assertString(string field, string value = null) + { + assert(field in db, "db.json should have an string field " ~ field); + assert(db[field].type == JSONType.string, "expected field " ~ field ~ " to be a string"); + if (value) + assert(db[field].str == value, "expected field " ~ field ~ " to equal " ~ value); + } + + assertArray("architecture"); + assertString("buildId"); + assertString("buildType", "debug"); + assertString("compiler"); + assertString("compilerBinary"); + assertString("compilerVersion"); + assertString("configuration", "lib"); + assertString("package", "gitcompatibledubpackage"); + assertArray("platform"); + assertString("targetBinaryPath"); + assertString("version", "1.0.4"); + + auto binName = db["targetBinaryPath"].str; + assert(isFile(binName), "expected " ~ binName ~ " to be a file."); + + db = json.array[1].object; + + assertArray("architecture"); + assertString("buildId"); + assertString("buildType", "debug"); + assertString("compiler"); + assertString("compilerBinary"); + assertString("compilerVersion"); + assertString("configuration", "exe"); + assertString("package", "gitcompatibledubpackage"); + assertArray("platform"); + assertString("targetBinaryPath"); + assertString("version", "1.0.4"); + + binName = db["targetBinaryPath"].str; + assert(isFile(binName), "expected " ~ binName ~ " to be a file."); +}