[M5+] drop curated linkdb JSON; SQLite overlay is the single source
This commit is contained in:
@@ -35,9 +35,6 @@ find_package(reproc REQUIRED)
|
|||||||
# ----- cargoxx library: module units + implementation units -----
|
# ----- cargoxx library: module units + implementation units -----
|
||||||
add_library(cargoxx STATIC)
|
add_library(cargoxx STATIC)
|
||||||
target_include_directories(cargoxx SYSTEM PRIVATE third_party)
|
target_include_directories(cargoxx SYSTEM PRIVATE third_party)
|
||||||
target_compile_definitions(cargoxx PRIVATE
|
|
||||||
CARGOXX_LINKDB_DEFAULT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/data/linkdb.json"
|
|
||||||
)
|
|
||||||
target_sources(cargoxx
|
target_sources(cargoxx
|
||||||
PRIVATE
|
PRIVATE
|
||||||
src/util/error.cpp
|
src/util/error.cpp
|
||||||
@@ -46,8 +43,7 @@ target_sources(cargoxx
|
|||||||
src/manifest/writer.cpp
|
src/manifest/writer.cpp
|
||||||
src/layout/layout.cpp
|
src/layout/layout.cpp
|
||||||
src/lockfile/lockfile.cpp
|
src/lockfile/lockfile.cpp
|
||||||
src/linkdb/recipe.cpp
|
src/linkdb/database.cpp
|
||||||
src/linkdb/curated.cpp
|
|
||||||
src/linkdb/overlay.cpp
|
src/linkdb/overlay.cpp
|
||||||
src/codegen/flake.cpp
|
src/codegen/flake.cpp
|
||||||
src/codegen/cmake.cpp
|
src/codegen/cmake.cpp
|
||||||
|
|||||||
212
data/linkdb.json
212
data/linkdb.json
@@ -1,212 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"packages": {
|
|
||||||
"fmt": [
|
|
||||||
{
|
|
||||||
"version": ">=10.0.0",
|
|
||||||
"nixpkgs_attr": "fmt_10",
|
|
||||||
"find_package": "fmt CONFIG REQUIRED",
|
|
||||||
"targets": ["fmt::fmt"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": ">=8.0.0,<10.0.0",
|
|
||||||
"nixpkgs_attr": "fmt_8",
|
|
||||||
"find_package": "fmt CONFIG REQUIRED",
|
|
||||||
"targets": ["fmt::fmt"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"spdlog": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "spdlog",
|
|
||||||
"find_package": "spdlog CONFIG REQUIRED",
|
|
||||||
"targets": ["spdlog::spdlog"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nlohmann_json": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "nlohmann_json",
|
|
||||||
"find_package": "nlohmann_json CONFIG REQUIRED",
|
|
||||||
"targets": ["nlohmann_json::nlohmann_json"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"boost": [
|
|
||||||
{
|
|
||||||
"version": ">=1.70.0",
|
|
||||||
"nixpkgs_attr": "boost",
|
|
||||||
"find_package": "Boost REQUIRED",
|
|
||||||
"targets": ["Boost::headers"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"openssl": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "openssl",
|
|
||||||
"find_package": "OpenSSL REQUIRED",
|
|
||||||
"targets": ["OpenSSL::SSL", "OpenSSL::Crypto"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"zlib": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "zlib",
|
|
||||||
"find_package": "ZLIB REQUIRED",
|
|
||||||
"targets": ["ZLIB::ZLIB"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sqlite3": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "sqlite",
|
|
||||||
"find_package": "SQLite3 REQUIRED",
|
|
||||||
"targets": ["SQLite::SQLite3"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"curl": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "curl",
|
|
||||||
"find_package": "CURL REQUIRED",
|
|
||||||
"targets": ["CURL::libcurl"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"protobuf": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "protobuf",
|
|
||||||
"find_package": "Protobuf REQUIRED",
|
|
||||||
"targets": ["protobuf::libprotobuf"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grpc": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "grpc",
|
|
||||||
"find_package": "gRPC CONFIG REQUIRED",
|
|
||||||
"targets": ["gRPC::grpc++"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"abseil-cpp": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "abseil-cpp",
|
|
||||||
"find_package": "absl CONFIG REQUIRED",
|
|
||||||
"targets": ["absl::{{component}}"],
|
|
||||||
"components": "supported"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"gtest": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "gtest",
|
|
||||||
"find_package": "GTest CONFIG REQUIRED",
|
|
||||||
"targets": ["GTest::gtest", "GTest::gtest_main"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"catch2": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "catch2_3",
|
|
||||||
"find_package": "Catch2 CONFIG REQUIRED",
|
|
||||||
"targets": ["Catch2::Catch2WithMain"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"eigen": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "eigen",
|
|
||||||
"find_package": "Eigen3 CONFIG REQUIRED",
|
|
||||||
"targets": ["Eigen3::Eigen"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tbb": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "tbb",
|
|
||||||
"find_package": "TBB CONFIG REQUIRED",
|
|
||||||
"targets": ["TBB::tbb"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"libpng": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "libpng",
|
|
||||||
"find_package": "PNG REQUIRED",
|
|
||||||
"targets": ["PNG::PNG"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"libjpeg": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "libjpeg",
|
|
||||||
"find_package": "JPEG REQUIRED",
|
|
||||||
"targets": ["JPEG::JPEG"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"freetype": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "freetype",
|
|
||||||
"find_package": "Freetype REQUIRED",
|
|
||||||
"targets": ["Freetype::Freetype"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"glfw": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "glfw",
|
|
||||||
"find_package": "glfw3 CONFIG REQUIRED",
|
|
||||||
"targets": ["glfw"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"glm": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "glm",
|
|
||||||
"find_package": "glm CONFIG REQUIRED",
|
|
||||||
"targets": ["glm::glm"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sdl2": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "SDL2",
|
|
||||||
"find_package": "SDL2 CONFIG REQUIRED",
|
|
||||||
"targets": ["SDL2::SDL2"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cli11": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "cli11",
|
|
||||||
"find_package": "CLI11 CONFIG REQUIRED",
|
|
||||||
"targets": ["CLI11::CLI11"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cxxopts": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "cxxopts",
|
|
||||||
"find_package": "cxxopts CONFIG REQUIRED",
|
|
||||||
"targets": ["cxxopts::cxxopts"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"range-v3": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "range-v3",
|
|
||||||
"find_package": "range-v3 CONFIG REQUIRED",
|
|
||||||
"targets": ["range-v3::range-v3"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"magic_enum": [
|
|
||||||
{
|
|
||||||
"version": "*",
|
|
||||||
"nixpkgs_attr": "magic-enum",
|
|
||||||
"find_package": "magic_enum CONFIG REQUIRED",
|
|
||||||
"targets": ["magic_enum::magic_enum"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {
|
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 {
|
struct OverlayRow {
|
||||||
std::string version_range;
|
std::string version_range;
|
||||||
std::string nixpkgs_attr;
|
std::string nixpkgs_attr;
|
||||||
@@ -140,7 +132,6 @@ class Database {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Database() = default;
|
Database() = default;
|
||||||
std::map<std::string, std::vector<detail::CuratedRecipe>> curated_;
|
|
||||||
std::unique_ptr<detail::OverlayState> overlay_;
|
std::unique_ptr<detail::OverlayState> overlay_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,11 +141,4 @@ class Database {
|
|||||||
// <cwd>/.cargoxx-linkdb.sqlite (final fallback)
|
// <cwd>/.cargoxx-linkdb.sqlite (final fallback)
|
||||||
auto default_overlay_path() -> std::filesystem::path;
|
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
|
} // 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
|
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
import cargoxx.cli;
|
import cargoxx.cli;
|
||||||
|
import cargoxx.linkdb;
|
||||||
import cargoxx.manifest;
|
import cargoxx.manifest;
|
||||||
import cargoxx.util;
|
import cargoxx.util;
|
||||||
import std;
|
import std;
|
||||||
|
|
||||||
using cargoxx::cli::cmd_add;
|
using cargoxx::cli::cmd_add;
|
||||||
using cargoxx::cli::cmd_new;
|
using cargoxx::cli::cmd_new;
|
||||||
|
using cargoxx::linkdb::Database;
|
||||||
|
using cargoxx::linkdb::Recipe;
|
||||||
using cargoxx::util::ErrorCode;
|
using cargoxx::util::ErrorCode;
|
||||||
namespace manifest = cargoxx::manifest;
|
namespace manifest = cargoxx::manifest;
|
||||||
|
|
||||||
@@ -36,11 +39,25 @@ auto scaffold(const std::filesystem::path& parent) -> std::filesystem::path {
|
|||||||
return parent / "app";
|
return parent / "app";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto seed_fmt(const std::filesystem::path& overlay) {
|
||||||
|
auto db = Database::open(overlay);
|
||||||
|
REQUIRE(db.has_value());
|
||||||
|
REQUIRE(db->add_manual("fmt", "*",
|
||||||
|
Recipe{
|
||||||
|
.nixpkgs_attr = "fmt_10",
|
||||||
|
.find_package = "fmt CONFIG REQUIRED",
|
||||||
|
.targets = {"fmt::fmt"},
|
||||||
|
.source = "manual",
|
||||||
|
})
|
||||||
|
.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("cmd_add appends a string-form dependency", "[cli][add]") {
|
TEST_CASE("cmd_add appends a string-form dependency", "[cli][add]") {
|
||||||
auto parent = fresh_dir();
|
auto parent = fresh_dir();
|
||||||
auto root = scaffold(parent);
|
auto root = scaffold(parent);
|
||||||
|
seed_fmt(overlay_path(parent));
|
||||||
|
|
||||||
auto r = cmd_add(root, "fmt", "10.2.0", {}, overlay_path(parent));
|
auto r = cmd_add(root, "fmt", "10.2.0", {}, overlay_path(parent));
|
||||||
REQUIRE(r.has_value());
|
REQUIRE(r.has_value());
|
||||||
@@ -53,25 +70,10 @@ TEST_CASE("cmd_add appends a string-form dependency", "[cli][add]") {
|
|||||||
REQUIRE(m->dependencies[0].components.empty());
|
REQUIRE(m->dependencies[0].components.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("cmd_add stores components when provided", "[cli][add]") {
|
|
||||||
auto parent = fresh_dir();
|
|
||||||
auto root = scaffold(parent);
|
|
||||||
|
|
||||||
auto r = cmd_add(root, "abseil-cpp", "20240116.0", {"strings", "base"},
|
|
||||||
overlay_path(parent));
|
|
||||||
REQUIRE(r.has_value());
|
|
||||||
|
|
||||||
auto m = manifest::parse(root / "Cargoxx.toml");
|
|
||||||
REQUIRE(m.has_value());
|
|
||||||
REQUIRE(m->dependencies.size() == 1);
|
|
||||||
REQUIRE(m->dependencies[0].name == "abseil-cpp");
|
|
||||||
REQUIRE(m->dependencies[0].components ==
|
|
||||||
std::vector<std::string>{"strings", "base"});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("cmd_add accepts an empty version and stores '*'", "[cli][add]") {
|
TEST_CASE("cmd_add accepts an empty version and stores '*'", "[cli][add]") {
|
||||||
auto parent = fresh_dir();
|
auto parent = fresh_dir();
|
||||||
auto root = scaffold(parent);
|
auto root = scaffold(parent);
|
||||||
|
seed_fmt(overlay_path(parent));
|
||||||
|
|
||||||
auto r = cmd_add(root, "fmt", "", {}, overlay_path(parent));
|
auto r = cmd_add(root, "fmt", "", {}, overlay_path(parent));
|
||||||
REQUIRE(r.has_value());
|
REQUIRE(r.has_value());
|
||||||
@@ -105,6 +107,7 @@ TEST_CASE("cmd_add rejects an unknown package", "[cli][add]") {
|
|||||||
TEST_CASE("cmd_add rejects an already-declared dep", "[cli][add]") {
|
TEST_CASE("cmd_add rejects an already-declared dep", "[cli][add]") {
|
||||||
auto parent = fresh_dir();
|
auto parent = fresh_dir();
|
||||||
auto root = scaffold(parent);
|
auto root = scaffold(parent);
|
||||||
|
seed_fmt(overlay_path(parent));
|
||||||
|
|
||||||
REQUIRE(cmd_add(root, "fmt", "10.2.0", {}, overlay_path(parent)).has_value());
|
REQUIRE(cmd_add(root, "fmt", "10.2.0", {}, overlay_path(parent)).has_value());
|
||||||
|
|
||||||
@@ -112,13 +115,3 @@ TEST_CASE("cmd_add rejects an already-declared dep", "[cli][add]") {
|
|||||||
REQUIRE_FALSE(r.has_value());
|
REQUIRE_FALSE(r.has_value());
|
||||||
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
|
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("cmd_add rejects componentized package without components",
|
|
||||||
"[cli][add]") {
|
|
||||||
auto parent = fresh_dir();
|
|
||||||
auto root = scaffold(parent);
|
|
||||||
|
|
||||||
auto r = cmd_add(root, "abseil-cpp", "20240116.0", {}, overlay_path(parent));
|
|
||||||
REQUIRE_FALSE(r.has_value());
|
|
||||||
REQUIRE(r.error().code == ErrorCode::LinkdbComponentNotSupported);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
import cargoxx.cli;
|
import cargoxx.cli;
|
||||||
|
import cargoxx.linkdb;
|
||||||
import cargoxx.manifest;
|
import cargoxx.manifest;
|
||||||
import cargoxx.lockfile;
|
import cargoxx.lockfile;
|
||||||
import cargoxx.util;
|
import cargoxx.util;
|
||||||
@@ -8,6 +9,8 @@ import std;
|
|||||||
|
|
||||||
using cargoxx::cli::cmd_build;
|
using cargoxx::cli::cmd_build;
|
||||||
using cargoxx::cli::cmd_new;
|
using cargoxx::cli::cmd_new;
|
||||||
|
using cargoxx::linkdb::Database;
|
||||||
|
using cargoxx::linkdb::Recipe;
|
||||||
using cargoxx::util::ErrorCode;
|
using cargoxx::util::ErrorCode;
|
||||||
namespace manifest = cargoxx::manifest;
|
namespace manifest = cargoxx::manifest;
|
||||||
namespace lockfile = cargoxx::lockfile;
|
namespace lockfile = cargoxx::lockfile;
|
||||||
@@ -43,6 +46,13 @@ auto add_dep(const std::filesystem::path& root, const std::string& name,
|
|||||||
REQUIRE(manifest::write(*m, path).has_value());
|
REQUIRE(manifest::write(*m, path).has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto seed_recipe(const std::filesystem::path& overlay, const std::string& package,
|
||||||
|
const std::string& version_range, const Recipe& r) {
|
||||||
|
auto db = Database::open(overlay);
|
||||||
|
REQUIRE(db.has_value());
|
||||||
|
REQUIRE(db->add_manual(package, version_range, r).has_value());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("cmd_build generates files for a no-deps binary project",
|
TEST_CASE("cmd_build generates files for a no-deps binary project",
|
||||||
@@ -82,12 +92,19 @@ TEST_CASE("cmd_build generates files for a library project", "[cli][build]") {
|
|||||||
REQUIRE(cmake_text.find("add_executable") == std::string::npos);
|
REQUIRE(cmake_text.find("add_executable") == std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("cmd_build resolves a curated dep into find_package + targets",
|
TEST_CASE("cmd_build resolves a manually-seeded dep into find_package + targets",
|
||||||
"[cli][build]") {
|
"[cli][build]") {
|
||||||
auto parent = fresh_dir();
|
auto parent = fresh_dir();
|
||||||
REQUIRE(cmd_new("app", false, parent).has_value());
|
REQUIRE(cmd_new("app", false, parent).has_value());
|
||||||
auto root = parent / "app";
|
auto root = parent / "app";
|
||||||
add_dep(root, "fmt", "10.2.0");
|
add_dep(root, "fmt", "10.2.0");
|
||||||
|
seed_recipe(overlay_path(parent), "fmt", ">=10.0.0",
|
||||||
|
Recipe{
|
||||||
|
.nixpkgs_attr = "fmt_10",
|
||||||
|
.find_package = "fmt CONFIG REQUIRED",
|
||||||
|
.targets = {"fmt::fmt"},
|
||||||
|
.source = "manual",
|
||||||
|
});
|
||||||
|
|
||||||
auto r = cmd_build(root, true, false, std::nullopt, overlay_path(parent));
|
auto r = cmd_build(root, true, false, std::nullopt, overlay_path(parent));
|
||||||
REQUIRE(r.has_value());
|
REQUIRE(r.has_value());
|
||||||
@@ -100,35 +117,33 @@ TEST_CASE("cmd_build resolves a curated dep into find_package + targets",
|
|||||||
REQUIRE(flake_text.find("pkgs.fmt_10") != std::string::npos);
|
REQUIRE(flake_text.find("pkgs.fmt_10") != std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("cmd_build resolves a componentized dep", "[cli][build]") {
|
|
||||||
auto parent = fresh_dir();
|
|
||||||
REQUIRE(cmd_new("app", false, parent).has_value());
|
|
||||||
auto root = parent / "app";
|
|
||||||
add_dep(root, "abseil-cpp", "20240116.0", {"strings", "base"});
|
|
||||||
|
|
||||||
auto r = cmd_build(root, true, false, std::nullopt, overlay_path(parent));
|
|
||||||
REQUIRE(r.has_value());
|
|
||||||
|
|
||||||
auto cmake_text = read_file(root / "build" / "CMakeLists.txt");
|
|
||||||
REQUIRE(cmake_text.find("find_package(absl CONFIG REQUIRED)") !=
|
|
||||||
std::string::npos);
|
|
||||||
REQUIRE(cmake_text.find("absl::strings") != std::string::npos);
|
|
||||||
REQUIRE(cmake_text.find("absl::base") != std::string::npos);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("cmd_build synthesizes a lockfile entry per dep", "[cli][build]") {
|
TEST_CASE("cmd_build synthesizes a lockfile entry per dep", "[cli][build]") {
|
||||||
auto parent = fresh_dir();
|
auto parent = fresh_dir();
|
||||||
REQUIRE(cmd_new("app", false, parent).has_value());
|
REQUIRE(cmd_new("app", false, parent).has_value());
|
||||||
auto root = parent / "app";
|
auto root = parent / "app";
|
||||||
add_dep(root, "fmt", "10.2.0");
|
add_dep(root, "fmt", "10.2.0");
|
||||||
add_dep(root, "spdlog", "1.13.0");
|
add_dep(root, "spdlog", "1.13.0");
|
||||||
|
seed_recipe(overlay_path(parent), "fmt", ">=10.0.0",
|
||||||
|
Recipe{
|
||||||
|
.nixpkgs_attr = "fmt_10",
|
||||||
|
.find_package = "fmt CONFIG REQUIRED",
|
||||||
|
.targets = {"fmt::fmt"},
|
||||||
|
.source = "manual",
|
||||||
|
});
|
||||||
|
seed_recipe(overlay_path(parent), "spdlog", "*",
|
||||||
|
Recipe{
|
||||||
|
.nixpkgs_attr = "spdlog",
|
||||||
|
.find_package = "spdlog CONFIG REQUIRED",
|
||||||
|
.targets = {"spdlog::spdlog"},
|
||||||
|
.source = "manual",
|
||||||
|
});
|
||||||
|
|
||||||
auto r = cmd_build(root, true, false, std::nullopt, overlay_path(parent));
|
auto r = cmd_build(root, true, false, std::nullopt, overlay_path(parent));
|
||||||
REQUIRE(r.has_value());
|
REQUIRE(r.has_value());
|
||||||
|
|
||||||
auto lock = lockfile::parse(root / "Cargoxx.lock");
|
auto lock = lockfile::parse(root / "Cargoxx.lock");
|
||||||
REQUIRE(lock.has_value());
|
REQUIRE(lock.has_value());
|
||||||
REQUIRE(lock->packages.size() == 3); // root + fmt + spdlog
|
REQUIRE(lock->packages.size() == 3);
|
||||||
REQUIRE(lock->packages[0].name == "app");
|
REQUIRE(lock->packages[0].name == "app");
|
||||||
REQUIRE(lock->packages[0].dependencies.size() == 2);
|
REQUIRE(lock->packages[0].dependencies.size() == 2);
|
||||||
}
|
}
|
||||||
@@ -172,6 +187,13 @@ TEST_CASE("cmd_build is idempotent — second run produces identical files",
|
|||||||
REQUIRE(cmd_new("app", false, parent).has_value());
|
REQUIRE(cmd_new("app", false, parent).has_value());
|
||||||
auto root = parent / "app";
|
auto root = parent / "app";
|
||||||
add_dep(root, "fmt", "10.2.0");
|
add_dep(root, "fmt", "10.2.0");
|
||||||
|
seed_recipe(overlay_path(parent), "fmt", ">=10.0.0",
|
||||||
|
Recipe{
|
||||||
|
.nixpkgs_attr = "fmt_10",
|
||||||
|
.find_package = "fmt CONFIG REQUIRED",
|
||||||
|
.targets = {"fmt::fmt"},
|
||||||
|
.source = "manual",
|
||||||
|
});
|
||||||
|
|
||||||
REQUIRE(cmd_build(root, true, false, std::nullopt, overlay_path(parent)).has_value());
|
REQUIRE(cmd_build(root, true, false, std::nullopt, overlay_path(parent)).has_value());
|
||||||
auto first_cmake = read_file(root / "build" / "CMakeLists.txt");
|
auto first_cmake = read_file(root / "build" / "CMakeLists.txt");
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
import cargoxx.cli;
|
import cargoxx.cli;
|
||||||
|
import cargoxx.linkdb;
|
||||||
import cargoxx.manifest;
|
import cargoxx.manifest;
|
||||||
import cargoxx.util;
|
import cargoxx.util;
|
||||||
import std;
|
import std;
|
||||||
@@ -8,6 +9,8 @@ import std;
|
|||||||
using cargoxx::cli::cmd_add;
|
using cargoxx::cli::cmd_add;
|
||||||
using cargoxx::cli::cmd_new;
|
using cargoxx::cli::cmd_new;
|
||||||
using cargoxx::cli::cmd_remove;
|
using cargoxx::cli::cmd_remove;
|
||||||
|
using cargoxx::linkdb::Database;
|
||||||
|
using cargoxx::linkdb::Recipe;
|
||||||
using cargoxx::util::ErrorCode;
|
using cargoxx::util::ErrorCode;
|
||||||
namespace manifest = cargoxx::manifest;
|
namespace manifest = cargoxx::manifest;
|
||||||
|
|
||||||
@@ -36,11 +39,26 @@ auto scaffold(const std::filesystem::path& parent) -> std::filesystem::path {
|
|||||||
return parent / "app";
|
return parent / "app";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto seed_recipe(const std::filesystem::path& overlay, const std::string& name,
|
||||||
|
const std::string& nixpkgs_attr) {
|
||||||
|
auto db = Database::open(overlay);
|
||||||
|
REQUIRE(db.has_value());
|
||||||
|
REQUIRE(db->add_manual(name, "*",
|
||||||
|
Recipe{
|
||||||
|
.nixpkgs_attr = nixpkgs_attr,
|
||||||
|
.find_package = std::format("{} CONFIG REQUIRED", name),
|
||||||
|
.targets = {std::format("{}::{}", name, name)},
|
||||||
|
.source = "manual",
|
||||||
|
})
|
||||||
|
.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("cmd_remove drops the dependency", "[cli][remove]") {
|
TEST_CASE("cmd_remove drops the dependency", "[cli][remove]") {
|
||||||
auto parent = fresh_dir();
|
auto parent = fresh_dir();
|
||||||
auto root = scaffold(parent);
|
auto root = scaffold(parent);
|
||||||
|
seed_recipe(overlay_path(parent), "fmt", "fmt_10");
|
||||||
REQUIRE(cmd_add(root, "fmt", "10.2.0", {}, overlay_path(parent)).has_value());
|
REQUIRE(cmd_add(root, "fmt", "10.2.0", {}, overlay_path(parent)).has_value());
|
||||||
|
|
||||||
REQUIRE(cmd_remove(root, "fmt").has_value());
|
REQUIRE(cmd_remove(root, "fmt").has_value());
|
||||||
@@ -53,6 +71,8 @@ TEST_CASE("cmd_remove drops the dependency", "[cli][remove]") {
|
|||||||
TEST_CASE("cmd_remove leaves other deps in place", "[cli][remove]") {
|
TEST_CASE("cmd_remove leaves other deps in place", "[cli][remove]") {
|
||||||
auto parent = fresh_dir();
|
auto parent = fresh_dir();
|
||||||
auto root = scaffold(parent);
|
auto root = scaffold(parent);
|
||||||
|
seed_recipe(overlay_path(parent), "fmt", "fmt_10");
|
||||||
|
seed_recipe(overlay_path(parent), "spdlog", "spdlog");
|
||||||
REQUIRE(cmd_add(root, "fmt", "10.2.0", {}, overlay_path(parent)).has_value());
|
REQUIRE(cmd_add(root, "fmt", "10.2.0", {}, overlay_path(parent)).has_value());
|
||||||
REQUIRE(cmd_add(root, "spdlog", "1.13.0", {}, overlay_path(parent)).has_value());
|
REQUIRE(cmd_add(root, "spdlog", "1.13.0", {}, overlay_path(parent)).has_value());
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import std;
|
|||||||
|
|
||||||
using cargoxx::linkdb::Database;
|
using cargoxx::linkdb::Database;
|
||||||
using cargoxx::linkdb::Recipe;
|
using cargoxx::linkdb::Recipe;
|
||||||
using cargoxx::linkdb::expand_targets;
|
|
||||||
using cargoxx::linkdb::substitute_components;
|
|
||||||
using cargoxx::util::ErrorCode;
|
using cargoxx::util::ErrorCode;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -27,134 +25,48 @@ auto open_db() -> Database {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("Database::open loads the curated linkdb", "[linkdb]") {
|
TEST_CASE("Database::open succeeds against a fresh overlay path", "[linkdb]") {
|
||||||
auto db = open_db();
|
auto db = open_db();
|
||||||
(void)db;
|
(void)db;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("resolve returns the curated recipe for fmt 10", "[linkdb]") {
|
TEST_CASE("resolve fails on an empty database", "[linkdb]") {
|
||||||
auto db = open_db();
|
auto db = open_db();
|
||||||
|
auto rec = db.resolve("fmt", "10.2.0");
|
||||||
|
REQUIRE_FALSE(rec.has_value());
|
||||||
|
REQUIRE(rec.error().code == ErrorCode::LinkdbUnknownPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("resolve returns a manually-added recipe", "[linkdb]") {
|
||||||
|
auto db = open_db();
|
||||||
|
auto add = db.add_manual("fmt", ">=10.0.0",
|
||||||
|
Recipe{
|
||||||
|
.nixpkgs_attr = "fmt_10",
|
||||||
|
.find_package = "fmt CONFIG REQUIRED",
|
||||||
|
.targets = {"fmt::fmt"},
|
||||||
|
.source = "manual",
|
||||||
|
});
|
||||||
|
REQUIRE(add.has_value());
|
||||||
|
|
||||||
auto rec = db.resolve("fmt", "10.2.0");
|
auto rec = db.resolve("fmt", "10.2.0");
|
||||||
REQUIRE(rec.has_value());
|
REQUIRE(rec.has_value());
|
||||||
REQUIRE(rec->nixpkgs_attr == "fmt_10");
|
REQUIRE(rec->nixpkgs_attr == "fmt_10");
|
||||||
REQUIRE(rec->find_package == "fmt CONFIG REQUIRED");
|
REQUIRE(rec->find_package == "fmt CONFIG REQUIRED");
|
||||||
REQUIRE(rec->targets == std::vector<std::string>{"fmt::fmt"});
|
REQUIRE(rec->targets == std::vector<std::string>{"fmt::fmt"});
|
||||||
REQUIRE(rec->source == "curated");
|
REQUIRE(rec->source == "manual");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("resolve returns the older fmt recipe for fmt 8", "[linkdb]") {
|
TEST_CASE("resolve fails when components are passed but the row is non-componentized",
|
||||||
auto db = open_db();
|
|
||||||
auto rec = db.resolve("fmt", "8.1.0");
|
|
||||||
REQUIRE(rec.has_value());
|
|
||||||
REQUIRE(rec->nixpkgs_attr == "fmt_8");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("resolve fails for an unknown package", "[linkdb]") {
|
|
||||||
auto db = open_db();
|
|
||||||
auto rec = db.resolve("obscurelib", "0.0.1");
|
|
||||||
REQUIRE_FALSE(rec.has_value());
|
|
||||||
REQUIRE(rec.error().code == ErrorCode::LinkdbUnknownPackage);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("resolve substitutes abseil-cpp components", "[linkdb]") {
|
|
||||||
auto db = open_db();
|
|
||||||
auto rec = db.resolve("abseil-cpp", "20240116.0", {"strings", "base"});
|
|
||||||
REQUIRE(rec.has_value());
|
|
||||||
REQUIRE(rec->find_package == "absl CONFIG REQUIRED");
|
|
||||||
REQUIRE(rec->targets ==
|
|
||||||
std::vector<std::string>{"absl::strings", "absl::base"});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("resolve fails when a componentized package gets no components",
|
|
||||||
"[linkdb]") {
|
|
||||||
auto db = open_db();
|
|
||||||
auto rec = db.resolve("abseil-cpp", "20240116.0");
|
|
||||||
REQUIRE_FALSE(rec.has_value());
|
|
||||||
REQUIRE(rec.error().code == ErrorCode::LinkdbComponentNotSupported);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("resolve fails when components are passed to a non-componentized package",
|
|
||||||
"[linkdb]") {
|
"[linkdb]") {
|
||||||
auto db = open_db();
|
auto db = open_db();
|
||||||
|
(void)db.add_manual("fmt", "*",
|
||||||
|
Recipe{
|
||||||
|
.nixpkgs_attr = "fmt_10",
|
||||||
|
.find_package = "fmt CONFIG REQUIRED",
|
||||||
|
.targets = {"fmt::fmt"},
|
||||||
|
.source = "manual",
|
||||||
|
});
|
||||||
auto rec = db.resolve("fmt", "10.2.0", {"core"});
|
auto rec = db.resolve("fmt", "10.2.0", {"core"});
|
||||||
REQUIRE_FALSE(rec.has_value());
|
REQUIRE_FALSE(rec.has_value());
|
||||||
REQUIRE(rec.error().code == ErrorCode::LinkdbComponentNotSupported);
|
REQUIRE(rec.error().code == ErrorCode::LinkdbComponentNotSupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("resolve handles wildcard versions", "[linkdb]") {
|
|
||||||
auto db = open_db();
|
|
||||||
auto rec = db.resolve("openssl", "3.2.0");
|
|
||||||
REQUIRE(rec.has_value());
|
|
||||||
REQUIRE(rec->find_package == "OpenSSL REQUIRED");
|
|
||||||
REQUIRE(rec->targets ==
|
|
||||||
std::vector<std::string>{"OpenSSL::SSL", "OpenSSL::Crypto"});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("resolve covers all 25 curated packages", "[linkdb]") {
|
|
||||||
auto db = open_db();
|
|
||||||
|
|
||||||
struct Sample {
|
|
||||||
std::string name;
|
|
||||||
std::string version;
|
|
||||||
std::vector<std::string> components;
|
|
||||||
};
|
|
||||||
const std::vector<Sample> samples = {
|
|
||||||
{"fmt", "10.2.0", {}},
|
|
||||||
{"spdlog", "1.13.0", {}},
|
|
||||||
{"nlohmann_json", "3.11.0", {}},
|
|
||||||
{"boost", "1.84.0", {}},
|
|
||||||
{"openssl", "3.2.0", {}},
|
|
||||||
{"zlib", "1.3.0", {}},
|
|
||||||
{"sqlite3", "3.45.0", {}},
|
|
||||||
{"curl", "8.5.0", {}},
|
|
||||||
{"protobuf", "25.0.0", {}},
|
|
||||||
{"grpc", "1.60.0", {}},
|
|
||||||
{"abseil-cpp", "20240116.0", {"strings"}},
|
|
||||||
{"gtest", "1.14.0", {}},
|
|
||||||
{"catch2", "3.5.0", {}},
|
|
||||||
{"eigen", "3.4.0", {}},
|
|
||||||
{"tbb", "2021.10.0", {}},
|
|
||||||
{"libpng", "1.6.40", {}},
|
|
||||||
{"libjpeg", "3.0.1", {}},
|
|
||||||
{"freetype", "2.13.2", {}},
|
|
||||||
{"glfw", "3.3.9", {}},
|
|
||||||
{"glm", "0.9.9.8", {}},
|
|
||||||
{"sdl2", "2.28.5", {}},
|
|
||||||
{"cli11", "2.4.1", {}},
|
|
||||||
{"cxxopts", "3.2.0", {}},
|
|
||||||
{"range-v3", "0.12.0", {}},
|
|
||||||
{"magic_enum", "0.9.5", {}},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& s : samples) {
|
|
||||||
auto rec = db.resolve(s.name, s.version, s.components);
|
|
||||||
INFO("resolving " << s.name);
|
|
||||||
REQUIRE(rec.has_value());
|
|
||||||
REQUIRE_FALSE(rec->nixpkgs_attr.empty());
|
|
||||||
REQUIRE_FALSE(rec->find_package.empty());
|
|
||||||
REQUIRE_FALSE(rec->targets.empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("substitute_components is a no-op when marker is absent",
|
|
||||||
"[linkdb][substitute]") {
|
|
||||||
REQUIRE(substitute_components("foo bar", {"a", "b"}) == "foo bar");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("substitute_components joins components with spaces",
|
|
||||||
"[linkdb][substitute]") {
|
|
||||||
REQUIRE(substitute_components("X {{components}} Y", {"a", "b", "c"}) ==
|
|
||||||
"X a b c Y");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("expand_targets fans out per-component templates",
|
|
||||||
"[linkdb][substitute]") {
|
|
||||||
REQUIRE(expand_targets({"Boost::{{component}}"}, {"filesystem", "system"}) ==
|
|
||||||
std::vector<std::string>{"Boost::filesystem", "Boost::system"});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("expand_targets keeps non-templated targets verbatim",
|
|
||||||
"[linkdb][substitute]") {
|
|
||||||
REQUIRE(expand_targets({"OpenSSL::SSL", "OpenSSL::Crypto"}, {}) ==
|
|
||||||
std::vector<std::string>{"OpenSSL::SSL", "OpenSSL::Crypto"});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -47,18 +47,17 @@ TEST_CASE("add_manual then resolve returns the manual recipe",
|
|||||||
REQUIRE(got->source == "manual");
|
REQUIRE(got->source == "manual");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("manual entry overrides curated for the same package",
|
TEST_CASE("manual entry resolves on subsequent open", "[linkdb][overlay]") {
|
||||||
"[linkdb][overlay]") {
|
|
||||||
auto db = Database::open(fresh_overlay());
|
auto db = Database::open(fresh_overlay());
|
||||||
REQUIRE(db.has_value());
|
REQUIRE(db.has_value());
|
||||||
|
|
||||||
Recipe override_r{
|
Recipe r{
|
||||||
.nixpkgs_attr = "fmt_pinned",
|
.nixpkgs_attr = "fmt_pinned",
|
||||||
.find_package = "fmt CONFIG REQUIRED",
|
.find_package = "fmt CONFIG REQUIRED",
|
||||||
.targets = {"fmt::fmt"},
|
.targets = {"fmt::fmt"},
|
||||||
.source = "manual",
|
.source = "manual",
|
||||||
};
|
};
|
||||||
REQUIRE(db->add_manual("fmt", ">=10.0.0", override_r).has_value());
|
REQUIRE(db->add_manual("fmt", ">=10.0.0", r).has_value());
|
||||||
|
|
||||||
auto got = db->resolve("fmt", "10.2.0");
|
auto got = db->resolve("fmt", "10.2.0");
|
||||||
REQUIRE(got.has_value());
|
REQUIRE(got.has_value());
|
||||||
@@ -79,13 +78,10 @@ TEST_CASE("manual entry is constrained by version_range",
|
|||||||
};
|
};
|
||||||
REQUIRE(db->add_manual("fmt", ">=11.0.0", r).has_value());
|
REQUIRE(db->add_manual("fmt", ">=11.0.0", r).has_value());
|
||||||
|
|
||||||
// 10.x falls outside the manual range and falls through to curated
|
auto miss = db->resolve("fmt", "10.2.0");
|
||||||
auto curated = db->resolve("fmt", "10.2.0");
|
REQUIRE_FALSE(miss.has_value());
|
||||||
REQUIRE(curated.has_value());
|
REQUIRE(miss.error().code == ErrorCode::LinkdbUnknownPackage);
|
||||||
REQUIRE(curated->source == "curated");
|
|
||||||
REQUIRE(curated->nixpkgs_attr == "fmt_10");
|
|
||||||
|
|
||||||
// 11.x matches the manual range
|
|
||||||
auto manual = db->resolve("fmt", "11.0.0");
|
auto manual = db->resolve("fmt", "11.0.0");
|
||||||
REQUIRE(manual.has_value());
|
REQUIRE(manual.has_value());
|
||||||
REQUIRE(manual->source == "manual");
|
REQUIRE(manual->source == "manual");
|
||||||
|
|||||||
@@ -94,13 +94,11 @@ TEST_CASE("verify_link rolls the provisional row back when the build fails",
|
|||||||
REQUIRE_FALSE(r.has_value());
|
REQUIRE_FALSE(r.has_value());
|
||||||
REQUIRE(r.error().code == cargoxx::util::ErrorCode::BuildCmakeFailed);
|
REQUIRE(r.error().code == cargoxx::util::ErrorCode::BuildCmakeFailed);
|
||||||
|
|
||||||
// The conan-source row must be gone; resolve falls through to the
|
|
||||||
// curated linkdb (which has its own fmt recipe with source = "curated").
|
|
||||||
auto db = Database::open(req.overlay_path);
|
auto db = Database::open(req.overlay_path);
|
||||||
REQUIRE(db.has_value());
|
REQUIRE(db.has_value());
|
||||||
auto rec = db->resolve("fmt", "10.2.0", {});
|
auto rec = db->resolve("fmt", "10.2.0", {});
|
||||||
REQUIRE(rec.has_value());
|
REQUIRE_FALSE(rec.has_value());
|
||||||
REQUIRE(rec->source == "curated");
|
REQUIRE(rec.error().code == cargoxx::util::ErrorCode::LinkdbUnknownPackage);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("verify_link cleans up its scratch project", "[resolver][verify_link]") {
|
TEST_CASE("verify_link cleans up its scratch project", "[resolver][verify_link]") {
|
||||||
|
|||||||
Reference in New Issue
Block a user