[M5+] add resolver::nix_cmake_scan
This commit is contained in:
@@ -26,3 +26,5 @@ cargoxx_add_test(cmd_add)
|
||||
cargoxx_add_test(cmd_remove)
|
||||
cargoxx_add_test(nixpkgs_probe_parse)
|
||||
cargoxx_add_test(nixpkgs_probe_live)
|
||||
cargoxx_add_test(nix_cmake_scan_parse)
|
||||
cargoxx_add_test(nix_cmake_scan_live)
|
||||
|
||||
57
tests/nix_cmake_scan_live.cpp
Normal file
57
tests/nix_cmake_scan_live.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// nix-store-gated integration test for resolver::nix_cmake_scan.
|
||||
// Realizes nixpkgs#fmt.dev (cheap if already cached) and verifies that
|
||||
// nix_cmake_scan picks up its real *-targets.cmake IMPORTED targets.
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
import cargoxx.resolver;
|
||||
import cargoxx.exec;
|
||||
import cargoxx.util;
|
||||
import std;
|
||||
|
||||
namespace {
|
||||
|
||||
auto network_tests_enabled() -> bool {
|
||||
auto* env = std::getenv("CARGOXX_NETWORK_TESTS");
|
||||
return env != nullptr && std::string_view{env} == "1";
|
||||
}
|
||||
|
||||
auto realize(std::string_view attr) -> std::optional<std::filesystem::path> {
|
||||
auto r = cargoxx::exec::run(
|
||||
"nix",
|
||||
{"--extra-experimental-features", "nix-command flakes",
|
||||
"build", std::format("nixpkgs#{}", attr),
|
||||
"--no-link", "--print-out-paths"},
|
||||
cargoxx::exec::ExecOptions{
|
||||
.cwd = {},
|
||||
.env_overrides = {},
|
||||
.timeout = std::chrono::seconds{180},
|
||||
.inherit_stdio = false,
|
||||
});
|
||||
if (!r || r->exit_code != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto path = r->stdout_text;
|
||||
while (!path.empty() && (path.back() == '\n' || path.back() == ' ')) {
|
||||
path.pop_back();
|
||||
}
|
||||
return std::filesystem::path{path};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("nix_cmake_scan finds fmt's IMPORTED targets",
|
||||
"[resolver][network]") {
|
||||
if (!network_tests_enabled()) {
|
||||
SKIP("CARGOXX_NETWORK_TESTS != 1");
|
||||
}
|
||||
auto p = realize("fmt.dev");
|
||||
REQUIRE(p.has_value());
|
||||
|
||||
auto r = cargoxx::resolver::nix_cmake_scan(*p, "fmt");
|
||||
REQUIRE(r.has_value());
|
||||
REQUIRE(r->find_package == "fmt CONFIG REQUIRED");
|
||||
// fmt-targets.cmake declares fmt::fmt + fmt::fmt-header-only.
|
||||
REQUIRE(std::ranges::find(r->targets, std::string{"fmt::fmt"}) !=
|
||||
r->targets.end());
|
||||
}
|
||||
159
tests/nix_cmake_scan_parse.cpp
Normal file
159
tests/nix_cmake_scan_parse.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
import cargoxx.resolver;
|
||||
import cargoxx.util;
|
||||
import std;
|
||||
|
||||
using cargoxx::resolver::config_stem_to_package;
|
||||
using cargoxx::resolver::nix_cmake_scan;
|
||||
using cargoxx::resolver::scan_imported_targets;
|
||||
using cargoxx::util::ErrorCode;
|
||||
|
||||
TEST_CASE("config_stem_to_package strips Config / -config / .cmake",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
REQUIRE(config_stem_to_package("fmtConfig.cmake") == "fmt");
|
||||
REQUIRE(config_stem_to_package("fmt-config.cmake") == "fmt");
|
||||
REQUIRE(config_stem_to_package("Catch2Config.cmake") == "Catch2");
|
||||
REQUIRE(config_stem_to_package("range-v3-config.cmake") == "range-v3");
|
||||
REQUIRE(config_stem_to_package("/abs/path/sub/spdlogConfig.cmake") == "spdlog");
|
||||
}
|
||||
|
||||
TEST_CASE("scan_imported_targets picks up IMPORTED libraries",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
constexpr std::string_view text = R"(
|
||||
# noise here
|
||||
add_library(fmt::fmt SHARED IMPORTED)
|
||||
set_target_properties(fmt::fmt PROPERTIES IMPORTED_LOCATION "/x")
|
||||
)";
|
||||
auto out = scan_imported_targets(text);
|
||||
REQUIRE(out == std::vector<std::string>{"fmt::fmt"});
|
||||
}
|
||||
|
||||
TEST_CASE("scan_imported_targets picks up ALIAS targets",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
constexpr std::string_view text = R"(
|
||||
add_library(absl_strings_internal STATIC IMPORTED)
|
||||
add_library(absl::strings ALIAS absl_strings_internal)
|
||||
)";
|
||||
auto out = scan_imported_targets(text);
|
||||
// Both the imported impl and the alias should be reported, deduped,
|
||||
// in declaration order.
|
||||
REQUIRE(out.size() == 2);
|
||||
REQUIRE(out[0] == "absl_strings_internal");
|
||||
REQUIRE(out[1] == "absl::strings");
|
||||
}
|
||||
|
||||
TEST_CASE("scan_imported_targets ignores plain (non-IMPORTED) add_library",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
constexpr std::string_view text = R"(
|
||||
add_library(local STATIC src.cpp)
|
||||
add_library(other_lib SHARED other.cpp)
|
||||
)";
|
||||
auto out = scan_imported_targets(text);
|
||||
REQUIRE(out.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("scan_imported_targets does not match _add_library or similar",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
constexpr std::string_view text = R"(
|
||||
# function definition uses the marker as a substring
|
||||
function(_add_library_helper)
|
||||
endfunction()
|
||||
)";
|
||||
auto out = scan_imported_targets(text);
|
||||
REQUIRE(out.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("scan_imported_targets dedupes duplicate target names",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
constexpr std::string_view text = R"(
|
||||
add_library(fmt::fmt SHARED IMPORTED)
|
||||
# benign duplicate (e.g. config-version reuse)
|
||||
add_library(fmt::fmt SHARED IMPORTED)
|
||||
)";
|
||||
auto out = scan_imported_targets(text);
|
||||
REQUIRE(out == std::vector<std::string>{"fmt::fmt"});
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
auto fresh_store() -> std::filesystem::path {
|
||||
auto d = std::filesystem::temp_directory_path() /
|
||||
std::format("cargoxx-cmake-scan-{}", std::random_device{}());
|
||||
std::filesystem::create_directories(d / "lib" / "cmake");
|
||||
return d;
|
||||
}
|
||||
|
||||
void touch_config(const std::filesystem::path& store, std::string_view rel,
|
||||
std::string_view content) {
|
||||
auto p = store / "lib" / "cmake" / rel;
|
||||
std::filesystem::create_directories(p.parent_path());
|
||||
std::ofstream{p} << content;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("nix_cmake_scan finds the canonical config", "[resolver][nix_cmake_scan]") {
|
||||
auto store = fresh_store();
|
||||
touch_config(store, "fmt/fmtConfig.cmake",
|
||||
R"(add_library(fmt::fmt SHARED IMPORTED))");
|
||||
|
||||
auto r = nix_cmake_scan(store, "fmt");
|
||||
REQUIRE(r.has_value());
|
||||
REQUIRE(r->find_package == "fmt CONFIG REQUIRED");
|
||||
REQUIRE(r->targets == std::vector<std::string>{"fmt::fmt"});
|
||||
REQUIRE(r->config_file.filename() == "fmtConfig.cmake");
|
||||
}
|
||||
|
||||
TEST_CASE("nix_cmake_scan prefers exact case-insensitive match",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
auto store = fresh_store();
|
||||
touch_config(store, "absl/abslConfig.cmake",
|
||||
R"(add_library(absl::strings SHARED IMPORTED))");
|
||||
touch_config(store, "absl-extras/abslExtrasConfig.cmake",
|
||||
R"(add_library(absl::extras SHARED IMPORTED))");
|
||||
|
||||
auto r = nix_cmake_scan(store, "absl");
|
||||
REQUIRE(r.has_value());
|
||||
REQUIRE(r->find_package == "absl CONFIG REQUIRED");
|
||||
}
|
||||
|
||||
TEST_CASE("nix_cmake_scan returns ResolutionUnknownPackage when nothing found",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
auto store = fresh_store();
|
||||
auto r = nix_cmake_scan(store, "nothing");
|
||||
REQUIRE_FALSE(r.has_value());
|
||||
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
|
||||
}
|
||||
|
||||
TEST_CASE("nix_cmake_scan ignores Config files with no IMPORTED targets",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
auto store = fresh_store();
|
||||
touch_config(store, "junk/junkConfig.cmake",
|
||||
R"(message(STATUS "no targets here"))");
|
||||
auto r = nix_cmake_scan(store, "junk");
|
||||
REQUIRE_FALSE(r.has_value());
|
||||
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
|
||||
}
|
||||
|
||||
TEST_CASE("nix_cmake_scan scans sibling *-targets.cmake files",
|
||||
"[resolver][nix_cmake_scan]") {
|
||||
// Mirrors fmt's real on-disk layout: the *-config.cmake is empty of
|
||||
// IMPORTED targets but include()s a sibling *-targets.cmake that
|
||||
// carries them.
|
||||
auto store = fresh_store();
|
||||
touch_config(store, "fmt/fmt-config.cmake",
|
||||
R"(include("${CMAKE_CURRENT_LIST_DIR}/fmt-targets.cmake"))");
|
||||
touch_config(store, "fmt/fmt-targets.cmake",
|
||||
R"(add_library(fmt::fmt SHARED IMPORTED)
|
||||
add_library(fmt::fmt-header-only INTERFACE IMPORTED))");
|
||||
|
||||
auto r = nix_cmake_scan(store, "fmt");
|
||||
REQUIRE(r.has_value());
|
||||
REQUIRE(r->find_package == "fmt CONFIG REQUIRED");
|
||||
REQUIRE(r->targets.size() == 2);
|
||||
REQUIRE(std::ranges::find(r->targets, std::string{"fmt::fmt"}) !=
|
||||
r->targets.end());
|
||||
REQUIRE(std::ranges::find(r->targets, std::string{"fmt::fmt-header-only"}) !=
|
||||
r->targets.end());
|
||||
}
|
||||
Reference in New Issue
Block a user