Have you ever tried to filter a derivation source using functions like builtins.path
or lib.cleanSourceWith
? If so, you probably already wrote your own helper function to make it easier, because it’s really hard to get it right!
Sponsored by Antithesis , I’ve been developing a new approach to filter local sources, with the goal of making it easier, safer and more flexible to do so.
With this post I’m asking for feedback to figure out whether the interface of the current draft achieves those goals while satisfying the necessary use cases, or whether some changes are still necessary. To that end I’ll give a brief introduction here and show you how you can try it out.
This idea is implemented here: [WIP] File set combinators by infinisil · Pull Request #222981 · NixOS/nixpkgs · GitHub
Update: The file set library is mostly implemented and usable now, notably with minor changes compared to this post here! See File set library tracking issue and feature requests · Issue #266356 · NixOS/nixpkgs · GitHub for status, updates and feature requests
Trying it out
The PR can be loaded into a nix repl
as follows. We also set fs = lib.fileset
for convenience.
nix repl -f https://github.com/tweag/nixpkgs/tarball/file-sets
Welcome to Nix 2.15.1. Type :? for help.
Loading installable ''...
Added 19162 variables.
nix-repl> fs = lib.fileset
Flakes
To temporarily override the default nixpkgs
input in your flake.nix
:
nix build --override-input nixpkgs github:tweag/nixpkgs/file-sets
I then recommend also defining fs = inputs.nixpkgs.lib.fileset
for convenience.
Overview
The PR implements a file set abstraction, which as you might expect, allows representing sets of files. Common set operations are supported, including:
-
Union:
fs.union a b
/fs.unions [ ... ]
-
Intersection:
fs.intersect a b
/fs.intersects [ ... ]
-
Difference:
fs.difference a b
- Filtering:
fs.fileFilter predicate a
Examples:
let
# The file ./Makefile and recursively all files in the ./src directory
a = fs.union ./Makefile ./src;
# Recursively all files in the ./. directory that are not in the ./tests directory
b = fs.difference ./. ./tests;
# Recurlively all Nix files in the ./. directory
c = fs.fileFilter (file: file.ext == "nix") ./.;
# Recursively all Nix files in the ./src directory
d = fs.intersect ./src c;
in null
To see which files are included in a file set, you can use fs.trace
:
nix-repl> fs.trace {} (fs.union ./Makefile ./src) null
trace: /home/user/my/project
trace: - Makefile (regular)
trace: - src (recursive directory)
null
Notably none of these operations actually import these files into the Nix store!
Instead the only way to get the files to be imported, and therefore usable in derivations, is to use the toSource
function:
# Can be used as the `src =` of a derivation
fs.toSource {
root = ./.;
fileset = fs.unions [
./Makefile
(fs.fileFilter (file: file.ext == "c") ./src)
];
}
These are some of the core functions, but more are available. The best way to explore them is to build the manual locally and open the lib.fileset
reference section in your browser:
nix-build '<nixpkgs/doc>' -I nixpkgs=https://github.com/tweag/nixpkgs/tarball/file-sets
firefox result/share/doc/nixpkgs/manual.html#sec-functions-library-fileset
Goals, limitations and alternatives
The goal of this abstraction is to be able to precisely specify which files should have an effect on your derivation builds. Doing this should be straightforward, with obvious semantics, explanatory error messages and good performance.
In order to achieve this, some limitations are imposed:
-
Only local files at evaluation time are supported. Files in Nix store paths are not supported.
Rationale: The path expression-based interface would be hard to use; might require IFD; without CA, original files would still be imported.
Alternative: Use build-time tools to create a new derivation with the desired layout. -
Empty directories cannot be represented.
Rationale: It’s not obvious what the semantics should be if this were allowed, it couldn’t be explained as set operations anymore.
Alternative:fs.toSource
supports anextraExistingDirs
argument which can be used to ensure certain directories exist in the resulting Nix store path.
File sets are intended as a replacement for builtins.path
-based filtering and the lib.sources
functions.
In contrast, file sets are not a replacement for functions like pkgs.nix-gitignore
, gitignore.nix, Flakes’ tracked-by-git filtering or fetchGit
.
However, file sets can serve as a more performant and composable foundation to implement such functions on top of.
If this is something you could benefit from, please give it a try and use this thread for any questions or feedback about the interface!
For the implementation, see the draft pull request.