[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);
|
return lockfile::write(lock, lock_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drives the resolver chain (Conan → vcpkg → nix-cmake-scan), running a
|
// Drives the resolver chain (Conan → vcpkg → nix-cmake-scan → pc-scan),
|
||||||
// real `cmd_build` against each candidate via verify_link. On success the
|
// running a real `cmd_build` against each candidate via verify_link.
|
||||||
// overlay carries a confirmed row for the package.
|
// 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,
|
auto run_auto_resolution(const std::string& name, const std::string& version,
|
||||||
const std::vector<std::string>& components,
|
const std::vector<std::string>& components,
|
||||||
const fs::path& overlay_path) -> util::Result<void> {
|
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,
|
return cmd_build(root, /*no_build=*/false, /*release=*/false,
|
||||||
/*target=*/std::nullopt, overlay_path);
|
/*target=*/std::nullopt, overlay_path);
|
||||||
};
|
};
|
||||||
const auto scratch_root =
|
const auto scratch_root = resolver::last_failure_dir(name);
|
||||||
std::filesystem::temp_directory_path() /
|
std::error_code ec;
|
||||||
std::format("cargoxx-discover-{}", std::random_device{}());
|
std::filesystem::remove_all(scratch_root, ec);
|
||||||
|
std::filesystem::create_directories(scratch_root, ec);
|
||||||
|
|
||||||
auto disc = resolver::discover(name, version, components, overlay_path,
|
auto disc = resolver::discover(name, version, components, overlay_path,
|
||||||
scratch_root, build_fn);
|
scratch_root, build_fn);
|
||||||
|
|
||||||
std::error_code ec;
|
|
||||||
std::filesystem::remove_all(scratch_root, ec);
|
|
||||||
|
|
||||||
if (!disc) {
|
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 std::unexpected(disc.error());
|
||||||
}
|
}
|
||||||
return {};
|
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,
|
return cmd_build(root, /*no_build=*/false, /*release=*/false,
|
||||||
/*target=*/std::nullopt, effective_overlay);
|
/*target=*/std::nullopt, effective_overlay);
|
||||||
};
|
};
|
||||||
const auto scratch_root =
|
const auto scratch_root = resolver::last_failure_dir(name);
|
||||||
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);
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
std::filesystem::remove_all(scratch_root, 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) {
|
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 std::unexpected(disc.error());
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ struct Candidate {
|
|||||||
auto try_verify(const Candidate& cand, const std::string& name,
|
auto try_verify(const Candidate& cand, const std::string& name,
|
||||||
const std::string& version_spec,
|
const std::string& version_spec,
|
||||||
const std::vector<std::string>& components,
|
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> {
|
const BuildFn& build_fn) -> util::Result<void> {
|
||||||
VerifyLinkRequest req{
|
VerifyLinkRequest req{
|
||||||
.candidate = cand.recipe,
|
.candidate = cand.recipe,
|
||||||
@@ -95,7 +95,7 @@ auto try_verify(const Candidate& cand, const std::string& name,
|
|||||||
.version_spec = version_spec,
|
.version_spec = version_spec,
|
||||||
.components = components,
|
.components = components,
|
||||||
.overlay_path = overlay_path,
|
.overlay_path = overlay_path,
|
||||||
.scratch_root = scratch_root,
|
.scratch_path = scratch_path,
|
||||||
};
|
};
|
||||||
return verify_link(req, build_fn);
|
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::format("no candidate for '{}' verified", name), "",
|
||||||
std::nullopt, std::nullopt,
|
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,
|
auto verified = try_verify(cand, name, version_spec, components, overlay_path,
|
||||||
scratch_root, build_fn);
|
scratch_path, build_fn);
|
||||||
if (verified) {
|
if (verified) {
|
||||||
return Discovered{
|
return Discovered{
|
||||||
.recipe = std::move(cand.recipe),
|
.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);
|
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
|
} // namespace cargoxx::resolver
|
||||||
|
|||||||
@@ -139,12 +139,17 @@ using BuildFn =
|
|||||||
|
|
||||||
struct VerifyLinkRequest {
|
struct VerifyLinkRequest {
|
||||||
linkdb::Recipe candidate; // recipe under test
|
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 package_name;
|
||||||
std::string version_spec; // user-supplied spec (e.g. "*", "1.2")
|
std::string version_spec; // user-supplied spec (e.g. "*", "1.2")
|
||||||
std::vector<std::string> components;
|
std::vector<std::string> components;
|
||||||
std::filesystem::path overlay_path; // sqlite file we read/write
|
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
|
// Scaffolds a tiny Cargoxx project under `req.scratch_root`, writes a
|
||||||
@@ -163,20 +168,34 @@ struct Discovered {
|
|||||||
std::string source;
|
std::string source;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Walks the full auto-resolution chain for a package not present in the
|
// Walks the full auto-resolution chain for a package not present in
|
||||||
// curated linkdb or the user's overlay:
|
// the user's overlay. Each candidate produced by a probe gets its own
|
||||||
// 1. nixpkgs_probe(name) — confirms the attribute exists, captures
|
// verify_link attempt at
|
||||||
// version + out_path
|
// <scratch_root>/<NN>-<probe>/
|
||||||
// 2. for each of conan_probe, vcpkg_probe, nix_cmake_scan(out_path,…):
|
// e.g. `01-conan/`, `02-vcpkg/`, `03-nix-probe/`, `04-pkg-config/`.
|
||||||
// build a candidate linkdb::Recipe, run verify_link on it, return
|
// Subdirs are NOT cleaned up — they're meant for the user to inspect
|
||||||
// on first success
|
// after a failed `cargoxx add`. The caller wipes `<scratch_root>`
|
||||||
// 3. all candidates failed → ResolutionUnsatisfiable
|
// 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,
|
auto discover(const std::string& name, const std::string& version_spec,
|
||||||
const std::vector<std::string>& components,
|
const std::vector<std::string>& components,
|
||||||
const std::filesystem::path& overlay_path,
|
const std::filesystem::path& overlay_path,
|
||||||
const std::filesystem::path& scratch_root, const BuildFn& build_fn)
|
const std::filesystem::path& scratch_root, const BuildFn& build_fn)
|
||||||
-> util::Result<Discovered>;
|
-> 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
|
// Output of devbox's /v1/resolve API. We capture only the fields cargoxx
|
||||||
// uses; the response carries far more metadata (license, summary, per-
|
// uses; the response carries far more metadata (license, summary, per-
|
||||||
// system store hashes) that we deliberately ignore.
|
// 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 {};
|
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
|
} // namespace
|
||||||
|
|
||||||
auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn)
|
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);
|
auto db = linkdb::Database::open(req.overlay_path);
|
||||||
if (!db) {
|
if (!db) {
|
||||||
@@ -74,26 +53,12 @@ auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn)
|
|||||||
!r) {
|
!r) {
|
||||||
return std::unexpected(r.error());
|
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);
|
fs::create_directories(proj_root / "src", ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
// Roll back the provisional row before bailing.
|
|
||||||
auto db = linkdb::Database::open(req.overlay_path);
|
auto db = linkdb::Database::open(req.overlay_path);
|
||||||
if (db) {
|
if (db) {
|
||||||
(void)db->abort_provisional(req.package_name, req.version_spec,
|
(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());
|
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) {
|
if (auto r = write_text(proj_root / "src" / "main.cpp", "int main() {}\n"); !r) {
|
||||||
auto db = linkdb::Database::open(req.overlay_path);
|
auto db = linkdb::Database::open(req.overlay_path);
|
||||||
if (db) {
|
if (db) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ auto make_request(const std::filesystem::path& parent) -> VerifyLinkRequest {
|
|||||||
.version_spec = "*",
|
.version_spec = "*",
|
||||||
.components = {},
|
.components = {},
|
||||||
.overlay_path = parent / "overlay.sqlite",
|
.overlay_path = parent / "overlay.sqlite",
|
||||||
.scratch_root = parent / "scratch",
|
.scratch_path = parent / "scratch" / "01-conan",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,8 @@ TEST_CASE("verify_link rolls the provisional row back when the build fails",
|
|||||||
REQUIRE(rec.error().code == cargoxx::util::ErrorCode::LinkdbUnknownPackage);
|
REQUIRE(rec.error().code == cargoxx::util::ErrorCode::LinkdbUnknownPackage);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("verify_link cleans up its scratch project", "[resolver][verify_link]") {
|
TEST_CASE("verify_link preserves its scratch project for inspection",
|
||||||
|
"[resolver][verify_link]") {
|
||||||
auto parent = fresh_dir();
|
auto parent = fresh_dir();
|
||||||
auto req = make_request(parent);
|
auto req = make_request(parent);
|
||||||
std::filesystem::path captured;
|
std::filesystem::path captured;
|
||||||
@@ -111,6 +112,7 @@ TEST_CASE("verify_link cleans up its scratch project", "[resolver][verify_link]"
|
|||||||
return cargoxx::util::Result<void>{};
|
return cargoxx::util::Result<void>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
REQUIRE_FALSE(captured.empty());
|
REQUIRE(captured == req.scratch_path);
|
||||||
REQUIRE_FALSE(std::filesystem::exists(captured));
|
REQUIRE(std::filesystem::exists(captured / "Cargoxx.toml"));
|
||||||
|
REQUIRE(std::filesystem::exists(captured / "src" / "main.cpp"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user