[M5+] verify_link retries with libc++ stdenv override on link failure
This commit is contained in:
@@ -18,6 +18,7 @@ struct DepBinding {
|
|||||||
std::string sanitized; // "nixpkgs_fmt_10_2_1" — input attr,
|
std::string sanitized; // "nixpkgs_fmt_10_2_1" — input attr,
|
||||||
// let-binding stem, lambda param
|
// let-binding stem, lambda param
|
||||||
std::optional<std::string> rev; // pinned commit (null → unpinned)
|
std::optional<std::string> rev; // pinned commit (null → unpinned)
|
||||||
|
bool libcxx_override = false; // wrap in .override { stdenv = libcxx }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Replaces every char outside [a-zA-Z0-9_] with '_'. The result is safe
|
// Replaces every char outside [a-zA-Z0-9_] with '_'. The result is safe
|
||||||
@@ -76,6 +77,7 @@ auto build_bindings(const GenerateInputs& in) -> std::vector<DepBinding> {
|
|||||||
.nixpkgs_attr = std::move(attr),
|
.nixpkgs_attr = std::move(attr),
|
||||||
.sanitized = sanitize_input_attr(dep.name, dep.version_spec),
|
.sanitized = sanitize_input_attr(dep.name, dep.version_spec),
|
||||||
.rev = std::move(ref.rev),
|
.rev = std::move(ref.rev),
|
||||||
|
.libcxx_override = rec.requires_libcxx_override,
|
||||||
};
|
};
|
||||||
out.push_back(std::move(b));
|
out.push_back(std::move(b));
|
||||||
}
|
}
|
||||||
@@ -138,20 +140,36 @@ auto emit_let_bindings(const std::vector<const DepBinding*>& pinned)
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto base_expr(const DepBinding& b) -> std::string {
|
||||||
|
return b.rev && !b.rev->empty()
|
||||||
|
? std::format("pkgs_{}.{}", b.sanitized, b.nixpkgs_attr)
|
||||||
|
: std::format("pkgs.{}", b.nixpkgs_attr);
|
||||||
|
}
|
||||||
|
|
||||||
auto emit_build_input_line(const DepBinding& b) -> std::string {
|
auto emit_build_input_line(const DepBinding& b) -> std::string {
|
||||||
if (b.rev && !b.rev->empty()) {
|
auto expr = base_expr(b);
|
||||||
return std::format(" pkgs_{}.{}\n", b.sanitized, b.nixpkgs_attr);
|
if (b.libcxx_override) {
|
||||||
|
// Stdenv swap (libc++) plus a `doCheck = false` overrideAttrs
|
||||||
|
// to skip the package's test suite — without it, the rebuilt
|
||||||
|
// dep would re-run its full check phase under the new stdenv,
|
||||||
|
// adding minutes to hours of evaluation/build time.
|
||||||
|
expr = std::format(
|
||||||
|
"(({}.override {{ stdenv = llvmPkgs.libcxxStdenv; }})"
|
||||||
|
".overrideAttrs (old: {{ doCheck = false; }}))",
|
||||||
|
expr);
|
||||||
}
|
}
|
||||||
return std::format(" pkgs.{}\n", b.nixpkgs_attr);
|
return std::format(" {}\n", expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto emit_build_inputs(const std::vector<DepBinding>& bindings) -> std::string {
|
auto emit_build_inputs(const std::vector<DepBinding>& bindings) -> std::string {
|
||||||
std::set<std::string> seen;
|
std::set<std::string> seen;
|
||||||
std::string out;
|
std::string out;
|
||||||
for (const auto& b : bindings) {
|
for (const auto& b : bindings) {
|
||||||
auto key = b.rev && !b.rev->empty()
|
// Dedupe by base expression — two deps that resolve to the same
|
||||||
? std::format("pkgs_{}.{}", b.sanitized, b.nixpkgs_attr)
|
// (set, attr) but differ only in override are considered distinct
|
||||||
: std::format("pkgs.{}", b.nixpkgs_attr);
|
// because the dedup key includes the override flag.
|
||||||
|
auto key = std::format("{}{}", base_expr(b),
|
||||||
|
b.libcxx_override ? "@libcxx" : "");
|
||||||
if (seen.insert(key).second) {
|
if (seen.insert(key).second) {
|
||||||
out += emit_build_input_line(b);
|
out += emit_build_input_line(b);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ auto Database::resolve(const std::string& package, const std::string& version,
|
|||||||
.find_package = row.find_package,
|
.find_package = row.find_package,
|
||||||
.targets = row.targets,
|
.targets = row.targets,
|
||||||
.source = row.source,
|
.source = row.source,
|
||||||
|
.requires_libcxx_override = row.requires_libcxx_override,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ struct Recipe {
|
|||||||
std::string find_package; // post-substitution
|
std::string find_package; // post-substitution
|
||||||
std::vector<std::string> targets; // post-substitution
|
std::vector<std::string> targets; // post-substitution
|
||||||
std::string source; // 'curated' | 'manual' | etc.
|
std::string source; // 'curated' | 'manual' | etc.
|
||||||
|
// Set when the dep needs `.override { stdenv = llvmPkgs.libcxxStdenv; }`
|
||||||
|
// to link against the user's libc++-built project (e.g. nixpkgs ships
|
||||||
|
// it built against libstdc++ — abseil-cpp, llvm). verify_link toggles
|
||||||
|
// this on after a libstdc++/libc++ link failure. Codegen wraps the
|
||||||
|
// buildInputs entry in the override expression when true.
|
||||||
|
bool requires_libcxx_override = false;
|
||||||
|
|
||||||
bool operator==(const Recipe&) const = default;
|
bool operator==(const Recipe&) const = default;
|
||||||
};
|
};
|
||||||
@@ -37,6 +43,7 @@ struct OverlayRow {
|
|||||||
std::vector<std::string> targets;
|
std::vector<std::string> targets;
|
||||||
std::string source;
|
std::string source;
|
||||||
std::int64_t verified_at = 0;
|
std::int64_t verified_at = 0;
|
||||||
|
bool requires_libcxx_override = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// RAII wrapper for an open sqlite3 connection used by the overlay database.
|
// RAII wrapper for an open sqlite3 connection used by the overlay database.
|
||||||
@@ -90,6 +97,13 @@ auto overlay_delete_recipe(OverlayState& state, const std::string& package,
|
|||||||
const std::string& source)
|
const std::string& source)
|
||||||
-> cargoxx::util::Result<void>;
|
-> cargoxx::util::Result<void>;
|
||||||
|
|
||||||
|
// Toggle the libcxx-override flag for an existing row. Used by
|
||||||
|
// `verify_link` after a libstdc++/libc++ link mismatch.
|
||||||
|
auto overlay_set_libcxx_override(OverlayState& state, const std::string& package,
|
||||||
|
const std::string& version_range,
|
||||||
|
const std::string& source, bool value)
|
||||||
|
-> cargoxx::util::Result<void>;
|
||||||
|
|
||||||
auto overlay_query(OverlayState& state, const std::string& package)
|
auto overlay_query(OverlayState& state, const std::string& package)
|
||||||
-> cargoxx::util::Result<std::vector<OverlayRow>>;
|
-> cargoxx::util::Result<std::vector<OverlayRow>>;
|
||||||
|
|
||||||
@@ -125,6 +139,11 @@ class Database {
|
|||||||
const std::string& version_range,
|
const std::string& version_range,
|
||||||
const std::string& source) -> util::Result<void>;
|
const std::string& source) -> util::Result<void>;
|
||||||
|
|
||||||
|
auto set_libcxx_override(const std::string& package,
|
||||||
|
const std::string& version_range,
|
||||||
|
const std::string& source, bool value)
|
||||||
|
-> util::Result<void>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Database() = default;
|
Database() = default;
|
||||||
std::map<std::string, std::vector<detail::CuratedRecipe>> curated_;
|
std::map<std::string, std::vector<detail::CuratedRecipe>> curated_;
|
||||||
|
|||||||
@@ -90,6 +90,25 @@ auto overlay_open(const std::filesystem::path& path)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the libcxx-override column to legacy overlay files. SQLite's
|
||||||
|
// ADD COLUMN errors when the column already exists; treat that as
|
||||||
|
// success (the column is present, which is all we need).
|
||||||
|
constexpr const char* MIGRATE =
|
||||||
|
"ALTER TABLE recipes ADD COLUMN requires_libcxx_override INTEGER NOT NULL DEFAULT 0";
|
||||||
|
char* mig_err = nullptr;
|
||||||
|
if (sqlite3_exec(state->handle(), MIGRATE, nullptr, nullptr, &mig_err) != SQLITE_OK) {
|
||||||
|
if (mig_err && std::string_view{mig_err}.find("duplicate column") ==
|
||||||
|
std::string_view::npos) {
|
||||||
|
std::string msg = std::format("cannot migrate overlay schema: {}",
|
||||||
|
mig_err ? mig_err : "?");
|
||||||
|
sqlite3_free(mig_err);
|
||||||
|
return std::unexpected(util::Error{
|
||||||
|
util::ErrorCode::LinkdbCorrupt, std::move(msg), "", path, std::nullopt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sqlite3_free(mig_err);
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,8 +118,8 @@ auto overlay_insert_manual(OverlayState& state, const std::string& package,
|
|||||||
constexpr const char* SQL =
|
constexpr const char* SQL =
|
||||||
"INSERT OR REPLACE INTO recipes "
|
"INSERT OR REPLACE INTO recipes "
|
||||||
"(package, version_range, nixpkgs_attr, find_package, targets, components, source, "
|
"(package, version_range, nixpkgs_attr, find_package, targets, components, source, "
|
||||||
" verified_at) "
|
" verified_at, requires_libcxx_override) "
|
||||||
"VALUES (?, ?, ?, ?, ?, NULL, 'manual', ?)";
|
"VALUES (?, ?, ?, ?, ?, NULL, 'manual', ?, ?)";
|
||||||
|
|
||||||
sqlite3* db = state.handle();
|
sqlite3* db = state.handle();
|
||||||
sqlite3_stmt* stmt = nullptr;
|
sqlite3_stmt* stmt = nullptr;
|
||||||
@@ -119,6 +138,7 @@ auto overlay_insert_manual(OverlayState& state, const std::string& package,
|
|||||||
sqlite3_bind_text(stmt, 4, r.find_package.c_str(), -1, SQLITE_TRANSIENT);
|
sqlite3_bind_text(stmt, 4, r.find_package.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
sqlite3_bind_text(stmt, 5, targets_str.c_str(), -1, SQLITE_TRANSIENT);
|
sqlite3_bind_text(stmt, 5, targets_str.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
sqlite3_bind_int64(stmt, 6, now);
|
sqlite3_bind_int64(stmt, 6, now);
|
||||||
|
sqlite3_bind_int(stmt, 7, r.requires_libcxx_override ? 1 : 0);
|
||||||
|
|
||||||
auto rc = sqlite3_step(stmt);
|
auto rc = sqlite3_step(stmt);
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
@@ -137,8 +157,8 @@ auto overlay_insert(OverlayState& state, const std::string& package,
|
|||||||
constexpr const char* SQL =
|
constexpr const char* SQL =
|
||||||
"INSERT OR REPLACE INTO recipes "
|
"INSERT OR REPLACE INTO recipes "
|
||||||
"(package, version_range, nixpkgs_attr, find_package, targets, components, source, "
|
"(package, version_range, nixpkgs_attr, find_package, targets, components, source, "
|
||||||
" verified_at) "
|
" verified_at, requires_libcxx_override) "
|
||||||
"VALUES (?, ?, ?, ?, ?, NULL, ?, ?)";
|
"VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?)";
|
||||||
|
|
||||||
sqlite3* db = state.handle();
|
sqlite3* db = state.handle();
|
||||||
sqlite3_stmt* stmt = nullptr;
|
sqlite3_stmt* stmt = nullptr;
|
||||||
@@ -155,6 +175,7 @@ auto overlay_insert(OverlayState& state, const std::string& package,
|
|||||||
sqlite3_bind_text(stmt, 5, targets_str.c_str(), -1, SQLITE_TRANSIENT);
|
sqlite3_bind_text(stmt, 5, targets_str.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
sqlite3_bind_text(stmt, 6, source.c_str(), -1, SQLITE_TRANSIENT);
|
sqlite3_bind_text(stmt, 6, source.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
sqlite3_bind_int64(stmt, 7, verified_at);
|
sqlite3_bind_int64(stmt, 7, verified_at);
|
||||||
|
sqlite3_bind_int(stmt, 8, r.requires_libcxx_override ? 1 : 0);
|
||||||
|
|
||||||
auto rc = sqlite3_step(stmt);
|
auto rc = sqlite3_step(stmt);
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
@@ -223,7 +244,8 @@ auto overlay_delete_recipe(OverlayState& state, const std::string& package,
|
|||||||
auto overlay_query(OverlayState& state, const std::string& package)
|
auto overlay_query(OverlayState& state, const std::string& package)
|
||||||
-> util::Result<std::vector<OverlayRow>> {
|
-> util::Result<std::vector<OverlayRow>> {
|
||||||
constexpr const char* SQL =
|
constexpr const char* SQL =
|
||||||
"SELECT version_range, nixpkgs_attr, find_package, targets, source, verified_at "
|
"SELECT version_range, nixpkgs_attr, find_package, targets, source, verified_at, "
|
||||||
|
" requires_libcxx_override "
|
||||||
"FROM recipes WHERE package = ?";
|
"FROM recipes WHERE package = ?";
|
||||||
|
|
||||||
sqlite3* db = state.handle();
|
sqlite3* db = state.handle();
|
||||||
@@ -254,6 +276,7 @@ auto overlay_query(OverlayState& state, const std::string& package)
|
|||||||
}
|
}
|
||||||
row.source = column_text(stmt, 4);
|
row.source = column_text(stmt, 4);
|
||||||
row.verified_at = sqlite3_column_int64(stmt, 5);
|
row.verified_at = sqlite3_column_int64(stmt, 5);
|
||||||
|
row.requires_libcxx_override = sqlite3_column_int(stmt, 6) != 0;
|
||||||
out.push_back(std::move(row));
|
out.push_back(std::move(row));
|
||||||
}
|
}
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
@@ -278,6 +301,30 @@ auto overlay_is_fresh(const OverlayRow& row, std::int64_t now) -> bool {
|
|||||||
return (now - row.verified_at) < THIRTY_DAYS_SECONDS;
|
return (now - row.verified_at) < THIRTY_DAYS_SECONDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto overlay_set_libcxx_override(OverlayState& state, const std::string& package,
|
||||||
|
const std::string& version_range,
|
||||||
|
const std::string& source, bool value)
|
||||||
|
-> util::Result<void> {
|
||||||
|
constexpr const char* SQL =
|
||||||
|
"UPDATE recipes SET requires_libcxx_override = ? "
|
||||||
|
"WHERE package = ? AND version_range = ? AND source = ?";
|
||||||
|
sqlite3* db = state.handle();
|
||||||
|
sqlite3_stmt* stmt = nullptr;
|
||||||
|
if (sqlite3_prepare_v2(db, SQL, -1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
|
return std::unexpected(sqlite_error(db, "prepare update libcxx_override"));
|
||||||
|
}
|
||||||
|
sqlite3_bind_int(stmt, 1, value ? 1 : 0);
|
||||||
|
sqlite3_bind_text(stmt, 2, package.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_text(stmt, 3, version_range.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_text(stmt, 4, source.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
|
auto rc = sqlite3_step(stmt);
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
if (rc != SQLITE_DONE) {
|
||||||
|
return std::unexpected(sqlite_error(db, "step update libcxx_override"));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
auto Database::add_manual(const std::string& package, const std::string& version_range,
|
auto Database::add_manual(const std::string& package, const std::string& version_range,
|
||||||
@@ -333,4 +380,15 @@ auto Database::abort_provisional(const std::string& package,
|
|||||||
return detail::overlay_delete_recipe(*overlay_, package, version_range, source);
|
return detail::overlay_delete_recipe(*overlay_, package, version_range, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Database::set_libcxx_override(const std::string& package,
|
||||||
|
const std::string& version_range,
|
||||||
|
const std::string& source, bool value)
|
||||||
|
-> util::Result<void> {
|
||||||
|
if (auto ok = require_overlay(overlay_); !ok) {
|
||||||
|
return std::unexpected(ok.error());
|
||||||
|
}
|
||||||
|
return detail::overlay_set_libcxx_override(*overlay_, package, version_range,
|
||||||
|
source, value);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace cargoxx::linkdb
|
} // namespace cargoxx::linkdb
|
||||||
|
|||||||
@@ -142,12 +142,63 @@ auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn)
|
|||||||
|
|
||||||
auto build_result = build_fn(proj_root);
|
auto build_result = build_fn(proj_root);
|
||||||
|
|
||||||
// Always re-open the db to flip the provisional row's fate.
|
// First-pass success — confirm and we're done.
|
||||||
|
if (build_result) {
|
||||||
auto db = linkdb::Database::open(req.overlay_path);
|
auto db = linkdb::Database::open(req.overlay_path);
|
||||||
if (!db) {
|
if (!db) {
|
||||||
return std::unexpected(db.error());
|
return std::unexpected(db.error());
|
||||||
}
|
}
|
||||||
if (build_result) {
|
if (auto r = db->confirm_provisional(req.package_name, req.version_spec,
|
||||||
|
req.source);
|
||||||
|
!r) {
|
||||||
|
return std::unexpected(r.error());
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// First pass failed. Try once more with a libstdc++/libc++ ABI
|
||||||
|
// workaround: flip `requires_libcxx_override` on the overlay row,
|
||||||
|
// which makes flake codegen wrap the dep in
|
||||||
|
// `.override { stdenv = llvmPkgs.libcxxStdenv; }`. cmd_build
|
||||||
|
// re-resolves and regenerates the flake, so the second build_fn
|
||||||
|
// call sees the override.
|
||||||
|
std::cerr << std::format(
|
||||||
|
"verify_link: first build of '{}' failed; retrying with "
|
||||||
|
"libc++/doCheck=false override\n",
|
||||||
|
req.package_name);
|
||||||
|
{
|
||||||
|
auto db = linkdb::Database::open(req.overlay_path);
|
||||||
|
if (!db) {
|
||||||
|
return std::unexpected(db.error());
|
||||||
|
}
|
||||||
|
if (auto r = db->set_libcxx_override(req.package_name, req.version_spec,
|
||||||
|
req.source, true);
|
||||||
|
!r) {
|
||||||
|
// Fall through to the abort path below — couldn't even mark.
|
||||||
|
(void)db->abort_provisional(req.package_name, req.version_spec,
|
||||||
|
req.source);
|
||||||
|
return std::unexpected(r.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipe CMake's per-profile build dirs. The first pass's
|
||||||
|
// CMakeCache.txt has cached `LLVM_DIR` (and other find_package
|
||||||
|
// results) at the default-stdenv path; without clearing it, the
|
||||||
|
// retry's regenerated flake.nix has no effect on what
|
||||||
|
// find_package sees.
|
||||||
|
{
|
||||||
|
std::error_code wipe_ec;
|
||||||
|
for (auto* sub : {"debug", "release"}) {
|
||||||
|
std::filesystem::remove_all(proj_root / "build" / sub, wipe_ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto retry_result = build_fn(proj_root);
|
||||||
|
auto db = linkdb::Database::open(req.overlay_path);
|
||||||
|
if (!db) {
|
||||||
|
return std::unexpected(db.error());
|
||||||
|
}
|
||||||
|
if (retry_result) {
|
||||||
if (auto r = db->confirm_provisional(req.package_name, req.version_spec,
|
if (auto r = db->confirm_provisional(req.package_name, req.version_spec,
|
||||||
req.source);
|
req.source);
|
||||||
!r) {
|
!r) {
|
||||||
@@ -156,7 +207,7 @@ auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
(void)db->abort_provisional(req.package_name, req.version_spec, req.source);
|
(void)db->abort_provisional(req.package_name, req.version_spec, req.source);
|
||||||
return std::unexpected(build_result.error());
|
return std::unexpected(retry_result.error());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cargoxx::resolver
|
} // namespace cargoxx::resolver
|
||||||
|
|||||||
Reference in New Issue
Block a user