[M6] preserve every probe's scratch under last-failure/<pkg>/
This commit is contained in:
@@ -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 {};
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user