[M5+] drop curated linkdb JSON; SQLite overlay is the single source
This commit is contained in:
@@ -1,195 +0,0 @@
|
||||
module;
|
||||
|
||||
#include <json.hpp>
|
||||
|
||||
module cargoxx.linkdb;
|
||||
|
||||
import std;
|
||||
import cargoxx.util;
|
||||
|
||||
#ifndef CARGOXX_LINKDB_DEFAULT_PATH
|
||||
#define CARGOXX_LINKDB_DEFAULT_PATH ""
|
||||
#endif
|
||||
|
||||
namespace cargoxx::linkdb {
|
||||
|
||||
namespace {
|
||||
|
||||
auto curated_source_error(std::string msg) -> util::Error {
|
||||
return util::Error{
|
||||
util::ErrorCode::LinkdbCorrupt, std::move(msg), "", std::nullopt, std::nullopt,
|
||||
};
|
||||
}
|
||||
|
||||
auto load_curated(const std::filesystem::path& path)
|
||||
-> util::Result<std::map<std::string, std::vector<detail::CuratedRecipe>>> {
|
||||
std::ifstream in{path};
|
||||
if (!in) {
|
||||
return std::unexpected(curated_source_error(
|
||||
std::format("cannot open curated linkdb at '{}'", path.string())));
|
||||
}
|
||||
|
||||
nlohmann::json j;
|
||||
try {
|
||||
in >> j;
|
||||
} catch (const nlohmann::json::parse_error& e) {
|
||||
return std::unexpected(curated_source_error(
|
||||
std::format("curated linkdb is not valid JSON: {}", e.what())));
|
||||
}
|
||||
|
||||
if (!j.is_object() || !j.contains("packages") || !j["packages"].is_object()) {
|
||||
return std::unexpected(
|
||||
curated_source_error("curated linkdb missing top-level 'packages' object"));
|
||||
}
|
||||
|
||||
std::map<std::string, std::vector<detail::CuratedRecipe>> out;
|
||||
for (const auto& [name, recipes] : j["packages"].items()) {
|
||||
if (!recipes.is_array()) {
|
||||
return std::unexpected(curated_source_error(
|
||||
std::format("packages.{} must be an array", name)));
|
||||
}
|
||||
std::vector<detail::CuratedRecipe> bucket;
|
||||
bucket.reserve(recipes.size());
|
||||
for (const auto& r : recipes) {
|
||||
detail::CuratedRecipe rec;
|
||||
try {
|
||||
rec.version_range = r.at("version").get<std::string>();
|
||||
rec.nixpkgs_attr = r.at("nixpkgs_attr").get<std::string>();
|
||||
rec.find_package = r.at("find_package").get<std::string>();
|
||||
rec.targets = r.at("targets").get<std::vector<std::string>>();
|
||||
} catch (const nlohmann::json::exception& e) {
|
||||
return std::unexpected(curated_source_error(std::format(
|
||||
"packages.{} has a malformed recipe: {}", name, e.what())));
|
||||
}
|
||||
if (r.contains("components") && r["components"] == "supported") {
|
||||
rec.components_supported = true;
|
||||
}
|
||||
bucket.push_back(std::move(rec));
|
||||
}
|
||||
out.emplace(name, std::move(bucket));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
auto resolve_curated(const std::map<std::string, std::vector<detail::CuratedRecipe>>& curated,
|
||||
const std::string& package, const std::string& version,
|
||||
const std::vector<std::string>& components) -> util::Result<Recipe> {
|
||||
auto it = curated.find(package);
|
||||
if (it == curated.end() || it->second.empty()) {
|
||||
return std::unexpected(util::Error{
|
||||
util::ErrorCode::LinkdbUnknownPackage,
|
||||
std::format("package '{}' has no known CMake link recipe", package),
|
||||
"file an issue at <repo>/issues/new, or add a manual recipe via cargoxx linkdb add",
|
||||
std::nullopt, std::nullopt,
|
||||
});
|
||||
}
|
||||
|
||||
const detail::CuratedRecipe* match = nullptr;
|
||||
for (const auto& r : it->second) {
|
||||
if (util::satisfies(version, r.version_range)) {
|
||||
match = &r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
return std::unexpected(util::Error{
|
||||
util::ErrorCode::LinkdbUnknownPackage,
|
||||
std::format("no curated recipe for {} {} matches", package, version),
|
||||
"", std::nullopt, std::nullopt,
|
||||
});
|
||||
}
|
||||
|
||||
if (match->components_supported && components.empty()) {
|
||||
return std::unexpected(util::Error{
|
||||
util::ErrorCode::LinkdbComponentNotSupported,
|
||||
std::format("package '{}' requires at least one component", package),
|
||||
"specify components in Cargoxx.toml: { version = \"...\", components = [\"...\"] }",
|
||||
std::nullopt, std::nullopt,
|
||||
});
|
||||
}
|
||||
if (!match->components_supported && !components.empty()) {
|
||||
return std::unexpected(util::Error{
|
||||
util::ErrorCode::LinkdbComponentNotSupported,
|
||||
std::format("package '{}' does not declare component support", package),
|
||||
"", std::nullopt, std::nullopt,
|
||||
});
|
||||
}
|
||||
|
||||
return Recipe{
|
||||
.nixpkgs_attr = match->nixpkgs_attr,
|
||||
.find_package = substitute_components(match->find_package, components),
|
||||
.targets = expand_targets(match->targets, components),
|
||||
.source = "curated",
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto default_overlay_path() -> std::filesystem::path {
|
||||
namespace fs = std::filesystem;
|
||||
if (auto* xdg = std::getenv("XDG_CACHE_HOME"); xdg && *xdg) {
|
||||
return fs::path{xdg} / "cargoxx" / "linkdb.sqlite";
|
||||
}
|
||||
if (auto* home = std::getenv("HOME"); home && *home) {
|
||||
return fs::path{home} / ".cache" / "cargoxx" / "linkdb.sqlite";
|
||||
}
|
||||
return fs::current_path() / ".cargoxx-linkdb.sqlite";
|
||||
}
|
||||
|
||||
auto Database::open(std::optional<std::filesystem::path> overlay_path) -> util::Result<Database> {
|
||||
Database db;
|
||||
|
||||
auto curated = load_curated(CARGOXX_LINKDB_DEFAULT_PATH);
|
||||
if (!curated) {
|
||||
return std::unexpected(curated.error());
|
||||
}
|
||||
db.curated_ = std::move(*curated);
|
||||
|
||||
auto path = overlay_path.value_or(default_overlay_path());
|
||||
auto handle = detail::overlay_open(path);
|
||||
if (!handle) {
|
||||
return std::unexpected(handle.error());
|
||||
}
|
||||
db.overlay_ = std::move(*handle);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
auto Database::resolve(const std::string& package, const std::string& version,
|
||||
const std::vector<std::string>& components) -> util::Result<Recipe> {
|
||||
if (overlay_) {
|
||||
auto rows = detail::overlay_query(*overlay_, package);
|
||||
if (!rows) {
|
||||
return std::unexpected(rows.error());
|
||||
}
|
||||
const auto now = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
for (const auto& row : *rows) {
|
||||
if (!detail::overlay_is_fresh(row, now)) {
|
||||
continue;
|
||||
}
|
||||
if (!util::satisfies(version, row.version_range)) {
|
||||
continue;
|
||||
}
|
||||
if (!components.empty()) {
|
||||
return std::unexpected(util::Error{
|
||||
util::ErrorCode::LinkdbComponentNotSupported,
|
||||
std::format("overlay recipe for '{}' does not support components", package),
|
||||
"", std::nullopt, std::nullopt,
|
||||
});
|
||||
}
|
||||
return Recipe{
|
||||
.nixpkgs_attr = row.nixpkgs_attr,
|
||||
.find_package = row.find_package,
|
||||
.targets = row.targets,
|
||||
.source = row.source,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return resolve_curated(curated_, package, version, components);
|
||||
}
|
||||
|
||||
} // namespace cargoxx::linkdb
|
||||
78
src/linkdb/database.cpp
Normal file
78
src/linkdb/database.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
module cargoxx.linkdb;
|
||||
|
||||
import std;
|
||||
import cargoxx.util;
|
||||
|
||||
namespace cargoxx::linkdb {
|
||||
|
||||
auto default_overlay_path() -> std::filesystem::path {
|
||||
namespace fs = std::filesystem;
|
||||
if (auto* xdg = std::getenv("XDG_CACHE_HOME"); xdg && *xdg) {
|
||||
return fs::path{xdg} / "cargoxx" / "linkdb.sqlite";
|
||||
}
|
||||
if (auto* home = std::getenv("HOME"); home && *home) {
|
||||
return fs::path{home} / ".cache" / "cargoxx" / "linkdb.sqlite";
|
||||
}
|
||||
return fs::current_path() / ".cargoxx-linkdb.sqlite";
|
||||
}
|
||||
|
||||
auto Database::open(std::optional<std::filesystem::path> overlay_path)
|
||||
-> util::Result<Database> {
|
||||
Database db;
|
||||
auto path = overlay_path.value_or(default_overlay_path());
|
||||
auto handle = detail::overlay_open(path);
|
||||
if (!handle) {
|
||||
return std::unexpected(handle.error());
|
||||
}
|
||||
db.overlay_ = std::move(*handle);
|
||||
return db;
|
||||
}
|
||||
|
||||
auto Database::resolve(const std::string& package, const std::string& version,
|
||||
const std::vector<std::string>& components)
|
||||
-> util::Result<Recipe> {
|
||||
if (!overlay_) {
|
||||
return std::unexpected(util::Error{
|
||||
util::ErrorCode::Internal, "no overlay database is open", "",
|
||||
std::nullopt, std::nullopt,
|
||||
});
|
||||
}
|
||||
auto rows = detail::overlay_query(*overlay_, package);
|
||||
if (!rows) {
|
||||
return std::unexpected(rows.error());
|
||||
}
|
||||
const auto now = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
for (const auto& row : *rows) {
|
||||
if (!detail::overlay_is_fresh(row, now)) {
|
||||
continue;
|
||||
}
|
||||
if (!util::satisfies(version, row.version_range)) {
|
||||
continue;
|
||||
}
|
||||
if (!components.empty()) {
|
||||
return std::unexpected(util::Error{
|
||||
util::ErrorCode::LinkdbComponentNotSupported,
|
||||
std::format("overlay recipe for '{}' does not support components",
|
||||
package),
|
||||
"", std::nullopt, std::nullopt,
|
||||
});
|
||||
}
|
||||
return Recipe{
|
||||
.nixpkgs_attr = row.nixpkgs_attr,
|
||||
.find_package = row.find_package,
|
||||
.targets = row.targets,
|
||||
.source = row.source,
|
||||
};
|
||||
}
|
||||
return std::unexpected(util::Error{
|
||||
util::ErrorCode::LinkdbUnknownPackage,
|
||||
std::format("package '{}' has no known CMake link recipe", package),
|
||||
"run `cargoxx add {0}` to discover and verify a recipe via the resolver, "
|
||||
"or add a manual recipe via `cargoxx linkdb add`",
|
||||
std::nullopt, std::nullopt,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace cargoxx::linkdb
|
||||
@@ -22,14 +22,6 @@ struct Recipe {
|
||||
|
||||
namespace cargoxx::linkdb::detail {
|
||||
|
||||
struct CuratedRecipe {
|
||||
std::string version_range;
|
||||
std::string nixpkgs_attr;
|
||||
std::string find_package;
|
||||
std::vector<std::string> targets;
|
||||
bool components_supported = false;
|
||||
};
|
||||
|
||||
struct OverlayRow {
|
||||
std::string version_range;
|
||||
std::string nixpkgs_attr;
|
||||
@@ -140,7 +132,6 @@ class Database {
|
||||
|
||||
private:
|
||||
Database() = default;
|
||||
std::map<std::string, std::vector<detail::CuratedRecipe>> curated_;
|
||||
std::unique_ptr<detail::OverlayState> overlay_;
|
||||
};
|
||||
|
||||
@@ -150,11 +141,4 @@ class Database {
|
||||
// <cwd>/.cargoxx-linkdb.sqlite (final fallback)
|
||||
auto default_overlay_path() -> std::filesystem::path;
|
||||
|
||||
// Pure helpers exported for unit testing.
|
||||
auto substitute_components(std::string find_package, const std::vector<std::string>& components)
|
||||
-> std::string;
|
||||
|
||||
auto expand_targets(const std::vector<std::string>& templates,
|
||||
const std::vector<std::string>& components) -> std::vector<std::string>;
|
||||
|
||||
} // namespace cargoxx::linkdb
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
module cargoxx.linkdb;
|
||||
|
||||
import std;
|
||||
|
||||
namespace cargoxx::linkdb {
|
||||
|
||||
namespace {
|
||||
|
||||
auto replace_all(std::string s, std::string_view marker, std::string_view value) -> std::string {
|
||||
std::string out;
|
||||
out.reserve(s.size());
|
||||
std::size_t pos = 0;
|
||||
while (pos < s.size()) {
|
||||
auto next = s.find(marker, pos);
|
||||
if (next == std::string::npos) {
|
||||
out.append(s, pos);
|
||||
break;
|
||||
}
|
||||
out.append(s, pos, next - pos);
|
||||
out.append(value);
|
||||
pos = next + marker.size();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto substitute_components(std::string find_package, const std::vector<std::string>& components)
|
||||
-> std::string {
|
||||
std::string joined;
|
||||
for (std::size_t i = 0; i < components.size(); ++i) {
|
||||
if (i > 0) {
|
||||
joined += ' ';
|
||||
}
|
||||
joined += components[i];
|
||||
}
|
||||
return replace_all(std::move(find_package), "{{components}}", joined);
|
||||
}
|
||||
|
||||
auto expand_targets(const std::vector<std::string>& templates,
|
||||
const std::vector<std::string>& components) -> std::vector<std::string> {
|
||||
std::vector<std::string> out;
|
||||
for (const auto& t : templates) {
|
||||
if (t.find("{{component}}") != std::string::npos) {
|
||||
for (const auto& c : components) {
|
||||
out.push_back(replace_all(t, "{{component}}", c));
|
||||
}
|
||||
} else {
|
||||
out.push_back(t);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace cargoxx::linkdb
|
||||
Reference in New Issue
Block a user