[M5+] use devbox attr_paths for pinned-dep nixpkgs attr
This commit is contained in:
13
CHANGELOG.md
13
CHANGELOG.md
@@ -91,6 +91,19 @@ All notable changes to cargoxx will be documented in this file.
|
|||||||
6 cases against fixtures derived from a real fmt 10.2.1 response;
|
6 cases against fixtures derived from a real fmt 10.2.1 response;
|
||||||
`tests/devbox_resolve_live.cpp` (gated by `CARGOXX_NETWORK_TESTS=1`)
|
`tests/devbox_resolve_live.cpp` (gated by `CARGOXX_NETWORK_TESTS=1`)
|
||||||
hits the live API.
|
hits the live API.
|
||||||
|
- Fix: pinned-dep `buildInputs` line used the curated linkdb's
|
||||||
|
`nixpkgs_attr` (e.g. `fmt_10`) regardless of the pinned rev, so
|
||||||
|
`cargoxx add fmt@12.1.0` emitted `pkgs_nixpkgs_fmt_12_1_0.fmt_10`
|
||||||
|
even though that rev only exposes `fmt`. `resolve_version` now
|
||||||
|
returns `ResolvedVersion { nixpkgs_rev, nixpkgs_attr }` (the attr
|
||||||
|
comes from devbox's `attr_paths` for the resolved rev, with the
|
||||||
|
package name as a best-effort fallback for the git path).
|
||||||
|
`cmd_add` writes the attr into the lockfile and `cmd_build`'s
|
||||||
|
`merge_lockfile` now also preserves it. Codegen prefers the
|
||||||
|
lockfile's attr over the linkdb recipe's whenever it's set, so
|
||||||
|
pinned deps reach the right attribute on their per-dep nixpkgs.
|
||||||
|
Live verified: `cargoxx add fmt@12.1.0 && cargoxx build` now emits
|
||||||
|
`pkgs_nixpkgs_fmt_12_1_0.fmt` and the binary builds and runs.
|
||||||
- `flake.nix` codegen rewritten for **per-dep nixpkgs revisions**. The
|
- `flake.nix` codegen rewritten for **per-dep nixpkgs revisions**. The
|
||||||
shared `nixpkgs` input always tracks `nixos-unstable` and provides
|
shared `nixpkgs` input always tracks `nixos-unstable` and provides
|
||||||
the toolchain. Each pinned manifest dep
|
the toolchain. Each pinned manifest dep
|
||||||
|
|||||||
@@ -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
|
// Overwrites any existing entry for the same (name, version). Other
|
||||||
// lockfile entries (root package, sibling deps) are preserved verbatim.
|
// lockfile entries (root package, sibling deps) are preserved verbatim.
|
||||||
auto record_lockfile_rev(const fs::path& project_root, const std::string& name,
|
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> {
|
-> util::Result<void> {
|
||||||
const auto lock_path = project_root / "Cargoxx.lock";
|
const auto lock_path = project_root / "Cargoxx.lock";
|
||||||
lockfile::Lockfile lock;
|
lockfile::Lockfile lock;
|
||||||
@@ -56,7 +57,8 @@ auto record_lockfile_rev(const fs::path& project_root, const std::string& name,
|
|||||||
bool replaced = false;
|
bool replaced = false;
|
||||||
for (auto& p : lock.packages) {
|
for (auto& p : lock.packages) {
|
||||||
if (p.name == name && p.version == version) {
|
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;
|
replaced = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -66,8 +68,8 @@ auto record_lockfile_rev(const fs::path& project_root, const std::string& name,
|
|||||||
.name = name,
|
.name = name,
|
||||||
.version = version,
|
.version = version,
|
||||||
.dependencies = {},
|
.dependencies = {},
|
||||||
.nixpkgs_attr = std::nullopt,
|
.nixpkgs_attr = resolved.nixpkgs_attr,
|
||||||
.nixpkgs_rev = rev,
|
.nixpkgs_rev = resolved.nixpkgs_rev,
|
||||||
.linkdb_source = std::nullopt,
|
.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 == "*";
|
const bool wildcard = effective_version == "*";
|
||||||
auto* env = std::getenv("CARGOXX_NO_AUTORESOLVE");
|
auto* env = std::getenv("CARGOXX_NO_AUTORESOLVE");
|
||||||
const bool autoresolve_disabled = env != nullptr && *env != 0;
|
const bool autoresolve_disabled = env != nullptr && *env != 0;
|
||||||
std::optional<std::string> resolved_rev;
|
std::optional<resolver::ResolvedVersion> resolved;
|
||||||
if (!wildcard && !autoresolve_disabled) {
|
if (!wildcard && !autoresolve_disabled) {
|
||||||
auto rev = resolver::resolve_version(name, effective_version);
|
auto r = resolver::resolve_version(name, effective_version);
|
||||||
if (!rev) {
|
if (!r) {
|
||||||
return std::unexpected(rev.error());
|
return std::unexpected(r.error());
|
||||||
}
|
}
|
||||||
resolved_rev = std::move(*rev);
|
resolved = std::move(*r);
|
||||||
}
|
}
|
||||||
|
|
||||||
m->dependencies.push_back(manifest::Dependency{
|
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());
|
return std::unexpected(r.error());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolved_rev) {
|
if (resolved) {
|
||||||
if (auto r = record_lockfile_rev(project_root, name, effective_version,
|
if (auto r = record_lockfile_rev(project_root, name, effective_version,
|
||||||
*resolved_rev);
|
*resolved);
|
||||||
!r) {
|
!r) {
|
||||||
return std::unexpected(r.error());
|
return std::unexpected(r.error());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,14 +75,23 @@ auto merge_lockfile(const manifest::Manifest& m,
|
|||||||
const auto& dep = m.dependencies[i];
|
const auto& dep = m.dependencies[i];
|
||||||
const auto& rec = recipes[i];
|
const auto& rec = recipes[i];
|
||||||
std::optional<std::string> rev;
|
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) {
|
if (auto p = find_prior(dep.name, dep.version_spec); p) {
|
||||||
rev = p->nixpkgs_rev;
|
rev = p->nixpkgs_rev;
|
||||||
|
if (p->nixpkgs_attr && !p->nixpkgs_attr->empty()) {
|
||||||
|
attr = *p->nixpkgs_attr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
lock.packages.push_back(lockfile::LockfilePackage{
|
lock.packages.push_back(lockfile::LockfilePackage{
|
||||||
.name = dep.name,
|
.name = dep.name,
|
||||||
.version = dep.version_spec,
|
.version = dep.version_spec,
|
||||||
.dependencies = {},
|
.dependencies = {},
|
||||||
.nixpkgs_attr = rec.nixpkgs_attr,
|
.nixpkgs_attr = std::move(attr),
|
||||||
.nixpkgs_rev = std::move(rev),
|
.nixpkgs_rev = std::move(rev),
|
||||||
.linkdb_source = rec.source,
|
.linkdb_source = rec.source,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,14 +42,19 @@ auto sanitize_input_attr(std::string_view name, std::string_view version)
|
|||||||
return std::format("nixpkgs_{}_{}", sanitize(name), sanitize(version));
|
return std::format("nixpkgs_{}_{}", sanitize(name), sanitize(version));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto find_lockfile_rev(const lockfile::Lockfile& lock, const std::string& name,
|
struct LockfileRef {
|
||||||
const std::string& version) -> std::optional<std::string> {
|
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) {
|
for (const auto& p : lock.packages) {
|
||||||
if (p.name == name && p.version == version) {
|
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> {
|
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) {
|
for (std::size_t i = 0; i < in.manifest.dependencies.size(); ++i) {
|
||||||
const auto& dep = in.manifest.dependencies[i];
|
const auto& dep = in.manifest.dependencies[i];
|
||||||
const auto& rec = in.recipes[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{
|
DepBinding b{
|
||||||
.name = dep.name,
|
.name = dep.name,
|
||||||
.version = dep.version_spec,
|
.version = dep.version_spec,
|
||||||
.nixpkgs_attr = rec.nixpkgs_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 = find_lockfile_rev(in.lock, dep.name, dep.version_spec),
|
.rev = std::move(ref.rev),
|
||||||
};
|
};
|
||||||
out.push_back(std::move(b));
|
out.push_back(std::move(b));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
std::optional<std::filesystem::path> repo_path = std::nullopt)
|
||||||
-> util::Result<std::string>;
|
-> 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
|
// 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.
|
// `ResolutionVersionNotFound` when both probes come back empty.
|
||||||
//
|
//
|
||||||
// Use this from `cargoxx add <pkg>@<concrete-ver>` to capture the rev
|
// Use this from `cargoxx add <pkg>@<concrete-ver>` to capture the pin
|
||||||
// that lockfile/codegen will pin. Wildcards (`*`, empty) should NOT be
|
// that lockfile/codegen will use. Wildcards (`*`, empty) should NOT
|
||||||
// passed — they are not concrete versions and the resolver returns
|
// be passed — they are not concrete versions and the resolver
|
||||||
// `ResolutionVersionNotFound` for them by design.
|
// returns `ResolutionVersionNotFound` for them by design.
|
||||||
auto resolve_version(const std::string& name, const std::string& version)
|
auto resolve_version(const std::string& name, const std::string& version)
|
||||||
-> util::Result<std::string>;
|
-> util::Result<ResolvedVersion>;
|
||||||
|
|
||||||
} // namespace cargoxx::resolver
|
} // namespace cargoxx::resolver
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ auto error(util::ErrorCode code, std::string msg) -> util::Error {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
auto resolve_version(const std::string& name, const std::string& version)
|
auto resolve_version(const std::string& name, const std::string& version)
|
||||||
-> util::Result<std::string> {
|
-> util::Result<ResolvedVersion> {
|
||||||
if (name.empty()) {
|
if (name.empty()) {
|
||||||
return std::unexpected(error(util::ErrorCode::ResolutionUnknownPackage,
|
return std::unexpected(error(util::ErrorCode::ResolutionUnknownPackage,
|
||||||
"resolve_version: name is empty"));
|
"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 (auto r = devbox_resolve(name, version); r) {
|
||||||
if (!r->commit_hash.empty()) {
|
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) {
|
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(
|
return std::unexpected(error(
|
||||||
util::ErrorCode::ResolutionVersionNotFound,
|
util::ErrorCode::ResolutionVersionNotFound,
|
||||||
|
|||||||
Reference in New Issue
Block a user