Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache json db #2642

Merged
merged 8 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions source/dub/dub.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
58 changes: 57 additions & 1 deletion source/dub/generators/build.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
rtbo marked this conversation as resolved.
Show resolved Hide resolved

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;
Expand Down
8 changes: 7 additions & 1 deletion source/dub/generators/generator.d
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
2 changes: 2 additions & 0 deletions test/pr2642-cache-db/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dubhome/
pr2642-cache-db
Empty file added test/pr2642-cache-db/.no_test
Empty file.
2 changes: 2 additions & 0 deletions test/pr2642-cache-db/dub.sdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name "pr2642-cache-db";
targetType "executable";
108 changes: 108 additions & 0 deletions test/pr2642-cache-db/source/test_cache_db.d
Original file line number Diff line number Diff line change
@@ -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",
"[email protected]",
];
auto dubFetch = spawnProcess(fetchProgram, stdin, stdout, stderr, env);
wait(dubFetch);

const buildProgramLib = [
environment["DUB"],
"build",
"--build=debug",
"--config=lib",
"[email protected]",
];
auto dubBuild = spawnProcess(buildProgramLib, stdin, stdout, stderr, env);
wait(dubBuild);

const buildProgramExe = [
environment["DUB"],
"build",
"--build=debug",
"--config=exe",
"[email protected]",
];
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.");
}