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