[M5+] swap toolchain to gcc15Stdenv + libstdc++

This commit is contained in:
2026-05-13 23:09:28 +00:00
parent b8171e3d03
commit 5db915e576
6 changed files with 14 additions and 187 deletions

View File

@@ -10,33 +10,20 @@
flake-utils.lib.eachDefaultSystem (system: flake-utils.lib.eachDefaultSystem (system:
let let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
llvmPkgs = pkgs.llvmPackages;
libcxxPkgs = {
catch2_3 = pkgs.catch2_3.override {
stdenv = llvmPkgs.libcxxStdenv;
};
};
in { in {
devShells.default = llvmPkgs.libcxxStdenv.mkDerivation { devShells.default = pkgs.gcc15Stdenv.mkDerivation {
name = "cargoxx-dev"; name = "cargoxx-dev";
version = "0.1.0"; version = "0.1.0";
nativeBuildInputs = [ nativeBuildInputs = [
pkgs.cmake pkgs.cmake
pkgs.ninja pkgs.ninja
pkgs.clang-tools
pkgs.git pkgs.git
pkgs.pkg-config pkgs.pkg-config
]; ];
buildInputs = [ buildInputs = [
pkgs.sqlite pkgs.sqlite
pkgs.reproc pkgs.reproc
libcxxPkgs.catch2_3 pkgs.catch2_3
];
env.NIX_CFLAGS_COMPILE = toString [
"-stdlib=libc++"
"-Wno-unused-command-line-argument"
"-B${pkgs.lib.getLib pkgs.libcxx}/lib"
"-isystem ${pkgs.lib.getDev pkgs.libcxx}/include/c++/v1"
]; ];
hardeningDisable = [ "all" ]; hardeningDisable = [ "all" ];
}; };

View File

@@ -18,7 +18,6 @@ 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
@@ -77,7 +76,6 @@ 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));
} }
@@ -146,32 +144,13 @@ auto base_expr(const DepBinding& b) -> std::string {
: std::format("pkgs.{}", b.nixpkgs_attr); : std::format("pkgs.{}", b.nixpkgs_attr);
} }
auto emit_build_input_line(const DepBinding& b) -> std::string {
auto expr = base_expr(b);
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(" {}\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) {
// Dedupe by base expression — two deps that resolve to the same auto expr = base_expr(b);
// (set, attr) but differ only in override are considered distinct if (seen.insert(expr).second) {
// because the dedup key includes the override flag. out += std::format(" {}\n", expr);
auto key = std::format("{}{}", base_expr(b),
b.libcxx_override ? "@libcxx" : "");
if (seen.insert(key).second) {
out += emit_build_input_line(b);
} }
} }
return out; return out;
@@ -197,25 +176,17 @@ auto flake_nix(const GenerateInputs& in) -> std::string {
" let\n" " let\n"
" pkgs = import nixpkgs { inherit system; };\n"; " pkgs = import nixpkgs { inherit system; };\n";
out += emit_let_bindings(pinned); out += emit_let_bindings(pinned);
out += " llvmPkgs = pkgs.llvmPackages;\n" out += " in {\n"
" in {\n" " devShell = pkgs.gcc15Stdenv.mkDerivation {\n"
" devShell = llvmPkgs.libcxxStdenv.mkDerivation {\n"
" name = \"shell\";\n" " name = \"shell\";\n"
" version = \"1.0\";\n" " version = \"1.0\";\n"
" nativeBuildInputs = [\n" " nativeBuildInputs = [\n"
" pkgs.ninja\n" " pkgs.ninja\n"
" pkgs.cmake\n" " pkgs.cmake\n"
" pkgs.clang-tools\n"
" ];\n" " ];\n"
" buildInputs = [\n"; " buildInputs = [\n";
out += emit_build_inputs(bindings); out += emit_build_inputs(bindings);
out += " ];\n" out += " ];\n"
" env.NIX_CFLAGS_COMPILE = toString [\n"
" \"-stdlib=libc++\"\n"
" \"-Wno-unused-command-line-argument\"\n"
" \"-B${pkgs.lib.getLib pkgs.libcxx}/lib\"\n"
" \"-isystem ${pkgs.lib.getDev pkgs.libcxx}/include/c++/v1\"\n"
" ];\n"
" hardeningDisable = [\n" " hardeningDisable = [\n"
" \"all\"\n" " \"all\"\n"
" ];\n" " ];\n"

View File

@@ -185,7 +185,6 @@ 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,
}; };
} }
} }

View File

@@ -14,12 +14,6 @@ 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;
}; };
@@ -43,7 +37,6 @@ 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.
@@ -97,13 +90,6 @@ 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>;
// Drops every overlay row for `package` whose source is not "manual". // Drops every overlay row for `package` whose source is not "manual".
// Used by `cargoxx add` to invalidate stale auto-discovered recipes // Used by `cargoxx add` to invalidate stale auto-discovered recipes
// when the resolver/scanner logic has changed under the user — they // when the resolver/scanner logic has changed under the user — they
@@ -146,11 +132,6 @@ 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>;
// Evict every non-manual overlay row for `package`. Called by // Evict every non-manual overlay row for `package`. Called by
// `cargoxx add` so users never have to manually drop stale // `cargoxx add` so users never have to manually drop stale
// auto-discovered recipes when cargoxx's resolver/scanner logic // auto-discovered recipes when cargoxx's resolver/scanner logic

View File

@@ -90,25 +90,6 @@ 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;
} }
@@ -118,8 +99,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, requires_libcxx_override) " " verified_at) "
"VALUES (?, ?, ?, ?, ?, NULL, 'manual', ?, ?)"; "VALUES (?, ?, ?, ?, ?, NULL, 'manual', ?)";
sqlite3* db = state.handle(); sqlite3* db = state.handle();
sqlite3_stmt* stmt = nullptr; sqlite3_stmt* stmt = nullptr;
@@ -138,7 +119,6 @@ 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);
@@ -157,8 +137,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, requires_libcxx_override) " " verified_at) "
"VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?)"; "VALUES (?, ?, ?, ?, ?, NULL, ?, ?)";
sqlite3* db = state.handle(); sqlite3* db = state.handle();
sqlite3_stmt* stmt = nullptr; sqlite3_stmt* stmt = nullptr;
@@ -175,7 +155,6 @@ 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);
@@ -266,8 +245,7 @@ auto overlay_evict_auto(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();
@@ -298,7 +276,6 @@ 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);
@@ -323,30 +300,6 @@ 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,
@@ -410,15 +363,4 @@ auto Database::evict_auto_recipes(const std::string& package)
return detail::overlay_evict_auto(*overlay_, package); return detail::overlay_evict_auto(*overlay_, package);
} }
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

View File

@@ -141,64 +141,11 @@ auto verify_link(const VerifyLinkRequest& req, const BuildFn& build_fn)
} }
auto build_result = build_fn(proj_root); auto build_result = build_fn(proj_root);
// First-pass success — confirm and we're done.
if (build_result) {
auto db = linkdb::Database::open(req.overlay_path);
if (!db) {
return std::unexpected(db.error());
}
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); auto db = linkdb::Database::open(req.overlay_path);
if (!db) { if (!db) {
return std::unexpected(db.error()); return std::unexpected(db.error());
} }
if (retry_result) { if (build_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) {
@@ -207,7 +154,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(retry_result.error()); return std::unexpected(build_result.error());
} }
} // namespace cargoxx::resolver } // namespace cargoxx::resolver