[M5+] realize candidate output before nix-cmake-scan

This commit is contained in:
2026-05-10 13:41:19 +00:00
parent 54da546ebc
commit 5e691ac37b
4 changed files with 89 additions and 7 deletions

View File

@@ -89,21 +89,31 @@ auto discover(const std::string& name, const std::string& version_spec,
if (auto v = vcpkg_probe(name); v) {
candidates.push_back({"vcpkg", recipe_from_vcpkg(*v, name, "vcpkg")});
}
// Multi-output nix packages keep CMake configs in the `dev` output;
// scan it first when available, fall back to the default `out`.
auto try_scan = [&](const fs::path& root)
// Multi-output nix packages keep CMake configs in the `dev` output.
// The probe above only computes paths via `nix eval`; for packages
// not yet present in the store, those paths don't exist on disk
// and the scan would fail on its `fs::exists` check. Realize each
// candidate output (downloads from the binary cache) before
// scanning. dev wins when available; fall back to the default out.
auto realize_and_scan = [&](std::string_view sub_attr)
-> std::optional<NixCmakeCandidate> {
if (root.empty()) {
auto attr =
sub_attr.empty() ? name : std::format("{}.{}", name, sub_attr);
auto realized = realize_path(attr);
if (!realized) {
return std::nullopt;
}
if (auto r = nix_cmake_scan(root, name); r) {
if (auto r = nix_cmake_scan(fs::path{*realized}, name); r) {
return std::move(*r);
}
return std::nullopt;
};
auto scan_hit = try_scan(info->dev_path);
std::optional<NixCmakeCandidate> scan_hit;
if (!info->dev_path.empty()) {
scan_hit = realize_and_scan("dev");
}
if (!scan_hit) {
scan_hit = try_scan(info->out_path);
scan_hit = realize_and_scan("");
}
if (scan_hit) {
candidates.push_back(

View File

@@ -115,4 +115,51 @@ auto nixpkgs_probe(const std::string& attr) -> util::Result<NixpkgsInfo> {
return parse_nix_eval_json(attr, r->stdout_text);
}
auto realize_path(const std::string& flake_attr) -> util::Result<std::string> {
if (flake_attr.empty()) {
return std::unexpected(make_error(util::ErrorCode::ResolutionUnknownPackage,
"realize_path: flake_attr is empty"));
}
std::vector<std::string> args{
"--extra-experimental-features", "nix-command flakes",
"build", std::format("nixpkgs#{}", flake_attr),
"--no-link", "--print-out-paths",
};
auto r = exec::run("nix", args,
exec::ExecOptions{
.cwd = {},
.env_overrides = {},
// Long enough for first-time fetch from the binary
// cache; multi-output llvm tarballs are ~hundreds of MB.
.timeout = std::chrono::seconds{600},
.inherit_stdio = false,
});
if (!r) {
return std::unexpected(r.error());
}
if (r->exit_code != 0) {
if (looks_like_missing_attribute(r->stderr_text)) {
return std::unexpected(make_error(
util::ErrorCode::ResolutionUnknownPackage,
std::format("nixpkgs has no attribute '{}'", flake_attr)));
}
return std::unexpected(make_error(
util::ErrorCode::ResolutionNetworkError,
std::format("nix build failed (exit {}): {}", r->exit_code,
r->stderr_text)));
}
auto path = r->stdout_text;
while (!path.empty() && (path.back() == '\n' || path.back() == ' ')) {
path.pop_back();
}
if (path.empty()) {
return std::unexpected(make_error(
util::ErrorCode::ResolutionNetworkError,
std::format("nix build emitted no path for '{}'", flake_attr)));
}
return path;
}
} // namespace cargoxx::resolver

View File

@@ -33,6 +33,18 @@ auto parse_nix_eval_json(std::string_view attr, std::string_view json)
// `ResolutionNetworkError` on timeout or evaluator errors.
auto nixpkgs_probe(const std::string& attr) -> util::Result<NixpkgsInfo>;
// Materializes a flake attribute on disk by shelling out to
// nix build --no-link --print-out-paths nixpkgs#<flake_attr>
// and returns the absolute store path. The eval-only `nixpkgs_probe`
// computes the path the output *would* have, but it isn't actually
// present on disk until it's been built / fetched from the binary
// cache; nix_cmake_scan needs it on disk to walk lib/cmake. Use
// `realize_path("libclang.dev")` to scan a dev output, etc.
//
// `ResolutionUnknownPackage` for unknown attrs, `ResolutionNetworkError`
// for build / network errors.
auto realize_path(const std::string& flake_attr) -> util::Result<std::string>;
// One CMake config-file's IMPORTED targets together with the find_package
// expression derived from its filename stem.
struct NixCmakeCandidate {