-
-
Notifications
You must be signed in to change notification settings - Fork 14.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lib.packagesFromDirectoryRecursive: use explicit recursion, support nested scopes #359984
base: master
Are you sure you want to change the base?
Changes from all commits
b377da6
3eb6c8c
03968c4
b0df85b
3ae1e97
1ca4a58
bf5cd41
0fe9ad2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,11 +12,6 @@ let | |
toString | ||
; | ||
|
||
inherit (lib.attrsets) | ||
mapAttrs' | ||
filterAttrs | ||
; | ||
|
||
inherit (lib.filesystem) | ||
pathIsDirectory | ||
pathIsRegularFile | ||
|
@@ -26,7 +21,6 @@ let | |
|
||
inherit (lib.strings) | ||
hasSuffix | ||
removeSuffix | ||
; | ||
in | ||
|
||
|
@@ -312,11 +306,13 @@ in | |
# Type | ||
|
||
``` | ||
packagesFromDirectoryRecursive :: { | ||
packagesFromDirectoryRecursive :: (args :: { | ||
callPackage :: Path -> {} -> a, | ||
newScope? :: AttrSet -> scope, | ||
directory :: Path, | ||
... | ||
} -> AttrSet | ||
recurseIntoDirectory? :: (args -> AttrSet) -> args -> AttrSet, | ||
recurseArgs? :: Any | ||
}) -> AttrSet | ||
``` | ||
|
||
# Inputs | ||
|
@@ -325,9 +321,36 @@ in | |
: The function used to convert a Nix file's path into a leaf of the attribute set. | ||
It is typically the `callPackage` function, taken from either `pkgs` or a new scope corresponding to the `directory`. | ||
|
||
`newScope` | ||
: If present, this function is used by the default `recurseIntoDirectory` to generate a new scope. | ||
The arguments are updated with the scope's `callPackage` and `newScope` functions, so packages can require | ||
anything in their scope, or in an ancestor of their scope. | ||
This argument has no effect when `recurseIntoDirectory` is provided. | ||
|
||
`directory` | ||
: The directory to read package files from. | ||
|
||
`recurseIntoDirectory` | ||
: This argument is applied to the function which processes directories. | ||
: Equivalently, this function takes `processDir` and `args`, and can modify arguments passed to `processDir` | ||
(same as above) before calling it, as well as modify its output (which is then returned by `recurseIntoDirectory`). | ||
|
||
:::{.note} | ||
When `newScope` is set, the default `recurseIntoDirectory` is equivalent to: | ||
```nix | ||
processDir: { newScope, ... }@args: | ||
# create a new scope and mark it `recurseForDerivations` | ||
lib.recurseIntoAttrs (lib.makeScope newScope (self: | ||
# generate the attrset representing the directory, using the new scope's `callPackage` and `newScope` | ||
processDir (args // { | ||
inherit (self) callPackage newScope; | ||
}) | ||
)) | ||
``` | ||
::: | ||
|
||
`recurseArgs` | ||
: Optional argument, which can be hold data used by `recurseIntoDirectory` | ||
|
||
# Examples | ||
:::{.example} | ||
|
@@ -348,12 +371,10 @@ in | |
::::{.example} | ||
## Create a scope for the nix files found in a directory | ||
```nix | ||
lib.makeScope pkgs.newScope ( | ||
self: packagesFromDirectoryRecursive { | ||
inherit (self) callPackage; | ||
directory = ./my-packages; | ||
} | ||
) | ||
packagesFromDirectoryRecursive { | ||
inherit (pkgs) callPackage newScope; | ||
directory = ./my-packages; | ||
} | ||
=> { ... } | ||
``` | ||
|
||
|
@@ -372,46 +393,100 @@ in | |
:::{.note} | ||
`a.nix` cannot directly take as inputs packages defined in a child directory, such as `b1`. | ||
::: | ||
:::: | ||
|
||
:::{.warning} | ||
As of now, `lib.packagesFromDirectoryRecursive` cannot create nested scopes for sub-directories. | ||
:::{.example} | ||
## Mark with `recurseIntoAttrs` when recursing into a directory | ||
```nix | ||
packagesFromDirectoryRecursive { | ||
inherit (pkgs) callPackage; | ||
directory = ./my-packages; | ||
|
||
In particular, files under `b/` can only require (as inputs) other files under `my-packages`, | ||
but not to those in the same directory, nor those in a parent directory; e.g, `b2.nix` cannot directly | ||
require `b1`. | ||
recurseIntoDirectory = processDir: args: lib.recurseIntoAttrs (processDir args); | ||
} | ||
``` | ||
::: | ||
|
||
:::{.example} | ||
## Express custom recursion behaviour with `recurseIntoDirectory` | ||
For instance, only mark attrsets produced by `packagesFromDirectoryRecursive` with `recurseForDerivations` | ||
if they (transitively) contain derivations. | ||
|
||
```nix | ||
packagesFromDirectoryRecursive { | ||
inherit (pkgs) callPackage; | ||
directory = ./my-packages; | ||
|
||
recurseIntoDirectory = processDir: args: let | ||
result = processDir args; | ||
in result // { | ||
recurseForDerivations = with lib; | ||
any (child: isDerivation child || child.recurseForDerivations or false) result; | ||
}; | ||
} | ||
``` | ||
::: | ||
:::: | ||
*/ | ||
packagesFromDirectoryRecursive = | ||
let | ||
inherit (lib) concatMapAttrs id makeScope recurseIntoAttrs removeSuffix; | ||
inherit (lib.path) append; | ||
|
||
# Generate an attrset corresponding to a given directory. | ||
# This function is outside `packagesFromDirectoryRecursive`'s lambda expression, | ||
# to prevent accidentally using its parameters. | ||
processDir = { callPackage, directory, ... }@args: | ||
concatMapAttrs (name: type: | ||
# for each directory entry | ||
let path = append directory name; in | ||
if type == "directory" then { | ||
# recurse into directories | ||
"${name}" = packagesFromDirectoryRecursive (args // { | ||
directory = path; | ||
}); | ||
} else if type == "regular" && hasSuffix ".nix" name then { | ||
# call .nix files | ||
"${removeSuffix ".nix" name}" = callPackage path {}; | ||
} else if type == "regular" then { | ||
# ignore non-nix files | ||
} else throw '' | ||
lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString path} | ||
'' | ||
) (builtins.readDir directory); | ||
in | ||
{ | ||
callPackage, | ||
newScope ? throw "lib.packagesFromDirectoryRecursive: newScope wasn't passed in args", | ||
directory, | ||
... | ||
}: | ||
# recurseIntoDirectory can modify the function used when processing directory entries | ||
# and recurseArgs can (optionally) hold data for its use ; see nixdoc above | ||
recurseArgs ? throw "lib.packagesFromDirectoryRecursive: recurseArgs wasn't passed in args", | ||
recurseIntoDirectory ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function could be its own unit. For example Also this inline documentation as plain comment will not render into any documentation. If you pull this out you can write a nice doc-comment describing the behavior. /**
Create a new scope and mark it `recurseForDerivations`.
This lets the packages refer to each other.
See also:
- [lib.makeScope](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope) and
- [lib.recurseIntoAttrs](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope)
*/
defaultRecurseIntoDirectory = args: if args ? newScope then ... |
||
if args ? newScope then | ||
# `processDir` is the same function as defined above | ||
# `args` are the arguments passed to (this recursive call of) `packagesFromDirectoryRecursive` | ||
processDir: { newScope, ... }@args: | ||
# Create a new scope and mark it `recurseForDerivations`. | ||
# This lets the packages refer to each other. | ||
# See: | ||
# [lib.makeScope](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope) and | ||
# [lib.recurseIntoAttrs](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope) | ||
recurseIntoAttrs (makeScope newScope (self: | ||
# generate the attrset representing the directory, using the new scope's `callPackage` and `newScope` | ||
processDir (args // { | ||
inherit (self) callPackage newScope; | ||
}) | ||
)) | ||
else | ||
# otherwise, no modification is necessary | ||
id, | ||
}@args: | ||
let | ||
inherit (lib) concatMapAttrs removeSuffix; | ||
inherit (lib.path) append; | ||
defaultPath = append directory "package.nix"; | ||
in | ||
if pathExists defaultPath then | ||
# if `${directory}/package.nix` exists, call it directly | ||
callPackage defaultPath {} | ||
else concatMapAttrs (name: type: | ||
# otherwise, for each directory entry | ||
let path = append directory name; in | ||
if type == "directory" then { | ||
# recurse into directories | ||
"${name}" = packagesFromDirectoryRecursive { | ||
inherit callPackage; | ||
directory = path; | ||
}; | ||
} else if type == "regular" && hasSuffix ".nix" name then { | ||
# call .nix files | ||
"${removeSuffix ".nix" name}" = callPackage path {}; | ||
} else if type == "regular" then { | ||
# ignore non-nix files | ||
} else throw '' | ||
lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString path} | ||
'' | ||
) (builtins.readDir directory); | ||
else | ||
recurseIntoDirectory processDir args; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{ }: "a" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ a }: | ||
assert a == "a"; | ||
"b" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{ } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{ }: "c" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{ } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ a, e }: | ||
# Check we can get parameter from the parent scope(s) as well as the current one | ||
assert a == "a"; | ||
assert e == "e"; | ||
"d" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ d }: | ||
# Check that mutual recursion is possible | ||
"e" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{ }: "f" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
a, | ||
d, | ||
h, | ||
}: | ||
# Check we can get parameters from ancestral scopes (e.g. the scope's grandparent) | ||
"g" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{ }: "h" |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -310,3 +310,11 @@ | |||||
- Structure of the `functor` of some types has changed. `functor` is an implementation detail and should not be relied upon. If you did rely on it let us know in this [PR](https://github.com/NixOS/nixpkgs/pull/363565). | ||||||
- [`lib.types.enum`](https://nixos.org/manual/nixos/unstable/#sec-option-types-basic): Previously the `functor.payload` was the list of enum values directly. Now it is an attribute set containing the values in the `values` attribute. | ||||||
- [`lib.types.separatedString`](https://nixos.org/manual/nixos/unstable/#sec-option-types-string): Previously the `functor.payload` was the seperator directly. Now it is an attribute set containing the seperator in the `sep` attribute. | ||||||
|
||||||
- [`lib.packagesFromDirectoryRecursive`] now rejects unknown arguments, and applies [`lib.recurseIntoAttrs`] when recursing into a directory. | ||||||
[`lib.packagesFromDirectoryRecursive`]: https://nixos.org/manual/nixpkgs/stable/#function-library-lib.filesystem.packagesFromDirectoryRecursive | ||||||
[`lib.recurseIntoAttrs`]: https://nixos.org/manual/nixpkgs/stable/#function-library-lib.attrsets.recurseIntoAttrs | ||||||
|
||||||
### Other notable changes {#sec-release-25.05-lib-notable-changes} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems we used a different heading in all the previous release notes:
Suggested change
|
||||||
|
||||||
- [`lib.packagesFromDirectoryRecursive`] can now construct nested scopes matching the directory tree passed as input. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.