Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/stdenv/stdenv.xml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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;
Comment on lines 171 to 172
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice if this were documented.

Copy link
Member Author

@jtojnar jtojnar Sep 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally removed the documentation in c1b0544 so that people do not rely on it yet. I am weighting whether the added complexity is worth it and reserve the right to remove it.

};

/* 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
Loading