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 }