[M6] preserve every probe's scratch under last-failure/<pkg>/

This commit is contained in:
2026-05-15 13:43:16 +00:00
parent c46f3aa1f0
commit 8b396bcd0f
6 changed files with 79 additions and 72 deletions

View File

@@ -77,9 +77,12 @@ auto record_lockfile_rev(const fs::path& project_root, const std::string& name,
return lockfile::write(lock, lock_path);
}
// Drives the resolver chain (Conan → vcpkg → nix-cmake-scan), running a
// real `cmd_build` against each candidate via verify_link. On success the
// overlay carries a confirmed row for the package.
// Drives the resolver chain (Conan → vcpkg → nix-cmake-scan → pc-scan),
// running a real `cmd_build` against each candidate via verify_link.
// On success the overlay carries a confirmed row for the package.
// Every probe's scratch project is preserved under
// `<XDG>/cargoxx/last-failure/<name>/<NN>-<probe>/` for inspection;
// the dir is wiped clean at the start of each call.
auto run_auto_resolution(const std::string& name, const std::string& version,
const std::vector<std::string>& components,
const fs::path& overlay_path) -> util::Result<void> {
@@ -87,17 +90,18 @@ auto run_auto_resolution(const std::string& name, const std::string& version,
return cmd_build(root, /*no_build=*/false, /*release=*/false,
/*target=*/std::nullopt, overlay_path);
};
const auto scratch_root =
std::filesystem::temp_directory_path() /
std::format("cargoxx-discover-{}", std::random_device{}());
const auto scratch_root = resolver::last_failure_dir(name);
std::error_code ec;
std::filesystem::remove_all(scratch_root, ec);
std::filesystem::create_directories(scratch_root, ec);
auto disc = resolver::discover(name, version, components, overlay_path,
scratch_root, build_fn);
std::error_code ec;
std::filesystem::remove_all(scratch_root, ec);
if (!disc) {
std::cerr << std::format(
"note: every probe attempt's scratch project is preserved at\n"
" {}/ — re-run cmake inside any subdir to reproduce.\n",
scratch_root.string());
return std::unexpected(disc.error());
}
return {};

View File

@@ -160,14 +160,17 @@ auto cmd_build(const fs::path& project_root, bool no_build, bool release,
return cmd_build(root, /*no_build=*/false, /*release=*/false,
/*target=*/std::nullopt, effective_overlay);
};
const auto scratch_root =
std::filesystem::temp_directory_path() /
std::format("cargoxx-discover-{}", std::random_device{}());
auto disc = resolver::discover(name, version, components,
effective_overlay, scratch_root, build_fn);
const auto scratch_root = resolver::last_failure_dir(name);
std::error_code ec;
std::filesystem::remove_all(scratch_root, ec);
std::filesystem::create_directories(scratch_root, ec);
auto disc = resolver::discover(name, version, components,
effective_overlay, scratch_root, build_fn);
if (!disc) {
std::cerr << std::format(
"note: every probe attempt's scratch project is preserved at\n"
" {}/ — re-run cmake inside any subdir to reproduce.\n",
scratch_root.string());
return std::unexpected(disc.error());
}
return {};

View File

@@ -86,7 +86,7 @@ struct Candidate {
auto try_verify(const Candidate& cand, const std::string& name,
const std::string& version_spec,
const std::vector<std::string>& components,
const fs::path& overlay_path, const fs::path& scratch_root,
const fs::path& overlay_path, const fs::path& scratch_path,
const BuildFn& build_fn) -> util::Result<void> {
VerifyLinkRequest req{
.candidate = cand.recipe,
@@ -95,7 +95,7 @@ auto try_verify(const Candidate& cand, const std::string& name,
.version_spec = version_spec,
.components = components,
.overlay_path = overlay_path,
.scratch_root = scratch_root,
.scratch_path = scratch_path,
};
return verify_link(req, build_fn);
}
@@ -181,9 +181,12 @@ auto discover(const std::string& name, const std::string& version_spec,
std::format("no candidate for '{}' verified", name), "",
std::nullopt, std::nullopt,
};
for (auto& cand : candidates) {
for (std::size_t i = 0; i < candidates.size(); ++i) {
auto& cand = candidates[i];
auto subdir = std::format("{:02}-{}", i + 1, cand.source);
auto scratch_path = scratch_root / subdir;
auto verified = try_verify(cand, name, version_spec, components, overlay_path,
scratch_root, build_fn);
scratch_path, build_fn);
if (verified) {
return Discovered{
.recipe = std::move(cand.recipe),
@@ -196,4 +199,17 @@ auto discover(const std::string& name, const std::string& version_spec,
return std::unexpected(last_error);
}
auto last_failure_dir(const std::string& package_name) -> fs::path {
auto base = []() -> fs::path {
if (auto* xdg = std::getenv("XDG_CACHE_HOME"); xdg && *xdg) {
return fs::path{xdg} / "cargoxx" / "last-failure";
}
if (auto* home = std::getenv("HOME"); home && *home) {
return fs::path{home} / ".cache" / "cargoxx" / "last-failure";
}
return fs::current_path() / ".cargoxx-last-failure";
}();
return base / package_name;
}
} // namespace cargoxx::resolver

View File

@@ -139,12 +139,17 @@ using BuildFn =
struct VerifyLinkRequest {
linkdb::Recipe candidate; // recipe under test
std::string source; // "conan" | "vcpkg" | "nix-probe"
std::string source; // "conan" | "vcpkg" | "nix-probe" | "pkg-config" | …
std::string package_name;
std::string version_spec; // user-supplied spec (e.g. "*", "1.2")
std::vector<std::string> components;
std::filesystem::path overlay_path; // sqlite file we read/write
std::filesystem::path scratch_root; // parent dir for the tmp project
// Exact directory the verify project lives in. verify_link creates
// it (and its `src/` subdir) but never deletes it; the caller is
// responsible for cleanup. discover() puts each probe's project at
// a distinct path under `<last-failure-dir>/<NN>-<probe>/` so
// every attempt is preserved for inspection.
std::filesystem::path scratch_path;
};
// Scaffolds a tiny Cargoxx project under `req.scratch_root`, writes a
@@ -163,20 +168,34 @@ struct Discovered {
std::string source;
};
// Walks the full auto-resolution chain for a package not present in the
// curated linkdb or the user's overlay:
// 1. nixpkgs_probe(name) — confirms the attribute exists, captures
// version + out_path
// 2. for each of conan_probe, vcpkg_probe, nix_cmake_scan(out_path,…):
// build a candidate linkdb::Recipe, run verify_link on it, return
// on first success
// 3. all candidates failed → ResolutionUnsatisfiable
// Walks the full auto-resolution chain for a package not present in
// the user's overlay. Each candidate produced by a probe gets its own
// verify_link attempt at
// <scratch_root>/<NN>-<probe>/
// e.g. `01-conan/`, `02-vcpkg/`, `03-nix-probe/`, `04-pkg-config/`.
// Subdirs are NOT cleaned up — they're meant for the user to inspect
// after a failed `cargoxx add`. The caller wipes `<scratch_root>`
// clean at the start of each invocation (cmd_add / cmd_build).
//
// Returns `Discovered` on the first verify_link success;
// `ResolutionUnsatisfiable` when all probes are exhausted; or the
// underlying error from `nixpkgs_probe`.
auto discover(const std::string& name, const std::string& version_spec,
const std::vector<std::string>& components,
const std::filesystem::path& overlay_path,
const std::filesystem::path& scratch_root, const BuildFn& build_fn)
-> util::Result<Discovered>;
// The on-disk parent dir that holds per-package verify_link scratch
// projects. Resolves to:
// $XDG_CACHE_HOME/cargoxx/last-failure/<pkg>/ (when XDG_CACHE_HOME is set)
// $HOME/.cache/cargoxx/last-failure/<pkg>/ (else if HOME is set)
// <cwd>/.cargoxx-last-failure/<pkg>/ (fallback)
// Each `cargoxx add <pkg>` (and `cmd_build`'s auto-resolve) wipes
// this dir clean, then discover() repopulates it with one subdir per
// probe attempt.
auto last_failure_dir(const std::string& package_name) -> std::filesystem::path;
// Output of devbox's /v1/resolve API. We capture only the fields cargoxx
// uses; the response carries far more metadata (license, summary, per-
// system store hashes) that we deliberately ignore.

View File

@@ -31,24 +31,6 @@ auto write_text(const fs::path& path, std::string_view content) -> util::Result<
return {};
}
class TmpProject {
public:
explicit TmpProject(fs::path root) : root_(std::move(root)) {}
~TmpProject() {
std::error_code ec;
fs::remove_all(root_, ec);
}
TmpProject(const TmpProject&) = delete;
TmpProject& operator=(const TmpProject&) = delete;
TmpProject(TmpProject&&) = delete;
TmpProject& operator=(TmpProject&&) = delete;
[[nodiscard]] auto path() const -> const fs::path& { return root_; }
private:
fs::path root_;
};
} // namespace
auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn)
@@ -61,9 +43,6 @@ auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn)
});
}
// Insert the provisional overlay row. It persists through the build's
// own Database::open() call, which is how the candidate recipe gets
// surfaced to cmake_lists codegen via Database::resolve.
{
auto db = linkdb::Database::open(req.overlay_path);
if (!db) {
@@ -74,26 +53,12 @@ auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn)
!r) {
return std::unexpected(r.error());
}
} // close db before re-opening it inside build_fn
// Scaffold a tmp project. We bypass cargoxx::cli::cmd_new to avoid a
// resolver-on-cli dependency cycle; the manifest + src/main.cpp are
// exactly what cmd_build needs for its codegen.
std::error_code ec;
fs::create_directories(req.scratch_root, ec);
if (ec) {
return std::unexpected(io_error(
std::format("cannot create scratch root: {}", ec.message()),
req.scratch_root));
}
auto proj_root = req.scratch_root /
std::format("cargoxx-verify-{}-{}", req.package_name,
std::random_device{}());
TmpProject scope{proj_root};
const auto& proj_root = req.scratch_path;
std::error_code ec;
fs::create_directories(proj_root / "src", ec);
if (ec) {
// Roll back the provisional row before bailing.
auto db = linkdb::Database::open(req.overlay_path);
if (db) {
(void)db->abort_provisional(req.package_name, req.version_spec,
@@ -129,8 +94,6 @@ auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn)
return std::unexpected(r.error());
}
// Empty main — exercises find_package + target + linker without
// requiring per-package symbol knowledge.
if (auto r = write_text(proj_root / "src" / "main.cpp", "int main() {}\n"); !r) {
auto db = linkdb::Database::open(req.overlay_path);
if (db) {