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
4 files VERSION+1 −1build.zig.zon+1 −1src/commands.zig+49 −4src/exec.zig+8 −2
VERSION+1 −1
@@ -1 +1 @@1-3.1.01+3.1.1

view file

build.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 allows

view file

src/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 {

view file

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 });

view file