From b631ef3e0e0a9ff3879e4d31d617996f03566be6 Mon Sep 17 00:00:00 2001 From: ic4y Date: Mon, 21 Sep 2026 13:36:46 +0200 Subject: [PATCH 1/5] npins: init at 0.5.1 --- pkgs/npins/default.nix | 84 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 pkgs/npins/default.nix diff --git a/pkgs/npins/default.nix b/pkgs/npins/default.nix new file mode 100644 index 00000000..4a89e885 --- /dev/null +++ b/pkgs/npins/default.nix @@ -0,0 +1,84 @@ +# npins — simple and convenient dependency pinning for Nix +{ + lib, + stdenv, + rustPlatform, + fetchFromGitHub, + makeWrapper, + installShellFiles, + testers, + nix-update-script, + + # runtime dependencies — npins shells out to all of these + nix, + nix-prefetch-git, + nix-prefetch-docker, + skopeo, + git, # for `git ls-remote` +}: + +let + runtimePath = lib.makeBinPath [ + nix + nix-prefetch-git + nix-prefetch-docker + skopeo + git + ]; +in +rustPlatform.buildRustPackage (finalAttrs: { + pname = "npins"; + version = "0.5.1"; + + src = fetchFromGitHub { + owner = "andir"; + repo = "npins"; + tag = finalAttrs.version; + hash = "sha256-PRdGQlxpv8qXdQ6KwlP2Ky2HBHDY83lGTSiD6yljUxE="; + }; + + cargoHash = "sha256-N0Hurb/cmXCDS7EZlYCct9WPbUMXvU+0TK1cBY6+mYc="; + + cargoBuildFlags = [ + "-p" + "npins" + "-p" + "npins-completions" + ]; + + nativeBuildInputs = [ + makeWrapper + installShellFiles + ]; + + # NOTE: openssh is deliberately absent from runtimePath. npins sets + # GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=yes", which git resolves + # through the shell and therefore through PATH, so ssh:// pins work anywhere + # openssh is provided by the caller's environment (any standard system) and + # fail loudly with "ssh: command not found" where it is not. Add openssh to + # runtimePath only if hermetic ssh support becomes a requirement. + postFixup = + lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) '' + installShellCompletion --cmd npins \ + --bash <($out/bin/npins-completions bash) \ + --fish <(cat <($out/bin/npins-completions fish) $src/completions/pin-completions.fish) \ + --zsh <($out/bin/npins-completions zsh) + '' + + '' + # Generated above, but a build-time tool rather than a shipped binary — + # removed unconditionally so it does not survive into cross builds. + rm -f $out/bin/npins-completions + + wrapProgram $out/bin/npins --prefix PATH : "${runtimePath}" + ''; + + passthru.tests.version = testers.testVersion { package = finalAttrs.finalPackage; }; + passthru.updateScript = nix-update-script { }; + + meta = { + description = "Simple and convenient dependency pinning for Nix"; + mainProgram = "npins"; + homepage = "https://github.com/andir/npins"; + license = lib.licenses.eupl12; + }; +}) From d0f03aa0f353f215635bdd7c5ebe93f54bc4254d Mon Sep 17 00:00:00 2001 From: ic4y Date: Mon, 21 Sep 2026 14:14:33 +0200 Subject: [PATCH 2/5] npins: record why nix and skopeo are runtime dependencies Both argument lines were unexplained in the local delta against nixpkgs. Note each one's direct call site, and that only half of nix's justification disappears once corepkgs#211 is fixed. --- pkgs/npins/default.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkgs/npins/default.nix b/pkgs/npins/default.nix index 4a89e885..b4442cce 100644 --- a/pkgs/npins/default.nix +++ b/pkgs/npins/default.nix @@ -10,9 +10,16 @@ nix-update-script, # runtime dependencies — npins shells out to all of these + # Spawned directly by npins itself, so required on every invocation + # (nix-prefetch-url libnpins/src/nix.rs:20, nix-instantiate :264). Also + # supplies the nix-hash/nix-store that nix-prefetch-git's own wrapper omits + # when --hash is passed without --builder (corepkgs#211). Only that second + # half goes away if #211 is fixed; the direct calls do not. nix, nix-prefetch-git, nix-prefetch-docker, + # Spawned directly for container pins (libnpins/src/nix.rs:204). nixpkgs' + # expression omits it and is latently broken for `npins add container`. skopeo, git, # for `git ls-remote` }: From a700b291f8416f91b81656d31007a74d86025005 Mon Sep 17 00:00:00 2001 From: ic4y Date: Mon, 21 Sep 2026 14:14:40 +0200 Subject: [PATCH 3/5] npins: build the completions helper only when it can be used npins-completions was built unconditionally while only its use was guarded, so on a cross build it still landed in $out/bin and forced an unguarded rm to remove it. Gate the build itself and collapse generation and removal into one guarded postInstall fragment, restoring the phase ordering the upstream expression relied on. nixpkgs keeps the rm inside the guard, which ships the helper on cross builds. --- pkgs/npins/default.nix | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/pkgs/npins/default.nix b/pkgs/npins/default.nix index b4442cce..01dd21e1 100644 --- a/pkgs/npins/default.nix +++ b/pkgs/npins/default.nix @@ -16,11 +16,11 @@ # when --hash is passed without --builder (corepkgs#211). Only that second # half goes away if #211 is fixed; the direct calls do not. nix, - nix-prefetch-git, - nix-prefetch-docker, # Spawned directly for container pins (libnpins/src/nix.rs:204). nixpkgs' # expression omits it and is latently broken for `npins add container`. skopeo, + nix-prefetch-git, + nix-prefetch-docker, git, # for `git ls-remote` }: @@ -32,6 +32,11 @@ let skopeo git ]; + + # npins-completions is a build-time helper, not a shipped binary: it is only + # useful on a host that can execute the npins it just built, so it is neither + # built nor invoked when cross-compiling. + canRunCompletions = stdenv.buildPlatform.canExecute stdenv.hostPlatform; in rustPlatform.buildRustPackage (finalAttrs: { pname = "npins"; @@ -49,6 +54,8 @@ rustPlatform.buildRustPackage (finalAttrs: { cargoBuildFlags = [ "-p" "npins" + ] + ++ lib.optionals canRunCompletions [ "-p" "npins-completions" ]; @@ -58,26 +65,26 @@ rustPlatform.buildRustPackage (finalAttrs: { installShellFiles ]; + # Generating completions and discarding the helper that generates them are + # one guarded unit, so the removal cannot drift ahead of the use. + postInstall = lib.optionalString canRunCompletions '' + installShellCompletion --cmd npins \ + --bash <($out/bin/npins-completions bash) \ + --fish <(cat <($out/bin/npins-completions fish) $src/completions/pin-completions.fish) \ + --zsh <($out/bin/npins-completions zsh) + + rm -f $out/bin/npins-completions + ''; + # NOTE: openssh is deliberately absent from runtimePath. npins sets # GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=yes", which git resolves # through the shell and therefore through PATH, so ssh:// pins work anywhere # openssh is provided by the caller's environment (any standard system) and # fail loudly with "ssh: command not found" where it is not. Add openssh to # runtimePath only if hermetic ssh support becomes a requirement. - postFixup = - lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) '' - installShellCompletion --cmd npins \ - --bash <($out/bin/npins-completions bash) \ - --fish <(cat <($out/bin/npins-completions fish) $src/completions/pin-completions.fish) \ - --zsh <($out/bin/npins-completions zsh) - '' - + '' - # Generated above, but a build-time tool rather than a shipped binary — - # removed unconditionally so it does not survive into cross builds. - rm -f $out/bin/npins-completions - - wrapProgram $out/bin/npins --prefix PATH : "${runtimePath}" - ''; + postFixup = '' + wrapProgram $out/bin/npins --prefix PATH : "${runtimePath}" + ''; passthru.tests.version = testers.testVersion { package = finalAttrs.finalPackage; }; passthru.updateScript = nix-update-script { }; From d57793a347215d6e8a7a26b79fe08fe522ea21d8 Mon Sep 17 00:00:00 2001 From: ic4y Date: Mon, 21 Sep 2026 14:36:52 +0200 Subject: [PATCH 4/5] =?UTF-8?q?refactor(police):=20elegance=20=E2=80=94=20?= =?UTF-8?q?place=20the=20openssh=20note=20at=20runtimePath?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The openssh rationale explains why runtimePath omits a dependency, but sat above postFixup where that binding is merely consumed. Move it to the binding it describes and align the formal argument order with runtimePath so the two lists read identically. --- pkgs/npins/default.nix | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkgs/npins/default.nix b/pkgs/npins/default.nix index 01dd21e1..abba04b1 100644 --- a/pkgs/npins/default.nix +++ b/pkgs/npins/default.nix @@ -16,15 +16,21 @@ # when --hash is passed without --builder (corepkgs#211). Only that second # half goes away if #211 is fixed; the direct calls do not. nix, + nix-prefetch-git, + nix-prefetch-docker, # Spawned directly for container pins (libnpins/src/nix.rs:204). nixpkgs' # expression omits it and is latently broken for `npins add container`. skopeo, - nix-prefetch-git, - nix-prefetch-docker, git, # for `git ls-remote` }: let + # NOTE: openssh is deliberately absent from runtimePath. npins sets + # GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=yes", which git resolves + # through the shell and therefore through PATH, so ssh:// pins work anywhere + # openssh is provided by the caller's environment (any standard system) and + # fail loudly with "ssh: command not found" where it is not. Add openssh here + # only if hermetic ssh support becomes a requirement. runtimePath = lib.makeBinPath [ nix nix-prefetch-git @@ -76,12 +82,6 @@ rustPlatform.buildRustPackage (finalAttrs: { rm -f $out/bin/npins-completions ''; - # NOTE: openssh is deliberately absent from runtimePath. npins sets - # GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=yes", which git resolves - # through the shell and therefore through PATH, so ssh:// pins work anywhere - # openssh is provided by the caller's environment (any standard system) and - # fail loudly with "ssh: command not found" where it is not. Add openssh to - # runtimePath only if hermetic ssh support becomes a requirement. postFixup = '' wrapProgram $out/bin/npins --prefix PATH : "${runtimePath}" ''; From 82ae08fdcd00ca60e736e3a4ccf34670ef25a683 Mon Sep 17 00:00:00 2001 From: ic4y Date: Tue, 22 Sep 2026 17:06:25 +0200 Subject: [PATCH 5/5] npins: shrink comments to the load-bearing detail Review feedback on #212: most of the prose can be inferred from the code. Collapse five multi-line comment blocks to one line each: - the runtime-dep preamble and per-dep notes move onto the formals themselves, keeping both reasons `nix` is required (npins' own direct calls, and nix-prefetch-git's nix-hash path from #211) so the surviving half stays identifiable if #211 closes - the openssh note keeps the mechanism (GIT_SSH_COMMAND is shell-resolved, so ssh comes from the caller's PATH) and drops the closure arithmetic - the completions guard and its build/removal pairing each fit one line No change to the derivation: same formals, runtimePath, build flags, postInstall and postFixup. Rebuilt and re-checked: `npins --version` returns 0.5.1, `$out/bin` holds only `npins`, all three completions install, and the wrapper PATH still carries git. --- pkgs/npins/default.nix | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/pkgs/npins/default.nix b/pkgs/npins/default.nix index abba04b1..61765594 100644 --- a/pkgs/npins/default.nix +++ b/pkgs/npins/default.nix @@ -10,27 +10,16 @@ nix-update-script, # runtime dependencies — npins shells out to all of these - # Spawned directly by npins itself, so required on every invocation - # (nix-prefetch-url libnpins/src/nix.rs:20, nix-instantiate :264). Also - # supplies the nix-hash/nix-store that nix-prefetch-git's own wrapper omits - # when --hash is passed without --builder (corepkgs#211). Only that second - # half goes away if #211 is fixed; the direct calls do not. - nix, + nix, # direct calls (nix.rs:20,:264) and nix-prefetch-git's nix-hash (corepkgs#211) nix-prefetch-git, nix-prefetch-docker, - # Spawned directly for container pins (libnpins/src/nix.rs:204). nixpkgs' - # expression omits it and is latently broken for `npins add container`. - skopeo, + skopeo, # container pins; absent upstream, leaving `npins add container` broken git, # for `git ls-remote` }: let - # NOTE: openssh is deliberately absent from runtimePath. npins sets - # GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=yes", which git resolves - # through the shell and therefore through PATH, so ssh:// pins work anywhere - # openssh is provided by the caller's environment (any standard system) and - # fail loudly with "ssh: command not found" where it is not. Add openssh here - # only if hermetic ssh support becomes a requirement. + # openssh deliberately absent: npins sets GIT_SSH_COMMAND, so git resolves + # `ssh` via PATH and callers supply it. runtimePath = lib.makeBinPath [ nix nix-prefetch-git @@ -39,9 +28,7 @@ let git ]; - # npins-completions is a build-time helper, not a shipped binary: it is only - # useful on a host that can execute the npins it just built, so it is neither - # built nor invoked when cross-compiling. + # npins-completions only runs on a host that can execute what it just built. canRunCompletions = stdenv.buildPlatform.canExecute stdenv.hostPlatform; in rustPlatform.buildRustPackage (finalAttrs: { @@ -71,8 +58,7 @@ rustPlatform.buildRustPackage (finalAttrs: { installShellFiles ]; - # Generating completions and discarding the helper that generates them are - # one guarded unit, so the removal cannot drift ahead of the use. + # built and removed together so the removal cannot drift ahead of the use postInstall = lib.optionalString canRunCompletions '' installShellCompletion --cmd npins \ --bash <($out/bin/npins-completions bash) \