155ea1de2442
home packages + flake-aware doctor fixes, bump v3.1.1 — (1) home packages now descends into the '…-home-manager-path' env (findHomeManagerPath finds it among the generation's refs and lists ITS references) instead of listing the generation's own activation deps, so the user's home.packages (hello, jq) show and bash/coreutils no longer leak; dedup by name collapses multi-output dups (jq/jq-lib). (2) doctor's config-syntax check is now flake-aware like apply/upgrade — runs 'nixos-rebuild dry-build --flake <config>' when the config dir has a flake.nix, fixing the spurious 'config syntax fail' on flake systems (plain dry-build needs <nixos-config> and fails). VERSION+zon -> 3.1.1. Verified on the unstable golden: home packages -> hello+jq(once), no bash; flake doctor -> config syntax ok / all good. build+tests green incl findHomeManagerPath test
- time
- 2026-06-10 11:31 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- flake + home manager support
- parent
- a57888937a6e
↓ nina-155ea1de.tar.gzatlas at this save
files changed4 files
VERSION+1 −1build.zig.zon+1 −1src/commands.zig+49 −4src/exec.zig+8 −2build.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.0",12+ .version = "3.1.1",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+49 −4
@@ -1013,10 +1013,29 @@10131013 output.homePackagesHeader(ctx.machine.name);10141014 if (gens.len == 0 or gens[0].path.len == 0) return;10151015 1016- // The current generation's references are its managed packages (plus a few1017- // home-manager scaffolding paths and versionless support outputs we skip).1018- const refs = exec.homeGenerationReferences(ctx.gpa, ctx.io, &ctx.machine, gens[0].path) catch return;1019- defer ctx.gpa.free(refs);1016+ // The generation's OWN references are home-manager's activation deps1017+ // (bash, coreutils, ...), not what the user installed. The user's packages1018+ // live one level down, in the `…-home-manager-path` env the generation1019+ // references — descend into it and list ITS references. Fall back to the1020+ // generation refs if that env isn't present (older home-manager layouts).1021+ const gen_refs = exec.homeGenerationReferences(ctx.gpa, ctx.io, &ctx.machine, gens[0].path) catch return;1022+ defer ctx.gpa.free(gen_refs);1023+1024+ var refs = gen_refs;1025+ var env_refs: ?[]u8 = null;1026+ defer if (env_refs) |e| ctx.gpa.free(e);1027+ if (findHomeManagerPath(gen_refs)) |pkg_path| {1028+ if (exec.homeGenerationReferences(ctx.gpa, ctx.io, &ctx.machine, pkg_path)) |e| {1029+ env_refs = e;1030+ refs = e;1031+ } else |_| {}1032+ }1033+1034+ // Dedup by name: multi-output derivations (jq, jq-lib, jq-doc) collapse to1035+ // the same name after the output suffix is stripped. Keys are slices into1036+ // `refs`, valid for this function's lifetime.1037+ var seen = std.StringHashMap(void).init(ctx.gpa);1038+ defer seen.deinit();10201039 var lines = std.mem.splitScalar(u8, refs, '\n');10211040 while (lines.next()) |line| {10221041 const t = std.mem.trim(u8, line, " \t\r");@@ -1024,8 +1043,34 @@10241043 const row = pkgFromStorePath(t);10251044 if (row.version.len == 0) continue;10261045 if (std.mem.indexOf(u8, row.name, "home-manager") != null) continue;1046+ if (seen.contains(row.name)) continue;1047+ seen.put(row.name, {}) catch {};10271048 output.listPkg(row.name, row.version);10281049 }1050+}1051+1052+// The `…-home-manager-path` env among a generation's references — the buildEnv1053+// of the user's home.packages. Returns the full store-path line, or null.1054+fn findHomeManagerPath(refs: []const u8) ?[]const u8 {1055+ var lines = std.mem.splitScalar(u8, refs, '\n');1056+ while (lines.next()) |line| {1057+ const t = std.mem.trim(u8, line, " \t\r");1058+ if (std.mem.indexOf(u8, t, "home-manager-path") != null) return t;1059+ }1060+ return null;1061+}1062+1063+test "findHomeManagerPath picks the env, not the generation or infra" {1064+ const refs =1065+ \\/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bash-5.31066+ \\/nix/store/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-home-manager-path1067+ \\/nix/store/cccccccccccccccccccccccccccccccc-coreutils-9.111068+ ;1069+ try std.testing.expectEqualStrings(1070+ "/nix/store/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-home-manager-path",1071+ findHomeManagerPath(refs).?,1072+ );1073+ try std.testing.expectEqual(@as(?[]const u8, null), findHomeManagerPath("/nix/store/x-bash-5.3\n"));10291074 }10301075 10311076 fn homeDiff(ctx: Ctx) !void {src/exec.zig+8 −2
@@ -1038,9 +1038,15 @@10381038 try checks.append(gpa, .{ .name = "nix daemon", .status = if (active) .ok else .fail });10391039 }10401040 1041- // Check config syntax1041+ // Check config syntax. Flake-aware like apply/upgrade: on a flake system a1042+ // plain `nixos-rebuild dry-build` evaluates <nixpkgs/nixos> + <nixos-config>1043+ // (channel style) and fails, so pass --flake when the config dir has one.10421044 {1043- const r = try capture(gpa, io, machine, &.{ "sudo", "nixos-rebuild", "dry-build" });1045+ const argv: []const []const u8 = if (useFlakeFlag(gpa, io, machine))1046+ &.{ "sudo", "nixos-rebuild", "dry-build", "--flake", machine.config_path }1047+ else1048+ &.{ "sudo", "nixos-rebuild", "dry-build" };1049+ const r = try capture(gpa, io, machine, argv);10441050 defer gpa.free(r.stdout);10451051 defer gpa.free(r.stderr);10461052 try checks.append(gpa, .{ .name = "config syntax", .status = if (r.exit_code == 0) .ok else .fail });