#include 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{"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{"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{"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()); }