packagesFromDirectoryRecursive
lib.filesystem.packagesFromDirectoryRecursive
Transform a directory tree containing package files suitable for
callPackage
into a matching nested attribute set of derivations.
For a directory tree like this:
my-packages
├── a.nix
├── b.nix
├── c
│ ├── my-extra-feature.patch
│ ├── package.nix
│ └── support-definitions.nix
└── my-namespace
├── d.nix
├── e.nix
└── f
└── package.nix
packagesFromDirectoryRecursive
will produce an attribute set like this:
# packagesFromDirectoryRecursive {
# callPackage = pkgs.callPackage;
# directory = ./my-packages;
# }
{
a = pkgs.callPackage ./my-packages/a.nix { };
b = pkgs.callPackage ./my-packages/b.nix { };
c = pkgs.callPackage ./my-packages/c/package.nix { };
my-namespace = {
d = pkgs.callPackage ./my-packages/my-namespace/d.nix { };
e = pkgs.callPackage ./my-packages/my-namespace/e.nix { };
f = pkgs.callPackage ./my-packages/my-namespace/f/package.nix { };
};
}
In particular:
- If the input directory contains a
package.nix
file, thencallPackage <directory>/package.nix { }
is returned. - Otherwise, the input directory's contents are listed and transformed into
an attribute set.
-
If a file name has the
.nix
extension, it is turned into attribute where:- The attribute name is the file name without the
.nix
extension - The attribute value is
callPackage <file path> { }
- The attribute name is the file name without the
-
Other files are ignored.
-
Directories are turned into an attribute where:
- The attribute name is the name of the directory
- The attribute value is the result of calling
packagesFromDirectoryRecursive { ... }
on the directory.
As a result, directories with no
.nix
files (including empty directories) will be transformed into empty attribute sets.
-
Type
packagesFromDirectoryRecursive :: {
callPackage :: Path -> {} -> a,
directory :: Path,
...
} -> AttrSet
Inputs
callPackage
- 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 eitherpkgs
or a new scope corresponding to thedirectory
. directory
- The directory to read package files from.
Examples
Basic use of lib.packagesFromDirectoryRecursive
packagesFromDirectoryRecursive {
inherit (pkgs) callPackage;
directory = ./my-packages;
}
=> { ... }
In this case, callPackage
will only search pkgs
for a file's input parameters.
In other words, a file cannot refer to another file in the directory in its input parameters.
Create a scope for the nix files found in a directory
lib.makeScope pkgs.newScope (
self: packagesFromDirectoryRecursive {
inherit (self) callPackage;
directory = ./my-packages;
}
)
=> { ... }
For example, take the following directory structure:
my-packages
├── a.nix → { b }: assert b ? b1; ...
└── b
├── b1.nix → { a }: ...
└── b2.nix
Here, b1.nix
can specify { a }
as a parameter, which callPackage
will resolve as expected.
Likewise, a.nix
receive an attrset corresponding to the contents of the b
directory.
a.nix
cannot directly take as inputs packages defined in a child directory, such as b1
.
As of now, lib.packagesFromDirectoryRecursive
cannot create nested scopes for sub-directories.
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
.
Noogle detected
Implementation
The following is the current implementation of this function.
packagesFromDirectoryRecursive =
{
callPackage,
directory,
...
}:
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);