{ package ? null , maintainer ? null , predicate ? null , path ? null , max-workers ? null , include-overlays ? false , keep-going ? null , commit ? null }: # TODO: add assert statements let pkgs = import ./../../default.nix ( if include-overlays == false then { overlays = []; } else if include-overlays == true then { } # Let Nixpkgs include overlays impurely. else { overlays = include-overlays; } ); inherit (pkgs) lib; /* Remove duplicate elements from the list based on some extracted value. O(n^2) complexity. */ nubOn = f: list: if list == [] then [] else let x = lib.head list; xs = lib.filter (p: f x != f p) (lib.drop 1 list); in [x] ++ nubOn f xs; /* Recursively find all packages (derivations) in `pkgs` matching `cond` predicate. Type: packagesWithPath :: AttrPath → (AttrPath → derivation → bool) → AttrSet → List AttrPath :: [str] The packages will be returned as a list of named pairs comprising of: - attrPath: stringified attribute path (based on `rootPath`) - package: corresponding derivation */ packagesWithPath = rootPath: cond: pkgs: let packagesWithPathInner = path: pathContent: let result = builtins.tryEval pathContent; dedupResults = lst: nubOn ({ package, attrPath }: package.updateScript) (lib.concatLists lst); in if result.success then let evaluatedPathContent = result.value; in if lib.isDerivation evaluatedPathContent then lib.optional (cond path evaluatedPathContent) { attrPath = lib.concatStringsSep "." path; package = evaluatedPathContent; } else if lib.isAttrs evaluatedPathContent then # If user explicitly points to an attrSet or it is marked for recursion, we recur. if path == rootPath || evaluatedPathContent.recurseForDerivations or false || evaluatedPathContent.recurseForRelease or false then dedupResults (lib.mapAttrsToList (name: elem: packagesWithPathInner (path ++ [name]) elem) evaluatedPathContent) else [] else [] else []; in packagesWithPathInner rootPath pkgs; /* Recursively find all packages (derivations) in `pkgs` matching `cond` predicate. */ packagesWith = packagesWithPath []; /* Recursively find all packages in `pkgs` with updateScript matching given predicate. */ packagesWithUpdateScriptMatchingPredicate = cond: packagesWith (path: pkg: builtins.hasAttr "updateScript" pkg && cond path pkg); /* Recursively find all packages in `pkgs` with updateScript by given maintainer. */ packagesWithUpdateScriptAndMaintainer = maintainer': let maintainer = if ! builtins.hasAttr maintainer' lib.maintainers then builtins.throw "Maintainer with name `${maintainer'} does not exist in `maintainers/maintainer-list.nix`." else builtins.getAttr maintainer' lib.maintainers; in packagesWithUpdateScriptMatchingPredicate (path: pkg: (if builtins.hasAttr "maintainers" pkg.meta then (if builtins.isList pkg.meta.maintainers then builtins.elem maintainer pkg.meta.maintainers else maintainer == pkg.meta.maintainers ) else false ) ); /* Recursively find all packages under `path` in `pkgs` with updateScript. */ packagesWithUpdateScript = path: pkgs: let prefix = lib.splitString "." path; pathContent = lib.attrByPath prefix null pkgs; in if pathContent == null then builtins.throw "Attribute path `${path}` does not exists." else packagesWithPath prefix (path: pkg: builtins.hasAttr "updateScript" pkg) pathContent; /* Find a package under `path` in `pkgs` and require that it has an updateScript. */ packageByName = path: pkgs: let package = lib.attrByPath (lib.splitString "." path) null pkgs; in if package == null then builtins.throw "Package with an attribute name `${path}` does not exists." else if ! builtins.hasAttr "updateScript" package then builtins.throw "Package with an attribute name `${path}` does not have a `passthru.updateScript` attribute defined." else { attrPath = path; inherit package; }; /* List of packages matched based on the CLI arguments. */ packages = if package != null then [ (packageByName package pkgs) ] else if predicate != null then packagesWithUpdateScriptMatchingPredicate predicate pkgs else if maintainer != null then packagesWithUpdateScriptAndMaintainer maintainer pkgs else if path != null then packagesWithUpdateScript path pkgs else builtins.throw "No arguments provided.\n\n${helpText}"; helpText = '' Please run: % nix-shell maintainers/scripts/update.nix --argstr maintainer garbas to run all update scripts for all packages that lists \`garbas\` as a maintainer and have \`updateScript\` defined, or: % nix-shell maintainers/scripts/update.nix --argstr package gnome.nautilus to run update script for specific package, or % 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"))' to run update script for all packages matching given predicate, or % nix-shell maintainers/scripts/update.nix --argstr path gnome to run update script for all package under an attribute path. You can also add --argstr max-workers 8 to increase the number of jobs in parallel, or --argstr keep-going true to continue running when a single update fails. You can also make the updater automatically commit on your behalf from updateScripts that support it by adding --argstr commit true ''; /* Transform a matched package into an object for update.py. */ packageData = { package, attrPath }: { name = package.name; pname = lib.getName package; oldVersion = lib.getVersion package; updateScript = map builtins.toString (lib.toList (package.updateScript.command or package.updateScript)); supportedFeatures = package.updateScript.supportedFeatures or []; attrPath = package.updateScript.attrPath or attrPath; }; /* JSON file with data for update.py. */ packagesJson = pkgs.writeText "packages.json" (builtins.toJSON (map packageData packages)); optionalArgs = lib.optional (max-workers != null) "--max-workers=${max-workers}" ++ lib.optional (keep-going == "true") "--keep-going" ++ lib.optional (commit == "true") "--commit"; args = [ packagesJson ] ++ optionalArgs; in pkgs.stdenv.mkDerivation { name = "nixpkgs-update-script"; buildCommand = '' echo "" echo "----------------------------------------------------------------" echo "" echo "Not possible to update packages using \`nix-build\`" echo "" echo "${helpText}" echo "----------------------------------------------------------------" exit 1 ''; shellHook = '' unset shellHook # do not contaminate nested shells exec ${pkgs.python3.interpreter} ${./update.py} ${builtins.concatStringsSep " " args} ''; }