1{ package ? null
2, maintainer ? null
3, predicate ? null
4, path ? null
5, max-workers ? null
6, include-overlays ? false
7, keep-going ? null
8, commit ? null
9}:
10
11# TODO: add assert statements
12
13let
14 pkgs = import ./../../default.nix (
15 if include-overlays == false then
16 { overlays = []; }
17 else if include-overlays == true then
18 { } # Let Nixpkgs include overlays impurely.
19 else { overlays = include-overlays; }
20 );
21
22 inherit (pkgs) lib;
23
24 /* Remove duplicate elements from the list based on some extracted value. O(n^2) complexity.
25 */
26 nubOn = f: list:
27 if list == [] then
28 []
29 else
30 let
31 x = lib.head list;
32 xs = lib.filter (p: f x != f p) (lib.drop 1 list);
33 in
34 [x] ++ nubOn f xs;
35
36 /* Recursively find all packages (derivations) in `pkgs` matching `cond` predicate.
37
38 Type: packagesWithPath :: AttrPath → (AttrPath → derivation → bool) → AttrSet → List<AttrSet{attrPath :: str; package :: derivation; }>
39 AttrPath :: [str]
40
41 The packages will be returned as a list of named pairs comprising of:
42 - attrPath: stringified attribute path (based on `rootPath`)
43 - package: corresponding derivation
44 */
45 packagesWithPath = rootPath: cond: pkgs:
46 let
47 packagesWithPathInner = path: pathContent:
48 let
49 result = builtins.tryEval pathContent;
50
51 dedupResults = lst: nubOn ({ package, attrPath }: package.updateScript) (lib.concatLists lst);
52 in
53 if result.success then
54 let
55 evaluatedPathContent = result.value;
56 in
57 if lib.isDerivation evaluatedPathContent then
58 lib.optional (cond path evaluatedPathContent) { attrPath = lib.concatStringsSep "." path; package = evaluatedPathContent; }
59 else if lib.isAttrs evaluatedPathContent then
60 # If user explicitly points to an attrSet or it is marked for recursion, we recur.
61 if path == rootPath || evaluatedPathContent.recurseForDerivations or false || evaluatedPathContent.recurseForRelease or false then
62 dedupResults (lib.mapAttrsToList (name: elem: packagesWithPathInner (path ++ [name]) elem) evaluatedPathContent)
63 else []
64 else []
65 else [];
66 in
67 packagesWithPathInner rootPath pkgs;
68
69 /* Recursively find all packages (derivations) in `pkgs` matching `cond` predicate.
70 */
71 packagesWith = packagesWithPath [];
72
73 /* Recursively find all packages in `pkgs` with updateScript matching given predicate.
74 */
75 packagesWithUpdateScriptMatchingPredicate = cond:
76 packagesWith (path: pkg: builtins.hasAttr "updateScript" pkg && cond path pkg);
77
78 /* Recursively find all packages in `pkgs` with updateScript by given maintainer.
79 */
80 packagesWithUpdateScriptAndMaintainer = maintainer':
81 let
82 maintainer =
83 if ! builtins.hasAttr maintainer' lib.maintainers then
84 builtins.throw "Maintainer with name `${maintainer'} does not exist in `maintainers/maintainer-list.nix`."
85 else
86 builtins.getAttr maintainer' lib.maintainers;
87 in
88 packagesWithUpdateScriptMatchingPredicate (path: pkg:
89 (if builtins.hasAttr "maintainers" pkg.meta
90 then (if builtins.isList pkg.meta.maintainers
91 then builtins.elem maintainer pkg.meta.maintainers
92 else maintainer == pkg.meta.maintainers
93 )
94 else false
95 )
96 );
97
98 /* Recursively find all packages under `path` in `pkgs` with updateScript.
99 */
100 packagesWithUpdateScript = path: pkgs:
101 let
102 prefix = lib.splitString "." path;
103 pathContent = lib.attrByPath prefix null pkgs;
104 in
105 if pathContent == null then
106 builtins.throw "Attribute path `${path}` does not exist."
107 else
108 packagesWithPath prefix (path: pkg: builtins.hasAttr "updateScript" pkg)
109 pathContent;
110
111 /* Find a package under `path` in `pkgs` and require that it has an updateScript.
112 */
113 packageByName = path: pkgs:
114 let
115 package = lib.attrByPath (lib.splitString "." path) null pkgs;
116 in
117 if package == null then
118 builtins.throw "Package with an attribute name `${path}` does not exist."
119 else if ! builtins.hasAttr "updateScript" package then
120 builtins.throw "Package with an attribute name `${path}` does not have a `passthru.updateScript` attribute defined."
121 else
122 { attrPath = path; inherit package; };
123
124 /* List of packages matched based on the CLI arguments.
125 */
126 packages =
127 if package != null then
128 [ (packageByName package pkgs) ]
129 else if predicate != null then
130 packagesWithUpdateScriptMatchingPredicate predicate pkgs
131 else if maintainer != null then
132 packagesWithUpdateScriptAndMaintainer maintainer pkgs
133 else if path != null then
134 packagesWithUpdateScript path pkgs
135 else
136 builtins.throw "No arguments provided.\n\n${helpText}";
137
138 helpText = ''
139 Please run:
140
141 % nix-shell maintainers/scripts/update.nix --argstr maintainer garbas
142
143 to run all update scripts for all packages that lists \`garbas\` as a maintainer
144 and have \`updateScript\` defined, or:
145
146 % nix-shell maintainers/scripts/update.nix --argstr package gnome.nautilus
147
148 to run update script for specific package, or
149
150 % nix-shell maintainers/scripts/update.nix --arg predicate '(path: pkg: builtins.isList pkg.updateScript && builtins.length pkg.updateScript >= 1 && (let script = builtins.head pkg.updateScript; in builtins.isAttrs script && script.name == "gnome-update-script"))'
151
152 to run update script for all packages matching given predicate, or
153
154 % nix-shell maintainers/scripts/update.nix --argstr path gnome
155
156 to run update script for all package under an attribute path.
157
158 You can also add
159
160 --argstr max-workers 8
161
162 to increase the number of jobs in parallel, or
163
164 --argstr keep-going true
165
166 to continue running when a single update fails.
167
168 You can also make the updater automatically commit on your behalf from updateScripts
169 that support it by adding
170
171 --argstr commit true
172 '';
173
174 /* Transform a matched package into an object for update.py.
175 */
176 packageData = { package, attrPath }: {
177 name = package.name;
178 pname = lib.getName package;
179 oldVersion = lib.getVersion package;
180 updateScript = map builtins.toString (lib.toList (package.updateScript.command or package.updateScript));
181 supportedFeatures = package.updateScript.supportedFeatures or [];
182 attrPath = package.updateScript.attrPath or attrPath;
183 };
184
185 /* JSON file with data for update.py.
186 */
187 packagesJson = pkgs.writeText "packages.json" (builtins.toJSON (map packageData packages));
188
189 optionalArgs =
190 lib.optional (max-workers != null) "--max-workers=${max-workers}"
191 ++ lib.optional (keep-going == "true") "--keep-going"
192 ++ lib.optional (commit == "true") "--commit";
193
194 args = [ packagesJson ] ++ optionalArgs;
195
196in pkgs.stdenv.mkDerivation {
197 name = "nixpkgs-update-script";
198 buildCommand = ''
199 echo ""
200 echo "----------------------------------------------------------------"
201 echo ""
202 echo "Not possible to update packages using \`nix-build\`"
203 echo ""
204 echo "${helpText}"
205 echo "----------------------------------------------------------------"
206 exit 1
207 '';
208 shellHook = ''
209 unset shellHook # do not contaminate nested shells
210 exec ${pkgs.python3.interpreter} ${./update.py} ${builtins.concatStringsSep " " args}
211 '';
212}