a178ca844c84
pkg commands: resolve as nixpkgs#name consistently — fixes why/deps/size/closure on flake systems (v3.1.5)
- time
- 2026-06-10 21:27 UTC · 4 months ago
- machine
- koh2
- parent
- a512584d3c19
↓ nina-a178ca84.tar.gzatlas at this save
files changed4 files
VERSION+1 −1build.zig.zon+1 −1src/commands.zig+7 −9src/exec.zig+83 −15build.zig.zon+1 −1
@@ -9,7 +9,7 @@99 .name = .nina,1010 // This is a [Semantic Version](https://semver.org/).1111 // In a future version of Zig it will be used for package deduplication.12- .version = "3.1.4",12+ .version = "3.1.5",1313 // Together with name, this represents a globally unique package1414 // identifier. This field is generated by the Zig toolchain when the1515 // package is first created, and then *never changes*. This allowssrc/commands.zig+7 −9
@@ -1435,19 +1435,17 @@14351435 if (ctx.sub.len == 0 or std.mem.eql(u8, ctx.sub, "help")) return output.subcommandHelp("pkg", &pkg_help, null);14361436 const name = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;14371437 if (std.mem.eql(u8, ctx.sub, "why")) {1438- _ = try exec.pkgWhy(ctx.io, &ctx.machine, ctx.gpa, name);1438+ if (try exec.pkgWhy(ctx.io, &ctx.machine, ctx.gpa, name) != 0) return error.PackageNotFound;14391439 return;14401440 }14411441 if (std.mem.eql(u8, ctx.sub, "deps")) {1442- _ = try exec.pkgDeps(ctx.io, &ctx.machine, ctx.gpa, name);1442+ if (try exec.pkgDeps(ctx.io, &ctx.machine, ctx.gpa, name) != 0) return error.PackageNotFound;14431443 return;14441444 }14451445 if (std.mem.eql(u8, ctx.sub, "size")) {1446- const s = exec.pkgSize(ctx.gpa, ctx.io, &ctx.machine, name) catch null;1447- if (s) |sz| {1448- defer ctx.gpa.free(sz);1449- output.pkgResult("size", sz);1450- }1446+ const s = try exec.pkgSize(ctx.gpa, ctx.io, &ctx.machine, name);1447+ defer ctx.gpa.free(s);1448+ output.pkgResult("size", s);14511449 return;14521450 }14531451 if (std.mem.eql(u8, ctx.sub, "path")) {@@ -1457,7 +1455,7 @@14571455 return;14581456 }14591457 if (std.mem.eql(u8, ctx.sub, "closure")) {1460- _ = try exec.pkgClosure(ctx.io, &ctx.machine, ctx.gpa, name);1458+ if (try exec.pkgClosure(ctx.io, &ctx.machine, ctx.gpa, name) != 0) return error.PackageNotFound;14611459 return;14621460 }14631461 }@@ -1466,7 +1464,7 @@14661464 // without the user needing to know about the `pkg` subgroup.14671465 pub fn why(ctx: Ctx) !void {14681466 const name = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;1469- _ = try exec.pkgWhy(ctx.io, &ctx.machine, ctx.gpa, name);1467+ if (try exec.pkgWhy(ctx.io, &ctx.machine, ctx.gpa, name) != 0) return error.PackageNotFound;14701468 }14711469 14721470 // --- Pin / Unpin ---src/exec.zig+83 −15
@@ -960,35 +960,103 @@960960 961961 // --- Pkg info ---962962 963+fn pkgInstallable(gpa: std.mem.Allocator, pkg: []const u8) ![]u8 {964+ return std.fmt.allocPrint(gpa, "nixpkgs#{s}", .{pkg});965+}966+967+fn resolvePkgStorePath(gpa: std.mem.Allocator, io: std.Io, machine: *const types.Machine, installable: []const u8) ![]u8 {968+ const r = try capture(gpa, io, machine, &.{ "nix", "build", "--no-link", "--print-out-paths", installable });969+ defer gpa.free(r.stderr);970+ defer gpa.free(r.stdout);971+ if (r.exit_code != 0) return error.PackageNotFound;972+973+ var lines = std.mem.tokenizeAny(u8, r.stdout, "\r\n");974+ const first_path = lines.next() orelse return error.PackageNotFound;975+ const store_path = std.mem.trim(u8, first_path, " \t");976+ if (store_path.len == 0) return error.PackageNotFound;977+ return gpa.dupe(u8, store_path);978+}979+963980 pub fn pkgWhy(io: std.Io, machine: *const types.Machine, gpa: std.mem.Allocator, pkg: []const u8) !u8 {964- return stream(io, machine, gpa, &.{ "nix", "why-depends", ".#", pkg });981+ const installable = try pkgInstallable(gpa, pkg);982+ defer gpa.free(installable);983+ const store_path = try resolvePkgStorePath(gpa, io, machine, installable);984+ defer gpa.free(store_path);985+ return stream(io, machine, gpa, &.{ "nix", "why-depends", "/run/current-system", store_path });965986 }966987 967988 pub fn pkgDeps(io: std.Io, machine: *const types.Machine, gpa: std.mem.Allocator, pkg: []const u8) !u8 {968- return stream(io, machine, gpa, &.{ "nix-store", "--query", "--requisites", pkg });989+ const installable = try pkgInstallable(gpa, pkg);990+ defer gpa.free(installable);991+ const store_path = try resolvePkgStorePath(gpa, io, machine, installable);992+ defer gpa.free(store_path);993+ return stream(io, machine, gpa, &.{ "nix-store", "--query", "--requisites", store_path });969994 }970995 971996 pub fn pkgSize(gpa: std.mem.Allocator, io: std.Io, machine: *const types.Machine, pkg: []const u8) ![]u8 {972- // A bare name ("ripgrep") isn't a valid installable; resolve via nixpkgs# like973- // `pkg path` does. -S -h gives closure size, human-readable. Output is974- // "<store-path> <size>"; return just the size.975- const ref = try std.fmt.allocPrint(gpa, "nixpkgs#{s}", .{pkg});976- defer gpa.free(ref);977- const r = try capture(gpa, io, machine, &.{ "nix", "path-info", "-S", "-h", ref });997+ const installable = try pkgInstallable(gpa, pkg);998+ defer gpa.free(installable);999+ const r = try capture(gpa, io, machine, &.{ "nix", "path-info", "-s", "--json", installable });9781000 defer gpa.free(r.stderr);9791001 defer gpa.free(r.stdout);9801002 if (r.exit_code != 0) return error.ProcessFailed;9811003 982- const line = std.mem.trim(u8, r.stdout, " \t\n\r");983- var it = std.mem.tokenizeAny(u8, line, " \t");984- _ = it.next(); // store path985- const size = std.mem.trim(u8, it.rest(), " \t");986- if (size.len == 0) return error.ProcessFailed;987- return gpa.dupe(u8, size);1004+ const nar_size = try pathInfoNarSize(gpa, r.stdout);1005+ return humanPkgSize(gpa, nar_size);9881006 }9891007 9901008 pub fn pkgClosure(io: std.Io, machine: *const types.Machine, gpa: std.mem.Allocator, pkg: []const u8) !u8 {991- return stream(io, machine, gpa, &.{ "nix", "path-info", "-rS", pkg });1009+ const installable = try pkgInstallable(gpa, pkg);1010+ defer gpa.free(installable);1011+ const store_path = try resolvePkgStorePath(gpa, io, machine, installable);1012+ defer gpa.free(store_path);1013+ return stream(io, machine, gpa, &.{ "nix", "path-info", "-rS", store_path });1014+}1015+1016+fn pathInfoNarSize(gpa: std.mem.Allocator, json_str: []const u8) !u64 {1017+ var parsed = std.json.parseFromSlice(std.json.Value, gpa, json_str, .{}) catch return error.ProcessFailed;1018+ defer parsed.deinit();1019+ switch (parsed.value) {1020+ .object => |obj| {1021+ var it = obj.iterator();1022+ while (it.next()) |entry| {1023+ if (entry.value_ptr.* == .object) {1024+ if (entry.value_ptr.object.get("narSize")) |v| {1025+ if (v == .integer and v.integer >= 0) return @intCast(v.integer);1026+ }1027+ }1028+ }1029+ },1030+ .array => |arr| {1031+ for (arr.items) |item| {1032+ if (item == .object) {1033+ if (item.object.get("narSize")) |v| {1034+ if (v == .integer and v.integer >= 0) return @intCast(v.integer);1035+ }1036+ }1037+ }1038+ },1039+ else => {},1040+ }1041+ return error.ProcessFailed;1042+}1043+1044+fn humanPkgSize(gpa: std.mem.Allocator, bytes: u64) ![]u8 {1045+ const units = [_][]const u8{ "B", "KiB", "MiB", "GiB", "TiB" };1046+ var val: f64 = @floatFromInt(bytes);1047+ var unit: usize = 0;1048+ while (val >= 1024.0 and unit + 1 < units.len) : (unit += 1) val /= 1024.0;1049+ if (unit == 0) return std.fmt.allocPrint(gpa, "{d} {s}", .{ bytes, units[0] });1050+ return std.fmt.allocPrint(gpa, "{d:.1} {s}", .{ val, units[unit] });1051+}1052+1053+test "pathInfoNarSize parses object and array shapes" {1054+ try std.testing.expectEqual(@as(u64, 1064629862), try pathInfoNarSize(std.testing.allocator,1055+ \\{"/nix/store/abc-ffmpeg":{"narSize":1064629862}}1056+ ));1057+ try std.testing.expectEqual(@as(u64, 2048), try pathInfoNarSize(std.testing.allocator,1058+ \\[{"path":"/nix/store/abc-pkg","narSize":2048}]1059+ ));9921060 }9931061 9941062 // --- Hash / Fetch ---