Skip to content
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: init #270537

Merged
merged 1 commit into from
Dec 19, 2023
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
3 changes: 2 additions & 1 deletion lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ let
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
Expand Down
154 changes: 154 additions & 0 deletions lib/filesystem.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,22 @@ let
inherit (builtins)
readDir
pathExists
toString
;

inherit (lib.attrsets)
mapAttrs'
filterAttrs
;

inherit (lib.filesystem)
pathType
;

inherit (lib.strings)
hasSuffix
removeSuffix
;
in

{
Expand Down Expand Up @@ -154,4 +165,147 @@ 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.

nbraud marked this conversation as resolved.
Show resolved Hide resolved
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}
'';
nbraud marked this conversation as resolved.
Show resolved Hide resolved

# 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 changes: 33 additions & 0 deletions lib/tests/misc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1988,4 +1988,37 @@ runTests {
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;
9999years marked this conversation as resolved.
Show resolved Hide resolved
directory = ./packages-from-directory/c;
};
expected = "c";
};
}
2 changes: 2 additions & 0 deletions lib/tests/packages-from-directory/a.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ }:
"a"
2 changes: 2 additions & 0 deletions lib/tests/packages-from-directory/b.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ }:
"b"
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions lib/tests/packages-from-directory/c/package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ }:
"c"
Empty file.
2 changes: 2 additions & 0 deletions lib/tests/packages-from-directory/my-namespace/d.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ }:
"d"
2 changes: 2 additions & 0 deletions lib/tests/packages-from-directory/my-namespace/e.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ }:
"e"
2 changes: 2 additions & 0 deletions lib/tests/packages-from-directory/my-namespace/f/package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ }:
"f"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ }:
"g"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ }:
"h"