lib.types: introduce a fileset type (#428293)
This commit is contained in:
@@ -101,6 +101,7 @@ let
|
|||||||
|
|
||||||
inherit (import ./internal.nix { inherit lib; })
|
inherit (import ./internal.nix { inherit lib; })
|
||||||
_coerce
|
_coerce
|
||||||
|
_coerceResult
|
||||||
_singleton
|
_singleton
|
||||||
_coerceMany
|
_coerceMany
|
||||||
_toSourceFilter
|
_toSourceFilter
|
||||||
@@ -1005,4 +1006,49 @@ in
|
|||||||
{
|
{
|
||||||
submodules = recurseSubmodules;
|
submodules = recurseSubmodules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
The empty fileset. It can be useful as a default value or as starting accumulator for a folding operation.
|
||||||
|
|
||||||
|
# Type
|
||||||
|
|
||||||
|
```
|
||||||
|
empty :: FileSet
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
empty = _emptyWithoutBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Tests whether a given value is a fileset, or can be used in place of a fileset.
|
||||||
|
|
||||||
|
# Inputs
|
||||||
|
|
||||||
|
`value`
|
||||||
|
|
||||||
|
: The value to test
|
||||||
|
|
||||||
|
# Type
|
||||||
|
|
||||||
|
```
|
||||||
|
isFileset :: Any -> Bool
|
||||||
|
```
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
:::{.example}
|
||||||
|
## `lib.fileset.isFileset` usage example
|
||||||
|
|
||||||
|
```nix
|
||||||
|
isFileset ./.
|
||||||
|
=> true
|
||||||
|
|
||||||
|
isFileset (unions [ ])
|
||||||
|
=> true
|
||||||
|
|
||||||
|
isFileset 1
|
||||||
|
=> false
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
*/
|
||||||
|
isFileset = x: (_coerceResult "" x).success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,14 +165,27 @@ rec {
|
|||||||
_noEval = throw _noEvalMessage;
|
_noEval = throw _noEvalMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Coerce a value to a fileset, erroring when the value cannot be coerced.
|
# Coerce a value to a fileset. Return a set containing the attribute `success`
|
||||||
# The string gives the context for error messages.
|
# indicating whether coercing succeeded, and either `value` when `success ==
|
||||||
# Type: String -> (fileset | Path) -> fileset
|
# true`, or an error `message` when `success == false`. The string gives the
|
||||||
_coerce =
|
# context for error messages.
|
||||||
|
#
|
||||||
|
# Type: String -> (fileset | Path) -> { success :: Bool, value :: fileset } ] -> { success :: Bool, message :: String }
|
||||||
|
_coerceResult =
|
||||||
|
let
|
||||||
|
ok = value: {
|
||||||
|
success = true;
|
||||||
|
inherit value;
|
||||||
|
};
|
||||||
|
error = message: {
|
||||||
|
success = false;
|
||||||
|
inherit message;
|
||||||
|
};
|
||||||
|
in
|
||||||
context: value:
|
context: value:
|
||||||
if value._type or "" == "fileset" then
|
if value._type or "" == "fileset" then
|
||||||
if value._internalVersion > _currentVersion then
|
if value._internalVersion > _currentVersion then
|
||||||
throw ''
|
error ''
|
||||||
${context} is a file set created from a future version of the file set library with a different internal representation:
|
${context} is a file set created from a future version of the file set library with a different internal representation:
|
||||||
- Internal version of the file set: ${toString value._internalVersion}
|
- Internal version of the file set: ${toString value._internalVersion}
|
||||||
- Internal version of the library: ${toString _currentVersion}
|
- Internal version of the library: ${toString _currentVersion}
|
||||||
@@ -184,27 +197,37 @@ rec {
|
|||||||
_currentVersion - value._internalVersion
|
_currentVersion - value._internalVersion
|
||||||
) migrations;
|
) migrations;
|
||||||
in
|
in
|
||||||
foldl' (value: migration: migration value) value migrationsToApply
|
ok (foldl' (value: migration: migration value) value migrationsToApply)
|
||||||
else
|
else
|
||||||
value
|
ok value
|
||||||
else if !isPath value then
|
else if !isPath value then
|
||||||
if value ? _isLibCleanSourceWith then
|
if value ? _isLibCleanSourceWith then
|
||||||
throw ''
|
error ''
|
||||||
${context} is a `lib.sources`-based value, but it should be a file set or a path instead.
|
${context} is a `lib.sources`-based value, but it should be a file set or a path instead.
|
||||||
To convert a `lib.sources`-based value to a file set you can use `lib.fileset.fromSource`.
|
To convert a `lib.sources`-based value to a file set you can use `lib.fileset.fromSource`.
|
||||||
Note that this only works for sources created from paths.''
|
Note that this only works for sources created from paths.''
|
||||||
else if isStringLike value then
|
else if isStringLike value then
|
||||||
throw ''
|
error ''
|
||||||
${context} ("${toString value}") is a string-like value, but it should be a file set or a path instead.
|
${context} ("${toString value}") is a string-like value, but it should be a file set or a path instead.
|
||||||
Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''
|
Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''
|
||||||
else
|
else
|
||||||
throw ''${context} is of type ${typeOf value}, but it should be a file set or a path instead.''
|
error ''${context} is of type ${typeOf value}, but it should be a file set or a path instead.''
|
||||||
else if !pathExists value then
|
else if !pathExists value then
|
||||||
throw ''
|
error ''
|
||||||
${context} (${toString value}) is a path that does not exist.
|
${context} (${toString value}) is a path that does not exist.
|
||||||
To create a file set from a path that may not exist, use `lib.fileset.maybeMissing`.''
|
To create a file set from a path that may not exist, use `lib.fileset.maybeMissing`.''
|
||||||
else
|
else
|
||||||
_singleton value;
|
ok (_singleton value);
|
||||||
|
|
||||||
|
# Coerce a value to a fileset, erroring when the value cannot be coerced.
|
||||||
|
# The string gives the context for error messages.
|
||||||
|
# Type: String -> (fileset | Path) -> fileset
|
||||||
|
_coerce =
|
||||||
|
context: value:
|
||||||
|
let
|
||||||
|
result = _coerceResult context value;
|
||||||
|
in
|
||||||
|
if result.success then result.value else throw result.message;
|
||||||
|
|
||||||
# Coerce many values to filesets, erroring when any value cannot be coerced,
|
# Coerce many values to filesets, erroring when any value cannot be coerced,
|
||||||
# or if the filesystem root of the values doesn't match.
|
# or if the filesystem root of the values doesn't match.
|
||||||
|
|||||||
@@ -223,6 +223,17 @@ checkConfigError 'A definition for option .* is not of type .path in the Nix sto
|
|||||||
checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: ".*/store/.links"' config.pathInStore.bad4 ./types.nix
|
checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: ".*/store/.links"' config.pathInStore.bad4 ./types.nix
|
||||||
checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: "/foo/bar"' config.pathInStore.bad5 ./types.nix
|
checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: "/foo/bar"' config.pathInStore.bad5 ./types.nix
|
||||||
|
|
||||||
|
# types.fileset
|
||||||
|
checkConfigOutput '^0$' config.filesetCardinal.ok1 ./fileset.nix
|
||||||
|
checkConfigOutput '^1$' config.filesetCardinal.ok2 ./fileset.nix
|
||||||
|
checkConfigOutput '^1$' config.filesetCardinal.ok3 ./fileset.nix
|
||||||
|
checkConfigOutput '^1$' config.filesetCardinal.ok4 ./fileset.nix
|
||||||
|
checkConfigOutput '^0$' config.filesetCardinal.ok5 ./fileset.nix
|
||||||
|
checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err1 ./fileset.nix
|
||||||
|
checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err2 ./fileset.nix
|
||||||
|
checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err3 ./fileset.nix
|
||||||
|
checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err4 ./fileset.nix
|
||||||
|
|
||||||
# Check boolean option.
|
# Check boolean option.
|
||||||
checkConfigOutput '^false$' config.enable ./declare-enable.nix
|
checkConfigOutput '^false$' config.enable ./declare-enable.nix
|
||||||
checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
|
checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
|
||||||
|
|||||||
50
lib/tests/modules/fileset.nix
Normal file
50
lib/tests/modules/fileset.nix
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
mkOption
|
||||||
|
mkIf
|
||||||
|
types
|
||||||
|
mapAttrs
|
||||||
|
length
|
||||||
|
;
|
||||||
|
inherit (lib.fileset)
|
||||||
|
empty
|
||||||
|
unions
|
||||||
|
toList
|
||||||
|
;
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
fileset = mkOption { type = with types; lazyAttrsOf fileset; };
|
||||||
|
|
||||||
|
## The following option is only here as a proxy to test `fileset` that does
|
||||||
|
## not work so well with `modules.sh` because it is not JSONable. It exposes
|
||||||
|
## the number of elements in the fileset.
|
||||||
|
filesetCardinal = mkOption { default = mapAttrs (_: fs: length (toList fs)) config.fileset; };
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
fileset.ok1 = empty;
|
||||||
|
fileset.ok2 = ./fileset;
|
||||||
|
fileset.ok3 = unions [
|
||||||
|
empty
|
||||||
|
./fileset
|
||||||
|
];
|
||||||
|
# fileset.ok4: see imports below
|
||||||
|
fileset.ok5 = mkIf false ./fileset;
|
||||||
|
|
||||||
|
fileset.err1 = 1;
|
||||||
|
fileset.err2 = "foo";
|
||||||
|
fileset.err3 = "./.";
|
||||||
|
fileset.err4 = [ empty ];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
{ fileset.ok4 = ./fileset; }
|
||||||
|
{ fileset.ok4 = empty; }
|
||||||
|
{ fileset.ok4 = ./fileset; }
|
||||||
|
];
|
||||||
|
}
|
||||||
1
lib/tests/modules/fileset/keepme
Normal file
1
lib/tests/modules/fileset/keepme
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Do not remove. This file is used by the tests in `../fileset.nix`.
|
||||||
@@ -66,6 +66,11 @@ let
|
|||||||
fixupOptionType
|
fixupOptionType
|
||||||
mergeOptionDecls
|
mergeOptionDecls
|
||||||
;
|
;
|
||||||
|
inherit (lib.fileset)
|
||||||
|
isFileset
|
||||||
|
unions
|
||||||
|
empty
|
||||||
|
;
|
||||||
|
|
||||||
inAttrPosSuffix =
|
inAttrPosSuffix =
|
||||||
v: name:
|
v: name:
|
||||||
@@ -618,6 +623,15 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fileset = mkOptionType {
|
||||||
|
name = "fileset";
|
||||||
|
description = "fileset";
|
||||||
|
descriptionClass = "noun";
|
||||||
|
check = isFileset;
|
||||||
|
merge = loc: defs: unions (map (x: x.value) defs);
|
||||||
|
emptyValue.value = empty;
|
||||||
|
};
|
||||||
|
|
||||||
# A package is a top-level store path (/nix/store/hash-name). This includes:
|
# A package is a top-level store path (/nix/store/hash-name). This includes:
|
||||||
# - derivations
|
# - derivations
|
||||||
# - more generally, attribute sets with an `outPath` or `__toString` attribute
|
# - more generally, attribute sets with an `outPath` or `__toString` attribute
|
||||||
|
|||||||
Reference in New Issue
Block a user