lib.packagesFromDirectoryRecursive: init

Co-authored-by: Gabriella Gonzalez <GenuineGabriella@gmail.com>

Changed files
+205 -1
lib
+2 -1
lib/default.nix
···
inherit (self.meta) addMetaAttrs dontDistribute setName updateName
appendToName mapDerivationAttrset setPrio lowPrio lowPrioSet hiPrio
hiPrioSet getLicenseFromSpdxId getExe getExe';
-
inherit (self.filesystem) pathType pathIsDirectory pathIsRegularFile;
+
inherit (self.filesystem) pathType pathIsDirectory pathIsRegularFile
+
packagesFromDirectoryRecursive;
inherit (self.sources) cleanSourceFilter
cleanSource sourceByRegex sourceFilesBySuffices
commitIdFromGitRepo cleanSourceWith pathHasContext
+154
lib/filesystem.nix
···
inherit (builtins)
readDir
pathExists
+
toString
+
;
+
+
inherit (lib.attrsets)
+
mapAttrs'
+
filterAttrs
;
inherit (lib.filesystem)
pathType
+
;
+
+
inherit (lib.strings)
+
hasSuffix
+
removeSuffix
;
in
···
dir + "/${name}"
) (builtins.readDir dir));
+
/*
+
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:
+
+
```nix
+
# 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, then
+
`callPackage <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> { }`
+
- 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.
+
+
Example:
+
packagesFromDirectoryRecursive {
+
inherit (pkgs) callPackage;
+
directory = ./my-packages;
+
}
+
=> { ... }
+
+
lib.makeScope pkgs.newScope (
+
self: packagesFromDirectoryRecursive {
+
callPackage = self.callPackage;
+
directory = ./my-packages;
+
}
+
)
+
=> { ... }
+
+
Type:
+
packagesFromDirectoryRecursive :: AttrSet -> AttrSet
+
*/
+
packagesFromDirectoryRecursive =
+
# Options.
+
{
+
/*
+
`pkgs.callPackage`
+
+
Type:
+
Path -> AttrSet -> a
+
*/
+
callPackage,
+
/*
+
The directory to read package files from
+
+
Type:
+
Path
+
*/
+
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;
}
+33
lib/tests/misc.nix
···
expr = meta.platformMatch { } "x86_64-linux";
expected = false;
};
+
+
testPackagesFromDirectoryRecursive = {
+
expr = packagesFromDirectoryRecursive {
+
callPackage = path: overrides: import path overrides;
+
directory = ./packages-from-directory;
+
};
+
expected = {
+
a = "a";
+
b = "b";
+
# Note: Other files/directories in `./test-data/c/` are ignored and can be
+
# used by `package.nix`.
+
c = "c";
+
my-namespace = {
+
d = "d";
+
e = "e";
+
f = "f";
+
my-sub-namespace = {
+
g = "g";
+
h = "h";
+
};
+
};
+
};
+
};
+
+
# Check that `packagesFromDirectoryRecursive` can process a directory with a
+
# top-level `package.nix` file into a single package.
+
testPackagesFromDirectoryRecursiveTopLevelPackageNix = {
+
expr = packagesFromDirectoryRecursive {
+
callPackage = path: overrides: import path overrides;
+
directory = ./packages-from-directory/c;
+
};
+
expected = "c";
+
};
+2
lib/tests/packages-from-directory/a.nix
···
+
{ }:
+
"a"
+2
lib/tests/packages-from-directory/b.nix
···
+
{ }:
+
"b"
lib/tests/packages-from-directory/c/my-extra-feature.patch

This is a binary file and will not be displayed.

lib/tests/packages-from-directory/c/not-a-namespace/not-a-package.nix

This is a binary file and will not be displayed.

+2
lib/tests/packages-from-directory/c/package.nix
···
+
{ }:
+
"c"
lib/tests/packages-from-directory/c/support-definitions.nix

This is a binary file and will not be displayed.

+2
lib/tests/packages-from-directory/my-namespace/d.nix
···
+
{ }:
+
"d"
+2
lib/tests/packages-from-directory/my-namespace/e.nix
···
+
{ }:
+
"e"
+2
lib/tests/packages-from-directory/my-namespace/f/package.nix
···
+
{ }:
+
"f"
+2
lib/tests/packages-from-directory/my-namespace/my-sub-namespace/g.nix
···
+
{ }:
+
"g"
+2
lib/tests/packages-from-directory/my-namespace/my-sub-namespace/h.nix
···
+
{ }:
+
"h"