[M5+] nix_cmake_scan filters Find*.cmake shims and picks public targets
This commit is contained in:
@@ -121,6 +121,61 @@ auto scan_imported_targets(std::string_view config_text) -> std::vector<std::str
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reduce a freshly-scanned list of IMPORTED targets to just the ones
|
||||||
|
// the package wants downstream consumers to link against. Strategy:
|
||||||
|
//
|
||||||
|
// * If the list contains any namespaced target (`Pkg::name`), keep
|
||||||
|
// only the namespaced ones — modern CMake's public-API convention
|
||||||
|
// (fmt::fmt, Catch2::Catch2WithMain, protobuf::libprotobuf, …).
|
||||||
|
//
|
||||||
|
// * Otherwise, the package only ships bare/legacy targets. LLVM is
|
||||||
|
// the canonical example: 213 internal libs (LLVMSupport, LLVMCore,
|
||||||
|
// …) plus the unified `LLVM` umbrella. Linking every internal
|
||||||
|
// target also pulls in their broken link interfaces (e.g.
|
||||||
|
// LLVMWindowsManifest references LibXml2::LibXml2 unconditionally,
|
||||||
|
// LLVMInterpreter references FFI::ffi). Keep only the target whose
|
||||||
|
// name matches the config stem case-insensitively (`LLVM` for
|
||||||
|
// `LLVMConfig.cmake`).
|
||||||
|
//
|
||||||
|
// * Fallback: if the stem-name filter yields nothing (no umbrella
|
||||||
|
// target exists), return the original list untouched — better a
|
||||||
|
// noisy link line than an empty recipe.
|
||||||
|
auto filter_public_targets(std::vector<std::string> targets,
|
||||||
|
std::string_view stem) -> std::vector<std::string> {
|
||||||
|
auto has_namespace = std::ranges::any_of(targets, [](const auto& t) {
|
||||||
|
return t.find("::") != std::string::npos;
|
||||||
|
});
|
||||||
|
if (has_namespace) {
|
||||||
|
std::erase_if(targets, [](const auto& t) {
|
||||||
|
return t.find("::") == std::string::npos;
|
||||||
|
});
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
auto stem_ci_eq = [&](std::string_view t) {
|
||||||
|
if (t.size() != stem.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < t.size(); ++i) {
|
||||||
|
auto a = std::tolower(static_cast<unsigned char>(t[i]));
|
||||||
|
auto b = std::tolower(static_cast<unsigned char>(stem[i]));
|
||||||
|
if (a != b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
std::vector<std::string> keep;
|
||||||
|
for (auto& t : targets) {
|
||||||
|
if (stem_ci_eq(t)) {
|
||||||
|
keep.push_back(std::move(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keep.empty()) {
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
return keep;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Normalize a name for cross-shape comparison:
|
// Normalize a name for cross-shape comparison:
|
||||||
@@ -253,6 +308,17 @@ auto nix_cmake_scan(const fs::path& store_path, const std::string& package_name)
|
|||||||
if (!sib.is_regular_file() || sib.path().extension() != ".cmake") {
|
if (!sib.is_regular_file() || sib.path().extension() != ".cmake") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Skip CMake FindModule shims (Find<X>.cmake). These declare
|
||||||
|
// cross-package IMPORTED targets like `FFI::ffi`,
|
||||||
|
// `LibEdit::LibEdit`, `zstd::libzstd_*` as fallbacks for
|
||||||
|
// when the host project doesn't have those libraries on
|
||||||
|
// CMAKE_PREFIX_PATH. Folding them into the recipe makes the
|
||||||
|
// generated `target_link_libraries` reference targets that
|
||||||
|
// don't exist in our buildInputs.
|
||||||
|
auto sib_stem = sib.path().stem().string();
|
||||||
|
if (sib_stem.starts_with("Find")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
std::ifstream in{sib.path()};
|
std::ifstream in{sib.path()};
|
||||||
if (!in) {
|
if (!in) {
|
||||||
continue;
|
continue;
|
||||||
@@ -269,6 +335,7 @@ auto nix_cmake_scan(const fs::path& store_path, const std::string& package_name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto stem = config_stem_to_package(entry.path().filename().string());
|
auto stem = config_stem_to_package(entry.path().filename().string());
|
||||||
|
targets = filter_public_targets(std::move(targets), stem);
|
||||||
auto parent_dir_name = pkg_dir.filename().string();
|
auto parent_dir_name = pkg_dir.filename().string();
|
||||||
NixCmakeCandidate cand{
|
NixCmakeCandidate cand{
|
||||||
.find_package = std::format("{} CONFIG REQUIRED", stem),
|
.find_package = std::format("{} CONFIG REQUIRED", stem),
|
||||||
|
|||||||
@@ -58,6 +58,13 @@ struct NixCmakeCandidate {
|
|||||||
// `<alias>::<member>` forms get picked up.
|
// `<alias>::<member>` forms get picked up.
|
||||||
auto scan_imported_targets(std::string_view config_text) -> std::vector<std::string>;
|
auto scan_imported_targets(std::string_view config_text) -> std::vector<std::string>;
|
||||||
|
|
||||||
|
// Pure: pick the public-API subset out of a freshly scanned target
|
||||||
|
// list. See nix_cmake_scan.cpp for the rule (namespaced > stem-named
|
||||||
|
// umbrella > pass-through). Exported so unit tests can drive it
|
||||||
|
// without scaffolding an on-disk store.
|
||||||
|
auto filter_public_targets(std::vector<std::string> targets,
|
||||||
|
std::string_view stem) -> std::vector<std::string>;
|
||||||
|
|
||||||
// Pure: turn a CMake config filename into the find_package name.
|
// Pure: turn a CMake config filename into the find_package name.
|
||||||
// `fmtConfig.cmake` / `fmt-config.cmake` -> `fmt`.
|
// `fmtConfig.cmake` / `fmt-config.cmake` -> `fmt`.
|
||||||
auto config_stem_to_package(std::string_view filename) -> std::string;
|
auto config_stem_to_package(std::string_view filename) -> std::string;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import cargoxx.util;
|
|||||||
import std;
|
import std;
|
||||||
|
|
||||||
using cargoxx::resolver::config_stem_to_package;
|
using cargoxx::resolver::config_stem_to_package;
|
||||||
|
using cargoxx::resolver::filter_public_targets;
|
||||||
using cargoxx::resolver::nix_cmake_scan;
|
using cargoxx::resolver::nix_cmake_scan;
|
||||||
using cargoxx::resolver::scan_imported_targets;
|
using cargoxx::resolver::scan_imported_targets;
|
||||||
using cargoxx::util::ErrorCode;
|
using cargoxx::util::ErrorCode;
|
||||||
@@ -64,6 +65,36 @@ TEST_CASE("scan_imported_targets does not match _add_library or similar",
|
|||||||
REQUIRE(out.empty());
|
REQUIRE(out.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("filter_public_targets keeps namespaced targets when present",
|
||||||
|
"[resolver][nix_cmake_scan]") {
|
||||||
|
auto out = filter_public_targets(
|
||||||
|
{"fmt::fmt", "fmt::fmt-header-only", "fmt_internal_helper"}, "fmt");
|
||||||
|
REQUIRE(out == std::vector<std::string>{"fmt::fmt", "fmt::fmt-header-only"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("filter_public_targets picks the umbrella when no targets are namespaced",
|
||||||
|
"[resolver][nix_cmake_scan]") {
|
||||||
|
// Mirrors LLVMExports: 213 bare targets, only `LLVM` is the
|
||||||
|
// intended downstream entry point.
|
||||||
|
auto out = filter_public_targets(
|
||||||
|
{"LLVM", "LLVMSupport", "LLVMCore", "LLVMWindowsManifest"}, "LLVM");
|
||||||
|
REQUIRE(out == std::vector<std::string>{"LLVM"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("filter_public_targets is case-insensitive when picking the umbrella",
|
||||||
|
"[resolver][nix_cmake_scan]") {
|
||||||
|
auto out = filter_public_targets({"GRPC", "grpc_internal"}, "gRPC");
|
||||||
|
REQUIRE(out == std::vector<std::string>{"GRPC"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("filter_public_targets keeps everything when no umbrella matches",
|
||||||
|
"[resolver][nix_cmake_scan]") {
|
||||||
|
// Stem doesn't match any bare target — fall back to passthrough so
|
||||||
|
// we still emit a non-empty link line.
|
||||||
|
auto out = filter_public_targets({"alpha", "beta", "gamma"}, "Whatever");
|
||||||
|
REQUIRE(out == std::vector<std::string>{"alpha", "beta", "gamma"});
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("scan_imported_targets dedupes duplicate target names",
|
TEST_CASE("scan_imported_targets dedupes duplicate target names",
|
||||||
"[resolver][nix_cmake_scan]") {
|
"[resolver][nix_cmake_scan]") {
|
||||||
constexpr std::string_view text = R"(
|
constexpr std::string_view text = R"(
|
||||||
|
|||||||
Reference in New Issue
Block a user