main
  1/*
  2Runner for packages declaring `passthru.updateScript`, modeled after
  3nixpkgs' maintainers/scripts/update.nix.
  4
  5Usage (also see scripts/update-package):
  6
  7    nix-shell scripts/update.nix --argstr packages "kelivo stinkpot"
  8    nix-shell scripts/update.nix --argstr packages ""
  9*/
 10{
 11  pkgs ? import <nixpkgs> {},
 12  # Space-separated list of package attribute paths to update.
 13  # Empty string means "update everything".
 14  packages ? "",
 15}: let
 16  inherit (pkgs) lib;
 17  selected = import ./update-packages.nix {inherit pkgs packages;};
 18
 19  invalidSelections = builtins.filter (sel: !lib.any (p: p.attrPath == sel || lib.hasSuffix ".${sel}" p.attrPath) selected) (
 20    lib.filter (s: s != "") (lib.splitString " " packages)
 21  );
 22
 23  packagesJson = pkgs.writeText "nur-update-packages.json" (
 24    builtins.toJSON (
 25      map (p: {
 26        inherit (p) attrPath oldVersion;
 27        inherit (p) pname;
 28        name = p.pkgName;
 29        updateScript = p.script;
 30      })
 31      selected
 32    )
 33  );
 34
 35  helpText = ''
 36    No packages with passthru.updateScript were selected.
 37  '';
 38in
 39  pkgs.stdenvNoCC.mkDerivation {
 40    name = "nur-update-script";
 41    nativeBuildInputs = [
 42      pkgs.git
 43      pkgs.cacert
 44      pkgs.jq
 45    ];
 46    buildCommand = ''
 47      echo ""
 48      echo "Not possible to run update scripts using nix-build."
 49      echo ""
 50      echo "${lib.replaceStrings ["\n"] [" "] helpText}"
 51      exit 1
 52    '';
 53    shellHook = ''
 54      unset shellHook
 55
 56      ${lib.optionalString (invalidSelections != []) ''
 57        echo "WARNING: no updateScript found for: ${lib.concatStringsSep " " invalidSelections}" >&2
 58      ''}
 59
 60      ${lib.optionalString (selected == []) ''
 61        echo "${lib.replaceStrings ["\n"] [" "] helpText}" >&2
 62        exit 1
 63      ''}
 64
 65      FAILED=""
 66      LOGDIR=$(mktemp -d)
 67      trap 'rm -rf "$LOGDIR"' EXIT
 68      cd "${toString ./.}/.."
 69
 70      MAX_JOBS="''${UPDATE_PACKAGE_JOBS:-$(nproc)}"
 71      declare -A JOB_ATTR=() JOB_LOG=()
 72      RUNNING=0
 73
 74      launch() {
 75        local PKG="$1" ATTR_PATH OLD_VERSION LOG
 76        ATTR_PATH=$(jq -r .attrPath <<<"$PKG")
 77        OLD_VERSION=$(jq -r .oldVersion <<<"$PKG")
 78        LOG=$(mktemp "$LOGDIR/log.XXXXXX")
 79        echo ">>> $ATTR_PATH: updating ($OLD_VERSION)"
 80        (
 81          if env \
 82              UPDATE_NIX_ATTR_PATH="$ATTR_PATH" \
 83              UPDATE_NIX_PNAME="$(jq -r .pname <<<"$PKG")" \
 84              UPDATE_NIX_NAME="$(jq -r .name <<<"$PKG")" \
 85              UPDATE_NIX_OLD_VERSION="$OLD_VERSION" \
 86              bash -c "$(jq -r '.updateScript | map(@sh) | join(" ")' <<<"$PKG")"; then
 87            echo ">>> $ATTR_PATH: done"
 88            exit 0
 89          else
 90            echo ">>> $ATTR_PATH: FAILED" >&2
 91            exit 1
 92          fi
 93        ) >"$LOG" 2>&1 &
 94        JOB_ATTR[$!]="$ATTR_PATH"
 95        JOB_LOG[$!]="$LOG"
 96        RUNNING=$((RUNNING + 1))
 97      }
 98
 99      reap_one() {
100        local PID STATUS ATTR_PATH LOG
101        PID=""
102        STATUS=0
103        # Requires bash >= 5.1 for `wait -n -p`
104        wait -n -p PID || STATUS=$?
105        ATTR_PATH="''${JOB_ATTR[$PID]}"
106        LOG="''${JOB_LOG[$PID]}"
107        echo ""
108        cat "$LOG"
109        rm -f "$LOG"
110        if [ "$STATUS" -ne 0 ]; then
111          FAILED="$FAILED $ATTR_PATH"
112        fi
113        unset "JOB_ATTR[$PID]" "JOB_LOG[$PID]"
114        RUNNING=$((RUNNING - 1))
115      }
116
117      while IFS= read -r PKG; do
118        while [ "$RUNNING" -ge "$MAX_JOBS" ]; do
119          reap_one
120        done
121        launch "$PKG"
122      done < <(jq -c '.[]' ${packagesJson})
123
124      while [ "$RUNNING" -gt 0 ]; do
125        reap_one
126      done
127
128      echo ""
129      if [ -n "$FAILED" ]; then
130        echo "Finished with failures:$FAILED" >&2
131        exit 1
132      fi
133      echo "All update scripts finished successfully."
134      exit 0
135    '';
136  }