2023-08-17 00:55:32 +02:00
{ lib }:
let
inherit ( import ./internal.nix { inherit lib ; } )
_coerce
2023-10-18 00:16:50 +02:00
_singleton
2023-09-13 23:29:28 +02:00
_coerceMany
2023-08-17 00:55:32 +02:00
_toSourceFilter
2023-10-18 00:16:50 +02:00
_fromSourceFilter
2023-09-13 23:29:28 +02:00
_unionMany
2023-09-21 01:01:45 +02:00
_printFileset
2023-09-26 02:10:46 +02:00
_intersection
2023-08-17 00:55:32 +02:00
;
inherit ( builtins )
2023-09-13 23:29:57 +02:00
isList
2023-08-17 00:55:32 +02:00
isPath
pathExists
2023-09-21 01:01:45 +02:00
seq
2023-08-17 00:55:32 +02:00
typeOf
;
2023-09-13 23:29:57 +02:00
inherit ( lib . lists )
2023-09-26 02:10:46 +02:00
elemAt
2023-09-13 23:29:57 +02:00
imap0
;
2023-08-17 00:55:32 +02:00
inherit ( lib . path )
hasPrefix
splitRoot
;
inherit ( lib . strings )
isStringLike
;
inherit ( lib . filesystem )
pathType
;
inherit ( lib . sources )
cleanSourceWith
;
2023-09-15 00:08:04 +02:00
inherit ( lib . trivial )
pipe
;
2023-08-17 00:55:32 +02:00
in {
/*
Add the local files contained in ` fileset ` to the store as a single [ store path ] ( https://nixos.org/manual/nix/stable/glossary #gloss-store-path) rooted at `root`.
The result is the store path as a string-like value , making it usable e . g . as the ` src ` of a derivation , or in string interpolation :
` ` ` nix
stdenv . mkDerivation {
src = lib . fileset . toSource { . . . } ;
# ...
}
` ` `
The name of the store path is always ` source ` .
Type :
toSource : : {
root : : Path ,
fileset : : FileSet ,
} -> SourceLike
Example :
2023-09-13 23:31:02 +02:00
# Import the current directory into the store
# but only include files under ./src
toSource {
root = ./. ;
fileset = ./src ;
}
2023-08-17 00:55:32 +02:00
= > " / n i x / s t o r e / . . . - s o u r c e "
2023-09-13 23:31:02 +02:00
# Import the current directory into the store
# but only include ./Makefile and all files under ./src
toSource {
root = ./. ;
fileset = union
./Makefile
./src ;
}
= > " / n i x / s t o r e / . . . - s o u r c e "
# Trying to include a file outside the root will fail
toSource {
root = ./. ;
fileset = unions [
./Makefile
./src
../LICENSE
] ;
}
2023-08-17 00:55:32 +02:00
= > <error>
2023-09-13 23:31:02 +02:00
# The root needs to point to a directory that contains all the files
toSource {
root = ../. ;
fileset = unions [
./Makefile
./src
../LICENSE
] ;
}
= > " / n i x / s t o r e / . . . - s o u r c e "
2023-08-17 00:55:32 +02:00
# The root has to be a local filesystem path
2023-09-13 23:31:02 +02:00
toSource {
root = " / n i x / s t o r e / . . . - s o u r c e " ;
fileset = ./. ;
}
2023-08-17 00:55:32 +02:00
= > <error>
* /
toSource = {
/*
( required ) The local directory [ path ] ( https://nixos.org/manual/nix/stable/language/values.html #type-path) that will correspond to the root of the resulting store path.
Paths in [ strings ] ( https://nixos.org/manual/nix/stable/language/values.html #type-string), including Nix store paths, cannot be passed as `root`.
` root ` has to be a directory .
2023-09-15 00:08:04 +02:00
< ! - - Ignore the indentation here , this is a nixdoc rendering bug that needs to be fixed : https://github.com/nix-community/nixdoc/issues/75 - ->
2023-08-17 00:55:32 +02:00
: : : { . note }
Changing ` root ` only affects the directory structure of the resulting store path , it does not change which files are added to the store .
The only way to change which files get added to the store is by changing the ` fileset ` attribute .
: : :
* /
root ,
/*
( required ) The file set whose files to import into the store .
2023-09-13 23:31:02 +02:00
File sets can be created using other functions in this library .
This argument can also be a path ,
which gets [ implicitly coerced to a file set ] ( #sec-fileset-path-coercion).
2023-09-15 00:08:04 +02:00
< ! - - Ignore the indentation here , this is a nixdoc rendering bug that needs to be fixed : https://github.com/nix-community/nixdoc/issues/75 - ->
2023-09-13 23:31:02 +02:00
: : : { . note }
If a directory does not recursively contain any file , it is omitted from the store path contents .
: : :
2023-08-17 00:55:32 +02:00
* /
fileset ,
} :
let
# We cannot rename matched attribute arguments, so let's work around it with an extra `let in` statement
2023-09-15 00:08:04 +02:00
filesetArg = fileset ;
2023-08-17 00:55:32 +02:00
in
let
2023-09-15 00:08:04 +02:00
fileset = _coerce " l i b . f i l e s e t . t o S o u r c e : ` f i l e s e t ` " filesetArg ;
2023-08-17 00:55:32 +02:00
rootFilesystemRoot = ( splitRoot root ) . root ;
filesetFilesystemRoot = ( splitRoot fileset . _internalBase ) . root ;
2023-09-15 00:08:04 +02:00
sourceFilter = _toSourceFilter fileset ;
2023-08-17 00:55:32 +02:00
in
if ! isPath root then
if isStringLike root then
throw ''
2023-09-15 00:08:04 +02:00
lib . fileset . toSource : ` root ` ( " ${ toString root } " ) is a string-like value , but it should be a path instead .
2023-08-17 00:55:32 +02:00
Paths in strings are not supported by ` lib . fileset ` , use ` lib . sources ` or derivations instead . ''
else
throw ''
lib . fileset . toSource : ` root ` is of type $ { typeOf root } , but it should be a path instead . ''
# Currently all Nix paths have the same filesystem root, but this could change in the future.
# See also ../path/README.md
2023-09-26 00:26:00 +02:00
else if ! fileset . _internalIsEmptyWithoutBase && rootFilesystemRoot != filesetFilesystemRoot then
2023-08-17 00:55:32 +02:00
throw ''
2023-09-15 00:08:04 +02:00
lib . fileset . toSource : Filesystem roots are not the same for ` fileset ` and ` root ` ( " ${ toString root } " ) :
2023-08-17 00:55:32 +02:00
` root ` : root " ${ toString rootFilesystemRoot } "
` fileset ` : root " ${ toString filesetFilesystemRoot } "
Different roots are not supported . ''
else if ! pathExists root then
throw ''
2023-09-15 00:08:04 +02:00
lib . fileset . toSource : ` root ` ( $ { toString root } ) does not exist . ''
2023-08-17 00:55:32 +02:00
else if pathType root != " d i r e c t o r y " then
throw ''
2023-09-15 00:08:04 +02:00
lib . fileset . toSource : ` root ` ( $ { toString root } ) is a file , but it should be a directory instead . Potential solutions :
2023-08-17 00:55:32 +02:00
- If you want to import the file into the store _without_ a containing directory , use string interpolation or ` builtins . path ` instead of this function .
- If you want to import the file into the store _with_ a containing directory , set ` root ` to the containing directory , such as $ { toString ( dirOf root ) } , and set ` fileset ` to the file path . ''
2023-09-26 00:26:00 +02:00
else if ! fileset . _internalIsEmptyWithoutBase && ! hasPrefix root fileset . _internalBase then
2023-08-17 00:55:32 +02:00
throw ''
2023-09-15 00:08:04 +02:00
lib . fileset . toSource : ` fileset ` could contain files in $ { toString fileset . _internalBase } , which is not under the ` root ` ( $ { toString root } ) . Potential solutions :
2023-08-17 00:55:32 +02:00
- Set ` root ` to $ { toString fileset . _internalBase } or any directory higher up . This changes the layout of the resulting store path .
2023-09-15 00:08:04 +02:00
- Set ` fileset ` to a file set that cannot contain files outside the ` root ` ( $ { toString root } ) . This could change the files included in the result . ''
2023-08-17 00:55:32 +02:00
else
2023-09-15 00:08:04 +02:00
builtins . seq sourceFilter
2023-08-17 00:55:32 +02:00
cleanSourceWith {
name = " s o u r c e " ;
src = root ;
2023-09-15 00:08:04 +02:00
filter = sourceFilter ;
2023-08-17 00:55:32 +02:00
} ;
2023-09-13 23:29:28 +02:00
2023-10-18 00:16:50 +02:00
/*
Create a file set with the same files as a ` lib . sources ` - based value .
This does not import any of the files into the store .
This can be used to gradually migrate from ` lib . sources ` - based filtering to ` lib . fileset ` .
A file set can be turned back into a source using [ ` toSource ` ] ( #function-library-lib.fileset.toSource).
: : : { . note }
File sets cannot represent empty directories .
Turning the result of this function back into a source using ` toSource ` will therefore not preserve empty directories .
: : :
Type :
fromSource : : SourceLike -> FileSet
Example :
# There's no cleanSource-like function for file sets yet,
# but we can just convert cleanSource to a file set and use it that way
toSource {
root = ./. ;
fileset = fromSource ( lib . sources . cleanSource ./. ) ;
}
# Keeping a previous sourceByRegex (which could be migrated to `lib.fileset.unions`),
# but removing a subdirectory using file set functions
difference
( fromSource ( lib . sources . sourceByRegex ./. [
" ^ R E A D M E \. m d $ "
# This regex includes everything in ./doc
" ^ d o c ( / . * ) ? $ "
] )
./doc/generated
# Use cleanSource, but limit it to only include ./Makefile and files under ./src
intersection
( fromSource ( lib . sources . cleanSource ./. ) )
( unions [
./Makefile
./src
] ) ;
* /
fromSource = source :
let
# This function uses `._isLibCleanSourceWith`, `.origSrc` and `.filter`,
# which are technically internal to lib.sources,
# but we'll allow this since both libraries are in the same code base
# and this function is a bridge between them.
isFiltered = source ? _isLibCleanSourceWith ;
path = if isFiltered then source . origSrc else source ;
in
# We can only support sources created from paths
if ! isPath path then
if isStringLike path then
throw ''
lib . fileset . fromSource : The source origin of the argument is a string-like value ( " ${ toString path } " ) , but it should be a path instead .
Sources created from paths in strings cannot be turned into file sets , use ` lib . sources ` or derivations instead . ''
else
throw ''
lib . fileset . fromSource : The source origin of the argument is of type $ { typeOf path } , but it should be a path instead . ''
else if ! pathExists path then
throw ''
lib . fileset . fromSource : The source origin ( $ { toString path } ) of the argument does not exist . ''
else if isFiltered then
_fromSourceFilter path source . filter
else
# If there's no filter, no need to run the expensive conversion, all subpaths will be included
_singleton path ;
2023-09-13 23:29:28 +02:00
/*
The file set containing all files that are in either of two given file sets .
2023-09-13 23:29:57 +02:00
This is the same as [ ` unions ` ] ( #function-library-lib.fileset.unions),
but takes just two file sets instead of a list .
2023-09-13 23:29:28 +02:00
See also [ Union ( set theory ) ] ( https://en.wikipedia.org/wiki/Union_ ( set_theory ) ) .
The given file sets are evaluated as lazily as possible ,
with the first argument being evaluated first if needed .
Type :
union : : FileSet -> FileSet -> FileSet
Example :
# Create a file set containing the file `Makefile`
# and all files recursively in the `src` directory
union ./Makefile ./src
# Create a file set containing the file `Makefile`
# and the LICENSE file from the parent directory
union ./Makefile ../LICENSE
* /
union =
# The first file set.
# This argument can also be a path,
# which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
fileset1 :
# The second file set.
# This argument can also be a path,
# which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
fileset2 :
2023-09-15 00:08:04 +02:00
_unionMany
( _coerceMany " l i b . f i l e s e t . u n i o n " [
2023-09-13 23:29:28 +02:00
{
context = " f i r s t a r g u m e n t " ;
value = fileset1 ;
}
{
context = " s e c o n d a r g u m e n t " ;
value = fileset2 ;
}
2023-09-15 00:08:04 +02:00
] ) ;
2023-09-13 23:29:28 +02:00
2023-09-13 23:29:57 +02:00
/*
The file set containing all files that are in any of the given file sets .
This is the same as [ ` union ` ] ( #function-library-lib.fileset.unions),
but takes a list of file sets instead of just two .
See also [ Union ( set theory ) ] ( https://en.wikipedia.org/wiki/Union_ ( set_theory ) ) .
The given file sets are evaluated as lazily as possible ,
with earlier elements being evaluated first if needed .
Type :
unions : : [ FileSet ] -> FileSet
Example :
# Create a file set containing selected files
unions [
# Include the single file `Makefile` in the current directory
# This errors if the file doesn't exist
./Makefile
# Recursively include all files in the `src/code` directory
# If this directory is empty this has no effect
./src/code
# Include the files `run.sh` and `unit.c` from the `tests` directory
./tests/run.sh
./tests/unit.c
# Include the `LICENSE` file from the parent directory
../LICENSE
]
* /
unions =
# A list of file sets.
# The elements can also be paths,
# which get [implicitly coerced to file sets](#sec-fileset-path-coercion).
filesets :
2023-09-15 00:08:04 +02:00
if ! isList filesets then
throw " l i b . f i l e s e t . u n i o n s : E x p e c t e d a r g u m e n t t o b e a l i s t , b u t g o t a ${ typeOf filesets } . "
2023-09-13 23:29:57 +02:00
else
2023-09-15 00:08:04 +02:00
pipe filesets [
# Annotate the elements with context, used by _coerceMany for better errors
( imap0 ( i : el : {
context = " e l e m e n t ${ toString i } " ;
value = el ;
} ) )
( _coerceMany " l i b . f i l e s e t . u n i o n s " )
_unionMany
] ;
2023-09-13 23:29:57 +02:00
2023-09-26 02:10:46 +02:00
/*
The file set containing all files that are in both of two given file sets .
See also [ Intersection ( set theory ) ] ( https://en.wikipedia.org/wiki/Intersection_ ( set_theory ) ) .
The given file sets are evaluated as lazily as possible ,
with the first argument being evaluated first if needed .
Type :
intersection : : FileSet -> FileSet -> FileSet
Example :
# Limit the selected files to the ones in ./., so only ./src and ./Makefile
intersection ./. ( unions [ ../LICENSE ./src ./Makefile ] )
* /
intersection =
# The first file set.
# This argument can also be a path,
# which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
fileset1 :
# The second file set.
# This argument can also be a path,
# which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
fileset2 :
let
filesets = _coerceMany " l i b . f i l e s e t . i n t e r s e c t i o n " [
{
context = " f i r s t a r g u m e n t " ;
value = fileset1 ;
}
{
context = " s e c o n d a r g u m e n t " ;
value = fileset2 ;
}
] ;
in
_intersection
( elemAt filesets 0 )
( elemAt filesets 1 ) ;
2023-09-21 01:01:45 +02:00
/*
Incrementally evaluate and trace a file set in a pretty way .
This function is only intended for debugging purposes .
The exact tracing format is unspecified and may change .
2023-09-21 01:43:39 +02:00
This function takes a final argument to return .
In comparison , [ ` traceVal ` ] ( #function-library-lib.fileset.traceVal) returns
the given file set argument .
This variant is useful for tracing file sets in the Nix repl .
2023-09-21 01:01:45 +02:00
Type :
trace : : FileSet -> Any -> Any
Example :
trace ( unions [ ./Makefile ./src ./tests/run.sh ] ) null
= >
trace : /home/user/src/myProject
trace : - Makefile ( regular )
trace : - src ( all files in directory )
trace : - tests
trace : - run . sh ( regular )
null
* /
trace =
/*
The file set to trace .
This argument can also be a path ,
which gets [ implicitly coerced to a file set ] ( #sec-fileset-path-coercion).
* /
fileset :
let
# "fileset" would be a better name, but that would clash with the argument name,
# and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
actualFileset = _coerce " l i b . f i l e s e t . t r a c e : a r g u m e n t " fileset ;
in
seq
( _printFileset actualFileset )
( x : x ) ;
2023-09-21 01:43:39 +02:00
/*
Incrementally evaluate and trace a file set in a pretty way .
This function is only intended for debugging purposes .
The exact tracing format is unspecified and may change .
This function returns the given file set .
In comparison , [ ` trace ` ] ( #function-library-lib.fileset.trace) takes another argument to return.
This variant is useful for tracing file sets passed as arguments to other functions .
Type :
traceVal : : FileSet -> FileSet
Example :
toSource {
root = ./. ;
fileset = traceVal ( unions [
./Makefile
./src
./tests/run.sh
] ) ;
}
= >
trace : /home/user/src/myProject
trace : - Makefile ( regular )
trace : - src ( all files in directory )
trace : - tests
trace : - run . sh ( regular )
" / n i x / s t o r e / . . . - s o u r c e "
* /
traceVal =
/*
The file set to trace and return .
This argument can also be a path ,
which gets [ implicitly coerced to a file set ] ( #sec-fileset-path-coercion).
* /
fileset :
let
# "fileset" would be a better name, but that would clash with the argument name,
# and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
actualFileset = _coerce " l i b . f i l e s e t . t r a c e V a l : a r g u m e n t " fileset ;
in
seq
( _printFileset actualFileset )
# We could also return the original fileset argument here,
# but that would then duplicate work for consumers of the fileset, because then they have to coerce it again
actualFileset ;
2023-08-17 00:55:32 +02:00
}