packagesFromDirectoryRecursive
lib.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
# Determine if a directory entry from `readDir` indicates a package or
# directory of packages.
directoryEntryIsPackage = basename: type:
type == "directory" || hasSuffix ".nix" basename;
# List directory entries that indicate packages in the given `path`.
packageDirectoryEntries = path:
filterAttrs directoryEntryIsPackage (readDir path);
# Transform a directory entry (a `basename` and `type` pair) into a
# package.
directoryEntryToAttrPair = subdirectory: basename: type:
let
path = subdirectory + "/${basename}";
in
if type == "regular"
then
{
name = removeSuffix ".nix" basename;
value = callPackage path { };
}
else
if type == "directory"
then
{
name = basename;
value = packagesFromDirectory path;
}
else
throw
''
lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString subdirectory}
'';
# Transform a directory into a package (if there's a `package.nix`) or
# set of packages (otherwise).
packagesFromDirectory = path:
let
defaultPackagePath = path + "/package.nix";
in
if pathExists defaultPackagePath
then callPackage defaultPackagePath { }
else mapAttrs'
(directoryEntryToAttrPair path)
(packageDirectoryEntries path);
in
packagesFromDirectory directory;