mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-11-09 08:08:34 +01:00
It was added in fcaa2b1097. Lix is based off of 2.18, and the minimum version for Nixpkgs has long since incorporated this builtin.
480 lines
12 KiB
Nix
480 lines
12 KiB
Nix
/**
|
|
Functions for querying information about the filesystem
|
|
without copying any files to the Nix store.
|
|
*/
|
|
{ lib }:
|
|
|
|
# Tested in lib/tests/filesystem.sh
|
|
let
|
|
inherit (builtins)
|
|
readDir
|
|
pathExists
|
|
toString
|
|
;
|
|
|
|
inherit (lib.filesystem)
|
|
pathIsDirectory
|
|
pathIsRegularFile
|
|
pathType
|
|
packagesFromDirectoryRecursive
|
|
;
|
|
|
|
inherit (lib.strings)
|
|
hasSuffix
|
|
;
|
|
in
|
|
|
|
{
|
|
|
|
/**
|
|
The type of a path. The path needs to exist and be accessible.
|
|
The result is either "directory" for a directory, "regular" for a regular file, "symlink" for a symlink, or "unknown" for anything else.
|
|
|
|
# Inputs
|
|
|
|
path
|
|
|
|
: The path to query
|
|
|
|
# Type
|
|
|
|
```
|
|
pathType :: Path -> String
|
|
```
|
|
|
|
# Examples
|
|
:::{.example}
|
|
## `lib.filesystem.pathType` usage example
|
|
|
|
```nix
|
|
pathType /.
|
|
=> "directory"
|
|
|
|
pathType /some/file.nix
|
|
=> "regular"
|
|
```
|
|
|
|
:::
|
|
*/
|
|
pathType = builtins.readFileType;
|
|
|
|
/**
|
|
Whether a path exists and is a directory.
|
|
|
|
# Inputs
|
|
|
|
`path`
|
|
|
|
: 1\. Function argument
|
|
|
|
# Type
|
|
|
|
```
|
|
pathIsDirectory :: Path -> Bool
|
|
```
|
|
|
|
# Examples
|
|
:::{.example}
|
|
## `lib.filesystem.pathIsDirectory` usage example
|
|
|
|
```nix
|
|
pathIsDirectory /.
|
|
=> true
|
|
|
|
pathIsDirectory /this/does/not/exist
|
|
=> false
|
|
|
|
pathIsDirectory /some/file.nix
|
|
=> false
|
|
```
|
|
|
|
:::
|
|
*/
|
|
pathIsDirectory = path: pathExists path && pathType path == "directory";
|
|
|
|
/**
|
|
Whether a path exists and is a regular file, meaning not a symlink or any other special file type.
|
|
|
|
# Inputs
|
|
|
|
`path`
|
|
|
|
: 1\. Function argument
|
|
|
|
# Type
|
|
|
|
```
|
|
pathIsRegularFile :: Path -> Bool
|
|
```
|
|
|
|
# Examples
|
|
:::{.example}
|
|
## `lib.filesystem.pathIsRegularFile` usage example
|
|
|
|
```nix
|
|
pathIsRegularFile /.
|
|
=> false
|
|
|
|
pathIsRegularFile /this/does/not/exist
|
|
=> false
|
|
|
|
pathIsRegularFile /some/file.nix
|
|
=> true
|
|
```
|
|
|
|
:::
|
|
*/
|
|
pathIsRegularFile = path: pathExists path && pathType path == "regular";
|
|
|
|
/**
|
|
A map of all haskell packages defined in the given path,
|
|
identified by having a cabal file with the same name as the
|
|
directory itself.
|
|
|
|
# Inputs
|
|
|
|
`root`
|
|
|
|
: The directory within to search
|
|
|
|
# Type
|
|
|
|
```
|
|
Path -> Map String Path
|
|
```
|
|
*/
|
|
haskellPathsInDir =
|
|
root:
|
|
let
|
|
# Files in the root
|
|
root-files = builtins.attrNames (builtins.readDir root);
|
|
# Files with their full paths
|
|
root-files-with-paths = map (file: {
|
|
name = file;
|
|
value = root + "/${file}";
|
|
}) root-files;
|
|
# Subdirectories of the root with a cabal file.
|
|
cabal-subdirs = builtins.filter (
|
|
{ name, value }: builtins.pathExists (value + "/${name}.cabal")
|
|
) root-files-with-paths;
|
|
in
|
|
builtins.listToAttrs cabal-subdirs;
|
|
/**
|
|
Find the first directory containing a file matching 'pattern'
|
|
upward from a given 'file'.
|
|
Returns 'null' if no directories contain a file matching 'pattern'.
|
|
|
|
# Inputs
|
|
|
|
`pattern`
|
|
|
|
: The pattern to search for
|
|
|
|
`file`
|
|
|
|
: The file to start searching upward from
|
|
|
|
# Type
|
|
|
|
```
|
|
RegExp -> Path -> Nullable { path : Path; matches : [ MatchResults ]; }
|
|
```
|
|
*/
|
|
locateDominatingFile =
|
|
pattern: file:
|
|
let
|
|
go =
|
|
path:
|
|
let
|
|
files = builtins.attrNames (builtins.readDir path);
|
|
matches = builtins.filter (match: match != null) (map (builtins.match pattern) files);
|
|
in
|
|
if builtins.length matches != 0 then
|
|
{ inherit path matches; }
|
|
else if path == /. then
|
|
null
|
|
else
|
|
go (dirOf path);
|
|
parent = dirOf file;
|
|
isDir =
|
|
let
|
|
base = baseNameOf file;
|
|
type = (builtins.readDir parent).${base} or null;
|
|
in
|
|
file == /. || type == "directory";
|
|
in
|
|
go (if isDir then file else parent);
|
|
|
|
/**
|
|
Given a directory, return a flattened list of all files within it recursively.
|
|
|
|
# Inputs
|
|
|
|
`dir`
|
|
|
|
: The path to recursively list
|
|
|
|
# Type
|
|
|
|
```
|
|
Path -> [ Path ]
|
|
```
|
|
*/
|
|
listFilesRecursive =
|
|
let
|
|
# We only flatten at the very end, as flatten is recursive.
|
|
internalFunc =
|
|
dir:
|
|
(lib.mapAttrsToList (
|
|
name: type: if type == "directory" then internalFunc (dir + "/${name}") else dir + "/${name}"
|
|
) (builtins.readDir dir));
|
|
in
|
|
dir: lib.flatten (internalFunc 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 regular file's 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> { }`
|
|
- 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.
|
|
- Other files are ignored, including symbolic links to directories and to regular `.nix`
|
|
files; this is because nixlang code cannot distinguish the type of a link's target.
|
|
|
|
# Type
|
|
|
|
```
|
|
packagesFromDirectoryRecursive :: {
|
|
callPackage :: Path -> {} -> a,
|
|
newScope? :: AttrSet -> scope,
|
|
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 either `pkgs` or a new scope corresponding to the `directory`.
|
|
|
|
`newScope`
|
|
: If present, this function is used when recursing into a directory, 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.
|
|
|
|
`directory`
|
|
: The directory to read package files from.
|
|
|
|
# Examples
|
|
:::{.example}
|
|
## Basic use of `lib.packagesFromDirectoryRecursive`
|
|
|
|
```nix
|
|
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.
|
|
:::
|
|
|
|
::::{.example}
|
|
## Create a scope for the nix files found in a directory
|
|
```nix
|
|
packagesFromDirectoryRecursive {
|
|
inherit (pkgs) callPackage newScope;
|
|
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.
|
|
|
|
:::{.note}
|
|
`a.nix` cannot directly take as inputs packages defined in a child directory, such as `b1`.
|
|
:::
|
|
::::
|
|
*/
|
|
packagesFromDirectoryRecursive =
|
|
let
|
|
inherit (lib)
|
|
concatMapAttrs
|
|
id
|
|
makeScope
|
|
recurseIntoAttrs
|
|
removeSuffix
|
|
;
|
|
|
|
# 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 = 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,
|
|
}@args:
|
|
let
|
|
defaultPath = directory + "/package.nix";
|
|
in
|
|
if pathExists defaultPath then
|
|
# if `${directory}/package.nix` exists, call it directly
|
|
callPackage defaultPath { }
|
|
else if args ? newScope then
|
|
# 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
|
|
processDir args;
|
|
|
|
/**
|
|
Append `/default.nix` if the passed path is a directory.
|
|
|
|
# Type
|
|
|
|
```
|
|
resolveDefaultNix :: (Path | String) -> (Path | String)
|
|
```
|
|
|
|
# Inputs
|
|
|
|
A single argument which can be a [path](https://nix.dev/manual/nix/stable/language/types#type-path) value or a string containing an absolute path.
|
|
|
|
# Output
|
|
|
|
If the input refers to a directory that exists, the output is that same path with `/default.nix` appended.
|
|
Furthermore, if the input is a string that ends with `/`, `default.nix` is appended to it.
|
|
Otherwise, the input is returned unchanged.
|
|
|
|
# Examples
|
|
:::{.example}
|
|
## `lib.filesystem.resolveDefaultNix` usage example
|
|
|
|
This expression checks whether `a` and `b` refer to the same locally available Nix file path.
|
|
|
|
```nix
|
|
resolveDefaultNix a == resolveDefaultNix b
|
|
```
|
|
|
|
For instance, if `a` is `/some/dir` and `b` is `/some/dir/default.nix`, and `/some/dir/` exists, the expression evaluates to `true`, despite `a` and `b` being different references to the same Nix file.
|
|
*/
|
|
resolveDefaultNix =
|
|
v:
|
|
if pathIsDirectory v then
|
|
v + "/default.nix"
|
|
else if lib.isString v && hasSuffix "/" v then
|
|
# A path ending in `/` can only refer to a directory, so we take the hint, even if we can't verify the validity of the path's `/` assertion.
|
|
# A `/` is already present, so we don't add another one.
|
|
v + "default.nix"
|
|
else
|
|
v;
|
|
}
|