[M5+] use devbox attr_paths for pinned-dep nixpkgs attr

This commit is contained in:
2026-05-10 13:06:35 +00:00
parent 935e8d5f79
commit 1604b1d5a8
6 changed files with 86 additions and 27 deletions

View File

@@ -38,7 +38,8 @@ auto recipe_already_known(const std::string& name, const std::string& version,
// Overwrites any existing entry for the same (name, version). Other
// lockfile entries (root package, sibling deps) are preserved verbatim.
auto record_lockfile_rev(const fs::path& project_root, const std::string& name,
const std::string& version, const std::string& rev)
const std::string& version,
const resolver::ResolvedVersion& resolved)
-> util::Result<void> {
const auto lock_path = project_root / "Cargoxx.lock";
lockfile::Lockfile lock;
@@ -56,7 +57,8 @@ auto record_lockfile_rev(const fs::path& project_root, const std::string& name,
bool replaced = false;
for (auto& p : lock.packages) {
if (p.name == name && p.version == version) {
p.nixpkgs_rev = rev;
p.nixpkgs_rev = resolved.nixpkgs_rev;
p.nixpkgs_attr = resolved.nixpkgs_attr;
replaced = true;
break;
}
@@ -66,8 +68,8 @@ auto record_lockfile_rev(const fs::path& project_root, const std::string& name,
.name = name,
.version = version,
.dependencies = {},
.nixpkgs_attr = std::nullopt,
.nixpkgs_rev = rev,
.nixpkgs_attr = resolved.nixpkgs_attr,
.nixpkgs_rev = resolved.nixpkgs_rev,
.linkdb_source = std::nullopt,
});
}
@@ -169,13 +171,13 @@ auto cmd_add(const fs::path& project_root, const std::string& name,
const bool wildcard = effective_version == "*";
auto* env = std::getenv("CARGOXX_NO_AUTORESOLVE");
const bool autoresolve_disabled = env != nullptr && *env != 0;
std::optional<std::string> resolved_rev;
std::optional<resolver::ResolvedVersion> resolved;
if (!wildcard && !autoresolve_disabled) {
auto rev = resolver::resolve_version(name, effective_version);
if (!rev) {
return std::unexpected(rev.error());
auto r = resolver::resolve_version(name, effective_version);
if (!r) {
return std::unexpected(r.error());
}
resolved_rev = std::move(*rev);
resolved = std::move(*r);
}
m->dependencies.push_back(manifest::Dependency{
@@ -188,9 +190,9 @@ auto cmd_add(const fs::path& project_root, const std::string& name,
return std::unexpected(r.error());
}
if (resolved_rev) {
if (resolved) {
if (auto r = record_lockfile_rev(project_root, name, effective_version,
*resolved_rev);
*resolved);
!r) {
return std::unexpected(r.error());
}

View File

@@ -75,14 +75,23 @@ auto merge_lockfile(const manifest::Manifest& m,
const auto& dep = m.dependencies[i];
const auto& rec = recipes[i];
std::optional<std::string> rev;
// The recipe's nixpkgs_attr is correct for unpinned deps (it's
// curated against nixos-unstable). When the prior lockfile
// already carries an attr — written by `cargoxx add <pkg>@<v>`
// from devbox's authoritative attr_paths for the pinned rev —
// that one wins.
std::string attr = rec.nixpkgs_attr;
if (auto p = find_prior(dep.name, dep.version_spec); p) {
rev = p->nixpkgs_rev;
if (p->nixpkgs_attr && !p->nixpkgs_attr->empty()) {
attr = *p->nixpkgs_attr;
}
}
lock.packages.push_back(lockfile::LockfilePackage{
.name = dep.name,
.version = dep.version_spec,
.dependencies = {},
.nixpkgs_attr = rec.nixpkgs_attr,
.nixpkgs_attr = std::move(attr),
.nixpkgs_rev = std::move(rev),
.linkdb_source = rec.source,
});

View File

@@ -42,14 +42,19 @@ auto sanitize_input_attr(std::string_view name, std::string_view version)
return std::format("nixpkgs_{}_{}", sanitize(name), sanitize(version));
}
auto find_lockfile_rev(const lockfile::Lockfile& lock, const std::string& name,
const std::string& version) -> std::optional<std::string> {
struct LockfileRef {
std::optional<std::string> rev;
std::optional<std::string> attr;
};
auto find_lockfile_ref(const lockfile::Lockfile& lock, const std::string& name,
const std::string& version) -> LockfileRef {
for (const auto& p : lock.packages) {
if (p.name == name && p.version == version) {
return p.nixpkgs_rev;
return LockfileRef{.rev = p.nixpkgs_rev, .attr = p.nixpkgs_attr};
}
}
return std::nullopt;
return LockfileRef{};
}
auto build_bindings(const GenerateInputs& in) -> std::vector<DepBinding> {
@@ -58,12 +63,19 @@ auto build_bindings(const GenerateInputs& in) -> std::vector<DepBinding> {
for (std::size_t i = 0; i < in.manifest.dependencies.size(); ++i) {
const auto& dep = in.manifest.dependencies[i];
const auto& rec = in.recipes[i];
auto ref = find_lockfile_ref(in.lock, dep.name, dep.version_spec);
// For pinned deps the lockfile's nixpkgs_attr is authoritative
// (it came from devbox's attr_paths for this specific rev). The
// curated recipe's attr only applies to nixos-unstable, so it's
// wrong when the dep pulls from a different rev.
std::string attr = (ref.attr && !ref.attr->empty()) ? *ref.attr
: rec.nixpkgs_attr;
DepBinding b{
.name = dep.name,
.version = dep.version_spec,
.nixpkgs_attr = rec.nixpkgs_attr,
.nixpkgs_attr = std::move(attr),
.sanitized = sanitize_input_attr(dep.name, dep.version_spec),
.rev = find_lockfile_rev(in.lock, dep.name, dep.version_spec),
.rev = std::move(ref.rev),
};
out.push_back(std::move(b));
}

View File

@@ -174,15 +174,25 @@ auto nixpkgs_git_resolve(const std::string& name, const std::string& version,
std::optional<std::filesystem::path> repo_path = std::nullopt)
-> util::Result<std::string>;
// What `resolve_version` returns: a nixpkgs commit and the attribute
// path under which the package lives at that commit. The attr is
// authoritative *for that rev* — the cargoxx-curated linkdb attrs
// (e.g. `fmt_10`) only apply to the unpinned `nixos-unstable` set.
// When devbox supplies multiple attr paths the first is canonical.
struct ResolvedVersion {
std::string nixpkgs_rev;
std::string nixpkgs_attr;
};
// Top-level orchestrator: try `devbox_resolve` first, then
// `nixpkgs_git_resolve` as fallback. Returns a 40-char nixpkgs SHA, or
// `nixpkgs_git_resolve` as fallback. Returns the rev + attr or
// `ResolutionVersionNotFound` when both probes come back empty.
//
// Use this from `cargoxx add <pkg>@<concrete-ver>` to capture the rev
// that lockfile/codegen will pin. Wildcards (`*`, empty) should NOT be
// passed — they are not concrete versions and the resolver returns
// `ResolutionVersionNotFound` for them by design.
// Use this from `cargoxx add <pkg>@<concrete-ver>` to capture the pin
// that lockfile/codegen will use. Wildcards (`*`, empty) should NOT
// be passed — they are not concrete versions and the resolver
// returns `ResolutionVersionNotFound` for them by design.
auto resolve_version(const std::string& name, const std::string& version)
-> util::Result<std::string>;
-> util::Result<ResolvedVersion>;
} // namespace cargoxx::resolver

View File

@@ -14,7 +14,7 @@ auto error(util::ErrorCode code, std::string msg) -> util::Error {
} // namespace
auto resolve_version(const std::string& name, const std::string& version)
-> util::Result<std::string> {
-> util::Result<ResolvedVersion> {
if (name.empty()) {
return std::unexpected(error(util::ErrorCode::ResolutionUnknownPackage,
"resolve_version: name is empty"));
@@ -28,11 +28,24 @@ auto resolve_version(const std::string& name, const std::string& version)
if (auto r = devbox_resolve(name, version); r) {
if (!r->commit_hash.empty()) {
return r->commit_hash;
// attr_paths is authoritative for the rev devbox pointed at.
// The first entry is the canonical path; fall back to the
// package name itself only when devbox didn't surface any.
std::string attr = r->attr_paths.empty() ? name : r->attr_paths.front();
return ResolvedVersion{
.nixpkgs_rev = r->commit_hash,
.nixpkgs_attr = std::move(attr),
};
}
}
if (auto r = nixpkgs_git_resolve(name, version, std::nullopt); r) {
return *r;
// The git fallback can't tell us the attr path. Best-effort:
// assume the package name itself is the attr (true for the
// vast majority of nixpkgs derivations).
return ResolvedVersion{
.nixpkgs_rev = *r,
.nixpkgs_attr = name,
};
}
return std::unexpected(error(
util::ErrorCode::ResolutionVersionNotFound,