From c4b2a1bc553ed80f0c884a56534bde00d3eeca12 Mon Sep 17 00:00:00 2001 From: Amadey Vorontsov Date: Sun, 10 May 2026 12:43:25 +0000 Subject: [PATCH] [M5+] cmd_build merges Cargoxx.lock instead of overwriting --- CHANGELOG.md | 7 +++++++ docs/version-resolution.md | 4 ++-- src/cli/cmd_build.cpp | 43 +++++++++++++++++++++++++++++++------- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fad0d91..d70b1f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,13 @@ All notable changes to cargoxx will be documented in this file. 6 cases against fixtures derived from a real fmt 10.2.1 response; `tests/devbox_resolve_live.cpp` (gated by `CARGOXX_NETWORK_TESTS=1`) hits the live API. +- `cargoxx build` now **merges** rather than overwrites + `Cargoxx.lock`: when an existing entry's `(name, version)` still + matches the manifest, its `nixpkgs_rev` is preserved. New deps and + version bumps still get a null rev (track `nixos-unstable`). Means + pins written by `cargoxx add @` survive arbitrary + rebuilds; cmd_build is now idempotent w.r.t. the lockfile. + `synthesize_lockfile` was renamed `merge_lockfile`. - `cargoxx.resolver::resolve_version(name, version)` orchestrator chains `devbox_resolve` (HTTP, primary) → `nixpkgs_git_resolve` (offline, fallback). Returns a 40-char nixpkgs SHA. Wildcards diff --git a/docs/version-resolution.md b/docs/version-resolution.md index ecce519..51d5561 100644 --- a/docs/version-resolution.md +++ b/docs/version-resolution.md @@ -308,8 +308,8 @@ mitigation is to append `-` to the input attr. | --- | --- | --- | | 1. devbox_resolve + parser | ✅ | `df2c25b` | | 2. nixpkgs_git_resolve fallback | ✅ | `cb82e91` | -| 3. resolve_version + cmd_add wire-up | ✅ | (this commit) | -| 4. cmd_build lockfile merge | pending | — | +| 3. resolve_version + cmd_add wire-up | ✅ | `6f8e9c4` | +| 4. cmd_build lockfile merge | ✅ | (this commit) | | 5. flake codegen for per-dep inputs | pending | — | | 6. SPEC §7/§10 amendment + smoke | pending | — | diff --git a/src/cli/cmd_build.cpp b/src/cli/cmd_build.cpp index 9fc0ed3..bfa100a 100644 --- a/src/cli/cmd_build.cpp +++ b/src/cli/cmd_build.cpp @@ -34,12 +34,27 @@ auto write_text(const fs::path& path, std::string_view content) -> util::Result< return {}; } -// Synthesizes a fresh lockfile from the manifest + resolved recipes. Without a -// resolver (M5), `nixpkgs_rev` is left null — flake codegen falls back to the -// `nixos-unstable` branch and the user gets a working but non-reproducible -// build. M5 will populate the rev. -auto synthesize_lockfile(const manifest::Manifest& m, - const std::vector& recipes) -> lockfile::Lockfile { +// 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` +// idempotent w.r.t. the lockfile — concrete pins written by +// `cargoxx add @` survive subsequent rebuilds. New deps and +// version bumps get a null rev (today's behaviour); the dep tracks the +// shared `nixos-unstable` input until a future `cargoxx update` +// repopulates it. +auto merge_lockfile(const manifest::Manifest& m, + const std::vector& recipes, + const lockfile::Lockfile& prior) -> lockfile::Lockfile { + auto find_prior = [&](const std::string& name, const std::string& version) + -> std::optional { + for (const auto& p : prior.packages) { + if (p.name == name && p.version == version) { + return p; + } + } + return std::nullopt; + }; + lockfile::Lockfile lock; lock.version = 1; @@ -59,12 +74,16 @@ auto synthesize_lockfile(const manifest::Manifest& m, for (std::size_t i = 0; i < m.dependencies.size(); ++i) { const auto& dep = m.dependencies[i]; const auto& rec = recipes[i]; + std::optional rev; + if (auto p = find_prior(dep.name, dep.version_spec); p) { + rev = p->nixpkgs_rev; + } lock.packages.push_back(lockfile::LockfilePackage{ .name = dep.name, .version = dep.version_spec, .dependencies = {}, .nixpkgs_attr = rec.nixpkgs_attr, - .nixpkgs_rev = std::nullopt, + .nixpkgs_rev = std::move(rev), .linkdb_source = rec.source, }); } @@ -130,7 +149,15 @@ auto cmd_build(const fs::path& project_root, bool no_build, bool release, recipes.push_back(std::move(*r)); } - auto lock = synthesize_lockfile(*m, recipes); + lockfile::Lockfile prior; + if (std::error_code ec; std::filesystem::exists(project_root / "Cargoxx.lock", ec)) { + if (auto r = lockfile::parse(project_root / "Cargoxx.lock"); r) { + prior = std::move(*r); + } + // A malformed prior lockfile is non-fatal — we just rebuild from + // scratch. The user can re-pin by running `cargoxx add` again. + } + auto lock = merge_lockfile(*m, recipes, prior); codegen::GenerateInputs in{*m, *layout_result, lock, recipes, project_root}; auto flake_text = codegen::flake_nix(in);