Pinning NixOS with npins

Rather than using your direnv approach, I ended up writing this script to execute a command with a set of sources loaded into NIX_PATH. That way I can do

with-nix-sources /etc/nixos/sources.nix -- nixos-rebuild build

to test building with updated pins (sources.nix is the wrapper from my previous post), and then do

with-nix-sources /etc/nixos/sources.nix ~/.config/home-manager/npins -- home-manager build

to test build my Home Manager config with the updated npins before switching.

set -euo pipefail

usage() {
  echo "Usage: $0 FILE [FILE...] -- COMMAND [ARGS...]" >&2
  exit "${1:-1}"
}

files=()
while [[ $# -gt 0 ]]; do
  case $1 in
    -h|--help) usage 0 ;;
    --) shift; break ;;
    *) files+=("$1"); shift ;;
  esac
done

[[ ${#files[@]} -gt 0 && $# -gt 0 ]] || usage

args=()
count=${#files[@]}
width=${#count}
for i in "${!files[@]}"; do
  args+=(--argstr "$(printf "file%0${width}d" "$i")" "$(realpath -- "${files[$i]}")")
done

expr='{ ... }@args:
  let
    pkgs = import <nixpkgs> {};
    sources = builtins.concatLists
      (pkgs.lib.mapAttrsToList
        (_: file: pkgs.lib.mapAttrsToList
          (name: path: "${name}=${path}")
          (import file))
        args);
  in
    pkgs.writeText "NIX_PATH" (pkgs.lib.concatStringsSep ":" sources)'

# Using nix-build to realize a file containing the NIX_PATH value
# ensures that the constituent paths are also realized.
nix_path=$(< "$(nix-build --no-out-link -E "$expr" "${args[@]}")")
if [[ -n $nix_path ]]; then
  export NIX_PATH="$nix_path${NIX_PATH:+:$NIX_PATH}"
fi
exec "$@"