223 lines
8.7 KiB
C++
223 lines
8.7 KiB
C++
#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 strips a leading lib prefix when scoring",
|
|
"[resolver][nix_cmake_scan]") {
|
|
// Real layout for nixpkgs `libllvm.dev`. `libllvm` should match
|
|
// `LLVMConfig.cmake`'s parent dir (`llvm`) after lib-stripping
|
|
// and beat `polly/PollyConfig.cmake`.
|
|
auto store = fresh_store();
|
|
touch_config(store, "llvm/LLVMConfig.cmake",
|
|
R"(add_library(LLVM SHARED IMPORTED))");
|
|
touch_config(store, "polly/PollyConfig.cmake",
|
|
R"(add_library(Polly SHARED IMPORTED))");
|
|
|
|
auto r = nix_cmake_scan(store, "libllvm");
|
|
REQUIRE(r.has_value());
|
|
REQUIRE(r->find_package == "LLVM CONFIG REQUIRED");
|
|
REQUIRE(r->config_file.filename() == "LLVMConfig.cmake");
|
|
}
|
|
|
|
TEST_CASE("nix_cmake_scan picks the canonical config from a versioned dir",
|
|
"[resolver][nix_cmake_scan]") {
|
|
// Boost ships a top-level `Boost-1.89.0/BoostConfig.cmake` plus
|
|
// 47 modular component configs at sibling versioned dirs.
|
|
// After version-suffix stripping the top-level Boost should win.
|
|
auto store = fresh_store();
|
|
touch_config(store, "Boost-1.89.0/BoostConfig.cmake",
|
|
R"(add_library(Boost::headers INTERFACE IMPORTED))");
|
|
touch_config(store, "boost_atomic-1.89.0/boost_atomic-config.cmake",
|
|
R"(add_library(Boost::atomic SHARED IMPORTED))");
|
|
touch_config(store, "boost_filesystem-1.89.0/boost_filesystem-config.cmake",
|
|
R"(add_library(Boost::filesystem SHARED IMPORTED))");
|
|
|
|
auto r = nix_cmake_scan(store, "boost");
|
|
REQUIRE(r.has_value());
|
|
REQUIRE(r->find_package == "Boost CONFIG REQUIRED");
|
|
REQUIRE(r->config_file.filename() == "BoostConfig.cmake");
|
|
}
|
|
|
|
TEST_CASE("nix_cmake_scan ignores satellite configs in multi-config stores",
|
|
"[resolver][nix_cmake_scan]") {
|
|
// Mirrors `protobuf` which ships protobuf-config.cmake plus an
|
|
// unrelated utf8_range-config.cmake under the same prefix tree.
|
|
auto store = fresh_store();
|
|
touch_config(store, "protobuf/protobuf-config.cmake",
|
|
R"(add_library(protobuf::libprotobuf SHARED IMPORTED))");
|
|
touch_config(store, "utf8_range/utf8_range-config.cmake",
|
|
R"(add_library(utf8_range::utf8_range SHARED IMPORTED))");
|
|
|
|
auto r = nix_cmake_scan(store, "protobuf");
|
|
REQUIRE(r.has_value());
|
|
REQUIRE(r->find_package == "protobuf CONFIG REQUIRED");
|
|
}
|
|
|
|
TEST_CASE("nix_cmake_scan normalizes case for spelling variants",
|
|
"[resolver][nix_cmake_scan]") {
|
|
// grpc ships `gRPCConfig.cmake` under `lib/cmake/grpc/`.
|
|
auto store = fresh_store();
|
|
touch_config(store, "grpc/gRPCConfig.cmake",
|
|
R"(add_library(gRPC::grpc++ SHARED IMPORTED))");
|
|
|
|
auto r = nix_cmake_scan(store, "grpc");
|
|
REQUIRE(r.has_value());
|
|
REQUIRE(r->find_package == "gRPC CONFIG REQUIRED");
|
|
}
|
|
|
|
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());
|
|
}
|