[M7] lockfile: pin top-level nixpkgs_rev + flake_utils_rev

This commit is contained in:
2026-05-16 00:20:11 +00:00
parent 7c10ea2382
commit 43a7d1f09d
9 changed files with 101 additions and 34 deletions

View File

@@ -35,6 +35,34 @@ auto write_text(const fs::path& path, std::string_view content) -> util::Result<
return {};
}
auto query_flake_rev(std::string_view flake_ref) -> std::optional<std::string> {
auto r = exec::run("nix",
{"--extra-experimental-features",
"nix-command flakes", "flake", "metadata", "--json",
std::string{flake_ref}},
exec::ExecOptions{
.cwd = fs::current_path(),
.env_overrides = {},
.timeout = std::chrono::seconds{30},
.inherit_stdio = false,
});
if (!r || r->exit_code != 0) {
return std::nullopt;
}
std::string_view body = r->stdout_text;
constexpr std::string_view key = "\"rev\":\"";
auto pos = body.find(key);
if (pos == std::string_view::npos) {
return std::nullopt;
}
pos += key.size();
auto end = body.find('"', pos);
if (end == std::string_view::npos) {
return std::nullopt;
}
return std::string{body.substr(pos, end - pos)};
}
// Builds the lockfile from the manifest + resolved recipes, **preserving**
// `nixpkgs_rev` for any (name, version) entry that already exists in
// `prior` with a matching key. This is what makes `cargoxx build`
@@ -59,6 +87,13 @@ auto merge_lockfile(const manifest::Manifest& m,
lockfile::Lockfile lock;
lock.version = 1;
lock.nixpkgs_rev_pin = prior.nixpkgs_rev_pin.has_value()
? prior.nixpkgs_rev_pin
: query_flake_rev("github:NixOS/nixpkgs/nixos-unstable");
lock.flake_utils_rev_pin =
prior.flake_utils_rev_pin.has_value()
? prior.flake_utils_rev_pin
: query_flake_rev("github:numtide/flake-utils");
lockfile::LockfilePackage root{
.name = m.package.name,
@@ -114,7 +149,9 @@ namespace {
auto run_nix_cmake(const fs::path& project_root, const std::vector<std::string>& cmake_args,
std::string_view phase) -> util::Result<void> {
std::vector<std::string> args{"develop", "path:./build", "--command", "cmake"};
std::vector<std::string> args{"--extra-experimental-features",
"nix-command flakes", "develop",
"path:./build", "--command", "cmake"};
args.insert(args.end(), cmake_args.begin(), cmake_args.end());
auto r = exec::run("nix", args, exec::ExecOptions{

View File

@@ -19,7 +19,8 @@ auto cmd_test(const fs::path& project_root, bool release,
const auto build_dir = std::format("build/{}", profile);
auto r = exec::run("nix",
{"develop", "path:./build", "--command", "ctest",
{"--extra-experimental-features", "nix-command flakes",
"develop", "path:./build", "--command", "ctest",
"--test-dir", build_dir, "--output-on-failure"},
exec::ExecOptions{
.cwd = project_root,

View File

@@ -99,20 +99,26 @@ auto pinned_inputs_dedup(const std::vector<DepBinding>& bindings)
return out;
}
auto emit_inputs_block(const std::vector<const DepBinding*>& pinned)
-> std::string {
// Always emit the shared toolchain `nixpkgs` and `flake-utils`
// inputs. Per-pinned-dep inputs land between them so the output
// diff stays stable across reruns.
auto emit_inputs_block(const std::vector<const DepBinding*>& pinned,
const lockfile::Lockfile& lock) -> std::string {
auto nixpkgs_url =
lock.nixpkgs_rev_pin && !lock.nixpkgs_rev_pin->empty()
? std::format("github:NixOS/nixpkgs/{}", *lock.nixpkgs_rev_pin)
: std::string{"github:NixOS/nixpkgs/nixos-unstable"};
auto flake_utils_url =
lock.flake_utils_rev_pin && !lock.flake_utils_rev_pin->empty()
? std::format("github:numtide/flake-utils/{}",
*lock.flake_utils_rev_pin)
: std::string{"github:numtide/flake-utils"};
std::string out =
" inputs = {\n"
" nixpkgs.url = \"github:NixOS/nixpkgs/nixos-unstable\";\n";
+ std::format(" nixpkgs.url = \"{}\";\n", nixpkgs_url);
for (const auto* b : pinned) {
out += std::format(" {}.url = \"github:NixOS/nixpkgs/{}\";\n",
b->sanitized, *b->rev);
}
out += " flake-utils.url = \"github:numtide/flake-utils\";\n"
" };\n";
out += std::format(" flake-utils.url = \"{}\";\n", flake_utils_url);
out += " };\n";
return out;
}
@@ -165,7 +171,7 @@ auto flake_nix(const GenerateInputs& in) -> std::string {
out += "{\n";
out += std::format(" description = \"{}\";\n\n", in.manifest.package.name);
out += emit_inputs_block(pinned);
out += emit_inputs_block(pinned, in.lock);
const bool any_pkg_config =
std::ranges::any_of(in.recipes,

View File

@@ -131,6 +131,12 @@ auto parse(const std::filesystem::path& path) -> util::Result<Lockfile> {
if (auto v = root["version"].value<int>()) {
lock.version = *v;
}
if (auto v = root["nixpkgs_rev"].value<std::string>()) {
lock.nixpkgs_rev_pin = *v;
}
if (auto v = root["flake_utils_rev"].value<std::string>()) {
lock.flake_utils_rev_pin = *v;
}
if (const auto* arr = root["package"].as_array()) {
lock.packages.reserve(arr->size());
@@ -154,6 +160,12 @@ auto parse(const std::filesystem::path& path) -> util::Result<Lockfile> {
auto write(const Lockfile& lock, const std::filesystem::path& path) -> util::Result<void> {
toml::table root;
root.insert_or_assign("version", lock.version);
if (lock.nixpkgs_rev_pin) {
root.insert_or_assign("nixpkgs_rev", *lock.nixpkgs_rev_pin);
}
if (lock.flake_utils_rev_pin) {
root.insert_or_assign("flake_utils_rev", *lock.flake_utils_rev_pin);
}
toml::array packages;
for (const auto& p : lock.packages) {

View File

@@ -24,12 +24,12 @@ struct LockfilePackage {
struct Lockfile {
int version = 1;
std::optional<std::string> nixpkgs_rev_pin;
std::optional<std::string> flake_utils_rev_pin;
std::vector<LockfilePackage> packages;
bool operator==(const Lockfile&) const = default;
// The nixpkgs revision is shared across every dep package per SPEC §5.
// Returns the first non-empty rev seen, or nullopt if no deps are pinned.
[[nodiscard]] auto nixpkgs_rev() const -> std::optional<std::string>;
};