Skip to content

Commit

Permalink
Merge pull request NixOS#98304 from jtojnar/updateScript-commit3
Browse files Browse the repository at this point in the history
maintainers/scripts/update.nix: Add support for auto-commiting changes
  • Loading branch information
jtojnar committed Oct 2, 2020
2 parents 1b17aea + 74a5bb4 commit 74c5472
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 84 deletions.
9 changes: 6 additions & 3 deletions doc/stdenv/stdenv.xml
Expand Up @@ -475,9 +475,12 @@ passthru.updateScript = writeScript "update-zoom-us" ''
<programlisting>
passthru.updateScript = [ ../../update.sh pname "--requested-release=unstable" ];
</programlisting>
</para>
<para>
The script will be usually run from the root of the Nixpkgs repository but you should not rely on that. Also note that the update scripts will be run in parallel by default; you should avoid running <command>git commit</command> or any other commands that cannot handle that.
The script will be run with <variable>UPDATE_NIX_ATTR_PATH</variable> environment variable set to the attribute path it is supposed to update.
<note>
<para>
The script will be usually run from the root of the Nixpkgs repository but you should not rely on that. Also note that the update scripts will be run in parallel by default; you should avoid running <command>git commit</command> or any other commands that cannot handle that.
</para>
</note>
</para>
<para>
For information about how to run the updates, execute <command>nix-shell maintainers/scripts/update.nix</command>.
Expand Down
125 changes: 80 additions & 45 deletions maintainers/scripts/update.nix
Expand Up @@ -4,6 +4,7 @@
, max-workers ? null
, include-overlays ? false
, keep-going ? null
, commit ? null
}:

# TODO: add assert statements
Expand Down Expand Up @@ -31,30 +32,47 @@ let
in
[x] ++ nubOn f xs;

packagesWithPath = relativePath: cond: return: pathContent:
let
result = builtins.tryEval pathContent;
/* Recursively find all packages (derivations) in `pkgs` matching `cond` predicate.
dedupResults = lst: nubOn (pkg: pkg.updateScript) (lib.concatLists lst);
in
if result.success then
Type: packagesWithPath :: AttrPath → (AttrPath → derivation → bool) → (AttrSet | List) → List<AttrSet{attrPath :: str; package :: derivation; }>
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
pathContent = result.value;
result = builtins.tryEval pathContent;

dedupResults = lst: nubOn ({ package, attrPath }: package.updateScript) (lib.concatLists lst);
in
if lib.isDerivation pathContent then
lib.optional (cond relativePath pathContent) (return relativePath pathContent)
else if lib.isAttrs pathContent then
# If user explicitly points to an attrSet or it is marked for recursion, we recur.
if relativePath == [] || pathContent.recurseForDerivations or false || pathContent.recurseForRelease or false then
dedupResults (lib.mapAttrsToList (name: elem: packagesWithPath (relativePath ++ [name]) cond return elem) pathContent)
else []
else if lib.isList pathContent then
dedupResults (lib.imap0 (i: elem: packagesWithPath (relativePath ++ [i]) cond return elem) pathContent)
else []
else [];
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 if lib.isList evaluatedPathContent then
dedupResults (lib.imap0 (i: elem: packagesWithPathInner (path ++ [i]) elem) evaluatedPathContent)
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 by given maintainer.
*/
packagesWithUpdateScriptAndMaintainer = maintainer':
let
maintainer =
Expand All @@ -63,47 +81,51 @@ let
else
builtins.getAttr maintainer' lib.maintainers;
in
packagesWith (relativePath: pkg: builtins.hasAttr "updateScript" 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
)
)
(relativePath: pkg: pkg)
pkgs;

packagesWithUpdateScript = path:
packagesWith (path: pkg: builtins.hasAttr "updateScript" 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
pathContent = lib.attrByPath (lib.splitString "." path) null pkgs;
prefix = lib.splitString "." path;
pathContent = lib.attrByPath prefix null pkgs;
in
if pathContent == null then
builtins.throw "Attribute path `${path}` does not exists."
else
packagesWith (relativePath: pkg: builtins.hasAttr "updateScript" pkg)
(relativePath: pkg: pkg)
packagesWithPath prefix (path: pkg: builtins.hasAttr "updateScript" pkg)
pathContent;

packageByName = name:
/* Find a package under `path` in `pkgs` and require that it has an updateScript.
*/
packageByName = path: pkgs:
let
package = lib.attrByPath (lib.splitString "." name) null pkgs;
package = lib.attrByPath (lib.splitString "." path) null pkgs;
in
if package == null then
builtins.throw "Package with an attribute name `${name}` does not exists."
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 `${name}` does not have a `passthru.updateScript` attribute defined."
builtins.throw "Package with an attribute name `${path}` does not have a `passthru.updateScript` attribute defined."
else
package;
{ attrPath = path; inherit package; };

/* List of packages matched based on the CLI arguments.
*/
packages =
if package != null then
[ (packageByName package) ]
[ (packageByName package pkgs) ]
else if maintainer != null then
packagesWithUpdateScriptAndMaintainer maintainer
packagesWithUpdateScriptAndMaintainer maintainer pkgs
else if path != null then
packagesWithUpdateScript path
packagesWithUpdateScript path pkgs
else
builtins.throw "No arguments provided.\n\n${helpText}";

Expand Down Expand Up @@ -132,19 +154,32 @@ let
--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
'';

packageData = package: {
/* Transform a matched package into an object for update.py.
*/
packageData = { package, attrPath }: {
name = package.name;
pname = lib.getName package;
updateScript = map builtins.toString (lib.toList package.updateScript);
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 (keep-going == "true") "--keep-going"
++ lib.optional (commit == "true") "--commit";

args = [ packagesJson ] ++ optionalArgs;

Expand Down

0 comments on commit 74c5472

Please sign in to comment.