at master 9.5 kB view raw
1/* 2 To run: 3 4 nix-shell maintainers/scripts/update.nix 5 6 See https://nixos.org/manual/nixpkgs/unstable/#var-passthru-updateScript 7*/ 8{ 9 package ? null, 10 maintainer ? null, 11 predicate ? null, 12 get-script ? pkg: pkg.updateScript or null, 13 path ? null, 14 max-workers ? null, 15 include-overlays ? false, 16 keep-going ? null, 17 commit ? null, 18 skip-prompt ? null, 19 order ? null, 20}: 21 22let 23 pkgs = import ./../../default.nix ( 24 ( 25 if include-overlays == false then 26 { overlays = [ ]; } 27 else if include-overlays == true then 28 { } # Let Nixpkgs include overlays impurely. 29 else 30 { overlays = include-overlays; } 31 ) 32 // { 33 config.allowAliases = false; 34 } 35 ); 36 37 inherit (pkgs) lib; 38 39 # Remove duplicate elements from the list based on some extracted value. O(n^2) complexity. 40 nubOn = 41 f: list: 42 if list == [ ] then 43 [ ] 44 else 45 let 46 x = lib.head list; 47 xs = lib.filter (p: f x != f p) (lib.drop 1 list); 48 in 49 [ x ] ++ nubOn f xs; 50 51 /* 52 Recursively find all packages (derivations) in `pkgs` matching `cond` predicate. 53 54 Type: packagesWithPath :: AttrPath (AttrPath derivation bool) AttrSet List<AttrSet{attrPath :: str; package :: derivation; }> 55 AttrPath :: [str] 56 57 The packages will be returned as a list of named pairs comprising of: 58 - attrPath: stringified attribute path (based on `rootPath`) 59 - package: corresponding derivation 60 */ 61 packagesWithPath = 62 rootPath: cond: pkgs: 63 let 64 packagesWithPathInner = 65 path: pathContent: 66 let 67 result = builtins.tryEval pathContent; 68 69 somewhatUniqueRepresentant = 70 { package, attrPath }: 71 { 72 updateScript = (get-script package); 73 # Some updaters use the same `updateScript` value for all packages. 74 # Also compare `meta.description`. 75 position = package.meta.position or null; 76 # We cannot always use `meta.position` since it might not be available 77 # or it might be shared among multiple packages. 78 }; 79 80 dedupResults = lst: nubOn somewhatUniqueRepresentant (lib.concatLists lst); 81 in 82 if result.success then 83 let 84 evaluatedPathContent = result.value; 85 in 86 if lib.isDerivation evaluatedPathContent then 87 lib.optional (cond path evaluatedPathContent) { 88 attrPath = lib.concatStringsSep "." path; 89 package = evaluatedPathContent; 90 } 91 else if lib.isAttrs evaluatedPathContent then 92 # If user explicitly points to an attrSet or it is marked for recursion, we recur. 93 if 94 path == rootPath 95 || evaluatedPathContent.recurseForDerivations or false 96 || evaluatedPathContent.recurseForRelease or false 97 then 98 dedupResults ( 99 lib.mapAttrsToList (name: elem: packagesWithPathInner (path ++ [ name ]) elem) evaluatedPathContent 100 ) 101 else 102 [ ] 103 else 104 [ ] 105 else 106 [ ]; 107 in 108 packagesWithPathInner rootPath pkgs; 109 110 # Recursively find all packages (derivations) in `pkgs` matching `cond` predicate. 111 packagesWith = packagesWithPath [ ]; 112 113 # Recursively find all packages in `pkgs` with updateScript matching given predicate. 114 packagesWithUpdateScriptMatchingPredicate = 115 cond: packagesWith (path: pkg: (get-script pkg != null) && cond path pkg); 116 117 # Recursively find all packages in `pkgs` with updateScript by given maintainer. 118 packagesWithUpdateScriptAndMaintainer = 119 maintainer': 120 let 121 maintainer = 122 if !builtins.hasAttr maintainer' lib.maintainers then 123 builtins.throw "Maintainer with name `${maintainer'} does not exist in `maintainers/maintainer-list.nix`." 124 else 125 builtins.getAttr maintainer' lib.maintainers; 126 in 127 packagesWithUpdateScriptMatchingPredicate ( 128 path: pkg: 129 ( 130 if builtins.hasAttr "maintainers" pkg.meta then 131 ( 132 if builtins.isList pkg.meta.maintainers then 133 builtins.elem maintainer pkg.meta.maintainers 134 else 135 maintainer == pkg.meta.maintainers 136 ) 137 else 138 false 139 ) 140 ); 141 142 # Recursively find all packages under `path` in `pkgs` with updateScript. 143 packagesWithUpdateScript = 144 path: pkgs: 145 let 146 prefix = lib.splitString "." path; 147 pathContent = lib.attrByPath prefix null pkgs; 148 in 149 if pathContent == null then 150 builtins.throw "Attribute path `${path}` does not exist." 151 else 152 packagesWithPath prefix (path: pkg: (get-script pkg != null)) pathContent; 153 154 # Find a package under `path` in `pkgs` and require that it has an updateScript. 155 packageByName = 156 path: pkgs: 157 let 158 package = lib.attrByPath (lib.splitString "." path) null pkgs; 159 in 160 if package == null then 161 builtins.throw "Package with an attribute name `${path}` does not exist." 162 else if get-script package == null then 163 builtins.throw "Package with an attribute name `${path}` does not have a `passthru.updateScript` attribute defined." 164 else 165 { 166 attrPath = path; 167 inherit package; 168 }; 169 170 # List of packages matched based on the CLI arguments. 171 packages = 172 if package != null then 173 [ (packageByName package pkgs) ] 174 else if predicate != null then 175 packagesWithUpdateScriptMatchingPredicate predicate pkgs 176 else if maintainer != null then 177 packagesWithUpdateScriptAndMaintainer maintainer pkgs 178 else if path != null then 179 packagesWithUpdateScript path pkgs 180 else 181 builtins.throw "No arguments provided.\n\n${helpText}"; 182 183 helpText = '' 184 Please run: 185 186 % nix-shell maintainers/scripts/update.nix --argstr maintainer garbas 187 188 to run all update scripts for all packages that lists \`garbas\` as a maintainer 189 and have \`updateScript\` defined, or: 190 191 % nix-shell maintainers/scripts/update.nix --argstr package nautilus 192 193 to run update script for specific package, or 194 195 % nix-shell maintainers/scripts/update.nix --arg predicate '(path: pkg: pkg.updateScript.name or null == "gnome-update-script")' 196 197 to run update script for all packages matching given predicate, or 198 199 % nix-shell maintainers/scripts/update.nix --argstr path gnome 200 201 to run update script for all package under an attribute path. 202 203 You can also add 204 205 --argstr max-workers 8 206 207 to increase the number of jobs in parallel, or 208 209 --argstr keep-going true 210 211 to continue running when a single update fails. 212 213 You can also make the updater automatically commit on your behalf from updateScripts 214 that support it by adding 215 216 --argstr commit true 217 218 to skip prompt: 219 220 --argstr skip-prompt true 221 222 By default, the updater will update the packages in arbitrary order. Alternately, you can force a specific order based on the packages dependency relations: 223 224 - Reverse topological order (e.g. {"gnome-text-editor", "gimp"}, {"gtk3", "gtk4"}, {"glib"}) is useful when you want checkout each commit one by one to build each package individually but some of the packages to be updated would cause a mass rebuild for the others. Of course, this requires that none of the updated dependents require a new version of the dependency. 225 226 --argstr order reverse-topological 227 228 - Topological order (e.g. {"glib"}, {"gtk3", "gtk4"}, {"gnome-text-editor", "gimp"}) is useful when the updated dependents require a new version of updated dependency. 229 230 --argstr order topological 231 232 Note that sorting requires instantiating each package and then querying Nix store for requisites so it will be pretty slow with large number of packages. 233 ''; 234 235 # Transform a matched package into an object for update.py. 236 packageData = 237 { package, attrPath }: 238 let 239 updateScript = get-script package; 240 in 241 { 242 name = package.name; 243 pname = lib.getName package; 244 oldVersion = lib.getVersion package; 245 updateScript = map builtins.toString (lib.toList (updateScript.command or updateScript)); 246 supportedFeatures = updateScript.supportedFeatures or [ ]; 247 attrPath = updateScript.attrPath or attrPath; 248 }; 249 250 # JSON file with data for update.py. 251 packagesJson = pkgs.writeText "packages.json" (builtins.toJSON (map packageData packages)); 252 253 optionalArgs = 254 lib.optional (max-workers != null) "--max-workers=${max-workers}" 255 ++ lib.optional (keep-going == "true") "--keep-going" 256 ++ lib.optional (commit == "true") "--commit" 257 ++ lib.optional (skip-prompt == "true") "--skip-prompt" 258 ++ lib.optional (order != null) "--order=${order}"; 259 260 args = [ packagesJson ] ++ optionalArgs; 261 262in 263pkgs.stdenv.mkDerivation { 264 name = "nixpkgs-update-script"; 265 buildCommand = '' 266 echo "" 267 echo "----------------------------------------------------------------" 268 echo "" 269 echo "Not possible to update packages using \`nix-build\`" 270 echo "" 271 echo "${helpText}" 272 echo "----------------------------------------------------------------" 273 exit 1 274 ''; 275 shellHook = '' 276 unset shellHook # do not contaminate nested shells 277 exec ${pkgs.python3.interpreter} ${./update.py} ${builtins.concatStringsSep " " args} 278 ''; 279 nativeBuildInputs = [ 280 pkgs.git 281 pkgs.nix 282 pkgs.cacert 283 ]; 284}