[M5+] realize candidate output before nix-cmake-scan
This commit is contained in:
13
CHANGELOG.md
13
CHANGELOG.md
@@ -91,6 +91,19 @@ All notable changes to cargoxx will be documented in this file.
|
|||||||
6 cases against fixtures derived from a real fmt 10.2.1 response;
|
6 cases against fixtures derived from a real fmt 10.2.1 response;
|
||||||
`tests/devbox_resolve_live.cpp` (gated by `CARGOXX_NETWORK_TESTS=1`)
|
`tests/devbox_resolve_live.cpp` (gated by `CARGOXX_NETWORK_TESTS=1`)
|
||||||
hits the live API.
|
hits the live API.
|
||||||
|
- Fix: `nix_cmake_scan` would silently fail when the dev output had
|
||||||
|
not yet been realized on the local machine — `nixpkgs_probe`'s
|
||||||
|
`nix eval` only *computes* a store path, so packages first-time
|
||||||
|
encountered (`libclang`, anything not previously built) returned a
|
||||||
|
path that didn't exist on disk. Discovery now realizes the
|
||||||
|
candidate output via
|
||||||
|
`nix build --no-link --print-out-paths nixpkgs#<attr>.dev` (or
|
||||||
|
`.<empty>`) before scanning. New free function
|
||||||
|
`cargoxx::resolver::realize_path(flake_attr)` wraps the call.
|
||||||
|
Live verified: `cargoxx add libclang` now reaches the verify-link
|
||||||
|
step (the subsequent `find_dependency(LLVM)` failure inside Clang's
|
||||||
|
CMake config is the pre-existing cross-package transitive-dep
|
||||||
|
limit — workaround is to `cargoxx add libllvm` first).
|
||||||
- Fix: `nix_cmake_scan` walked only the package's default output. For
|
- Fix: `nix_cmake_scan` walked only the package's default output. For
|
||||||
multi-output Nix packages — `boost`, `openssl`, `libllvm`,
|
multi-output Nix packages — `boost`, `openssl`, `libllvm`,
|
||||||
`libclang`, glib, … — CMake configs live in the `dev` output
|
`libclang`, glib, … — CMake configs live in the `dev` output
|
||||||
|
|||||||
@@ -89,21 +89,31 @@ auto discover(const std::string& name, const std::string& version_spec,
|
|||||||
if (auto v = vcpkg_probe(name); v) {
|
if (auto v = vcpkg_probe(name); v) {
|
||||||
candidates.push_back({"vcpkg", recipe_from_vcpkg(*v, name, "vcpkg")});
|
candidates.push_back({"vcpkg", recipe_from_vcpkg(*v, name, "vcpkg")});
|
||||||
}
|
}
|
||||||
// Multi-output nix packages keep CMake configs in the `dev` output;
|
// Multi-output nix packages keep CMake configs in the `dev` output.
|
||||||
// scan it first when available, fall back to the default `out`.
|
// The probe above only computes paths via `nix eval`; for packages
|
||||||
auto try_scan = [&](const fs::path& root)
|
// 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> {
|
-> 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;
|
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::move(*r);
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
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) {
|
if (!scan_hit) {
|
||||||
scan_hit = try_scan(info->out_path);
|
scan_hit = realize_and_scan("");
|
||||||
}
|
}
|
||||||
if (scan_hit) {
|
if (scan_hit) {
|
||||||
candidates.push_back(
|
candidates.push_back(
|
||||||
|
|||||||
@@ -115,4 +115,51 @@ auto nixpkgs_probe(const std::string& attr) -> util::Result<NixpkgsInfo> {
|
|||||||
return parse_nix_eval_json(attr, r->stdout_text);
|
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
|
} // namespace cargoxx::resolver
|
||||||
|
|||||||
@@ -33,6 +33,18 @@ auto parse_nix_eval_json(std::string_view attr, std::string_view json)
|
|||||||
// `ResolutionNetworkError` on timeout or evaluator errors.
|
// `ResolutionNetworkError` on timeout or evaluator errors.
|
||||||
auto nixpkgs_probe(const std::string& attr) -> util::Result<NixpkgsInfo>;
|
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
|
// One CMake config-file's IMPORTED targets together with the find_package
|
||||||
// expression derived from its filename stem.
|
// expression derived from its filename stem.
|
||||||
struct NixCmakeCandidate {
|
struct NixCmakeCandidate {
|
||||||
|
|||||||
Reference in New Issue
Block a user