22# Source file selection
33<!-- Note on title choice: While there's more uses outside of sources, it's by far the most prominent one -->
44
5+ <!-- TODO: Switch all mentions of unstable to stable once 23.11 is out -->
6+
57To build a local project in a Nix derivation, its source files must be accessible to the builder.
68But since the builder runs in an isolated environment (if the [ sandbox] ( https://nixos.org/manual/nix/stable/command-ref/conf-file.html#conf-sandbox ) is enabled),
79it won't have access to the local project files by default.
@@ -27,7 +29,6 @@ $ cd select
2729$ nix-shell -p niv --run "niv init --nixpkgs nixos/nixpkgs --nixpkgs-branch nixos-unstable"
2830```
2931
30- <!-- TODO: Switch to 23.11 once out -->
3132:::{note}
3233For now we're using the nixos-unstable channel, since no stable channel has all the features we need yet.
3334:::
4647 inherit system;
4748 };
4849in
49- pkgs.callPackage ./package.nix { }
50+ {
51+ package = pkgs.callPackage ./package.nix { };
52+ inherit pkgs;
53+ }
5054```
5155
5256In this tutorial we'll experiment with different ` package.nix ` contents, while keeping ` default.nix ` the same.
@@ -62,7 +66,7 @@ runCommand "hello" { } ''
6266
6367And try it out:
6468``` shell-session
65- $ nix-build
69+ $ nix-build -A package
6670this derivation will be built:
6771 /nix/store/kmf9sw8fn7ps3ndqs31hvqwsa35b8l3g-hello.drv
6872building '/nix/store/kmf9sw8fn7ps3ndqs31hvqwsa35b8l3g-hello.drv'...
@@ -76,7 +80,9 @@ but we'll omit that for the sake of the tutorial, since we only need the build l
7680This also makes it easier to build it again, since successful derivation builds would get cached.
7781From now on we'll also make build outputs a bit shorter for the sake of brevity.
7882
79- ## Builtin coercion of paths to strings
83+ ## Builtins
84+
85+ ### Coercion of paths to strings
8086
8187The easiest way to use local files in builds is using the built-in coercion of [ paths] ( https://nixos.org/manual/nix/stable/language/values.html#type-path ) to strings.
8288
@@ -134,7 +140,7 @@ The two main ways to coerce paths to strings are:
134140
135141These all do the same when built:
136142``` shell-session
137- $ nix-build
143+ $ nix-build -A package
138144building '/nix/store/9fi0khrkmqw5srjzjsfa0b05hf8div4c-file-coercion.drv'...
139145++ cat /nix/store/j5lwpnlfrngks3bpidfr5hcrhgq0fy78-string.txt
140146This is a string
@@ -149,7 +155,7 @@ $ nix-store --add $(pwd)/string.txt
149155/nix/store/j5lwpnlfrngks3bpidfr5hcrhgq0fy78-string.txt
150156```
151157
152- ## Built-in path coercion on directories
158+ ### Coercion of directory paths to strings
153159
154160This path coercion also works on directories the same as it does on files, let's try it out:
155161
@@ -165,7 +171,7 @@ runCommand "directory-coercion" {
165171
166172Running it gives us:
167173``` shell-session
168- $ nix-build
174+ $ nix-build -A package
169175building '/nix/store/6ybg4v48xy8azhrnfdccdmhd2gr938f5-directory-interpolation.drv'...
170176/nix/store/xdfchqpfx20ar9jil9kys99wc6hnm9zx-select
171177|-- default.nix
@@ -195,7 +201,7 @@ Setting `src` will copy the resulting store path into the build directory and ma
195201For the many commands that expect to be able to write to the current directory, this is great:
196202
197203``` shell-session
198- $ nix-build
204+ $ nix-build -A package
199205building '/nix/store/2cqd93fpnb4vqwkwmbl66dbxhndq1vhh-directory-coercion.drv'...
200206unpacking sources
201207unpacking source archive /nix/store/178fbwa8iwdl6b85yafksdbwlxf6mjca-select
@@ -228,7 +234,7 @@ However there are some subtle problems with this approach:
228234 - If you have any secrets in the current directory,
229235 they get imported into the Nix store too, exposing them to all users on the system!
230236
231- ## ` builtins.path `
237+ ### ` builtins.path `
232238
233239<!-- TODO: Use lib.cleanSourceWith instead and only briefly mention builtins.path? -->
234240
@@ -239,7 +245,7 @@ And it allows selecting the files that should be included with its `filter` argu
239245``` nix
240246{ runCommand, tree, lib }:
241247let
242- source = builtins.path {
248+ src = builtins.path {
243249 # The convention is to use "source"
244250 name = "source";
245251 path = ./.;
@@ -251,17 +257,20 @@ let
251257 || ! lib.hasSuffix ".nix" pathString;
252258 };
253259in
254- runCommand "builtins-path" {
260+ stdenv.mkDerivation {
261+ name = "builtins-path";
262+ inherit src;
255263 nativeBuildInputs = [ tree ];
256- } ''
257- tree ${source}
258- ''
264+ postInstall = ''
265+ tree
266+ '';
267+ }
259268```
260269
261270The shown filter will recurse into all directories and filter out all ` .nix ` files:
262271
263272``` shell-session
264- $ nix-build
273+ $ nix-build -A package
265274building '/nix/store/3x051rr6fainqi3a4mmmb06145m0j0mw-builtins-path.drv'...
266275/nix/store/95mlqjmm13vd4ambw2pac5gj6i4wxcx4-source
267276|-- nix
@@ -272,6 +281,146 @@ building '/nix/store/3x051rr6fainqi3a4mmmb06145m0j0mw-builtins-path.drv'...
272281Writing more complex ` filter ` functions however is notoriously tricky,
273282which is why it's not recommended to use this function directly.
274283
284+ ## File sets
285+
286+ A better way to select local files is to use the [ file set library] ( https://nixos.org/manual/nixpkgs/unstable/#sec-functions-library-fileset ) .
287+ This library is an abstraction built on top of ` builtins.path ` , with essentially the same functionality, but a much easier to use and safer interface.
288+
289+ The library is based on the concept of file sets,
290+ a data type representing a collection of local files.
291+ File sets can be created, composed and used with the various functions of the library.
292+
293+ The easiest way to experiment with the file set library is to first use it through ` nix repl ` .
294+ We can use the ` pkgs ` attribute exposed by ` default.nix `
295+ to define ` fs = pkgs.lib.fileset ` as a shorthand alias for the library:
296+
297+ ``` shell-session
298+ $ nix repl -f .
299+ [...]
300+ nix-repl> fs = pkgs.lib.fileset
301+ ```
302+
303+ ### Basics
304+
305+ It's probably the easiest to just jump right in
306+ by using the [ ` trace ` ] ( https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.fileset.trace ) function,
307+ which pretty-prints the files included in a given file set:
308+
309+ ``` shell-session
310+ nix-repl> fs.trace ./. null
311+ trace: /home/user/select (all files in directory)
312+ null
313+ ```
314+
315+ You might wonder where the file set here is, because we just passed a _ path_ to the function!
316+
317+ The key is that for all file set functions that expect a file set for an argument, it also accepts a path instead.
318+ Such a path argument is then [ implicitly coerced] ( https://nixos.org/manual/nixpkgs/unstable/#sec-fileset-path-coercion )
319+ to a file set that contains _ all files under the given path_ .
320+ We can see this from the trace ` /home/user/select (all files in directory) `
321+
322+ Note that in contrast to [ coercion of paths to strings] ( #coercion-of-paths-to-strings ) ,
323+ file sets _ never_ add their files to the Nix store unless explicitly requested.
324+ So you don't have to worry about accidentally copying secrets into the store.
325+
326+ :::{tip}
327+ The ` trace ` function pretty-prints its first agument and returns its second argument.
328+ But since you often just need the pretty-printing in ` nix repl ` , you can omit the second argument:
329+
330+ ``` shell-session
331+ nix-repl> fs.trace ./.
332+ trace: /home/user/select (all files in directory)
333+ «lambda @ /nix/store/1czr278x24s3bl6qdnifpvm5z03wfi2p-nixpkgs-src/lib/fileset/default.nix:555:8»
334+ ```
335+ :::
336+
337+ This implicit coercion also works for files:
338+
339+ ``` shell-session
340+ nix-repl> fs.trace ./string.txt
341+ trace: /home/user/select
342+ trace: - string.txt (regular)
343+ ```
344+
345+ We can see that in addition to the included file,
346+ it also prints its [ file type] ( https://nixos.org/manual/nix/stable/language/builtins.html#builtins-readFileType ) .
347+
348+ And if we make a typo for a path that doesn't exist, the file set library adequately complains about it:
349+
350+ ``` shell-session
351+ nix-repl> fs.trace ./string.nix
352+ error: lib.fileset.trace: Argument (/home/user/select/string.nix) is a path that does not exist.
353+ ```
354+
355+ ### Adding files to the Nix store
356+
357+ The file set library wouldn't be very useful if you couldn't also add its files to the Nix store for use in derivations.
358+ That's where [ ` toSource ` ] ( https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.fileset.toSource ) comes in.
359+ It allows us to create a Nix store path containing exactly the files in the file set, with its root at a specific path.
360+
361+ Let's switch back to editing our ` package.nix ` file to try it out:
362+
363+ ``` nix
364+ { stdenv, tree, lib }:
365+ let
366+ fs = lib.fileset;
367+ in
368+ stdenv.mkDerivation {
369+ name = "filesets";
370+ src = fs.toSource {
371+ root = ./.;
372+ fileset = ./string.txt;
373+ };
374+ nativeBuildInputs = [ tree ];
375+ postInstall = ''
376+ tree
377+ '';
378+ }
379+ ```
380+
381+ Building this, we can see that indeed only ` string.nix ` was included:
382+ ```
383+ $ nix-build
384+ building '/nix/store/1cdgj8b1jrg2k751jidj9564r66lpvj9-filesets.drv'...
385+ unpacking sources
386+ unpacking source archive /nix/store/083k2phzhdakc649z5ql8f7cyisws6wp-source
387+ [...]
388+ .
389+ `-- string.txt
390+ ```
391+
392+ We can also avoid using ` tree ` by using the ` trace ` function instead:
393+
394+ ``` nix
395+ { stdenv, lib }:
396+ let
397+ fs = lib.fileset;
398+ sourceFiles = ./string.txt;
399+ in
400+ fs.trace sourceFiles
401+ stdenv.mkDerivation {
402+ name = "filesets";
403+ src = fs.toSource {
404+ root = ./.;
405+ fileset = sourceFiles;
406+ };
407+ }
408+ ```
409+
410+ And building it:
411+
412+ ```
413+ $ nix-build
414+ trace: /home/user/select
415+ trace: - string.txt (regular)
416+ ```
417+
418+ ### Combinators
419+
420+ The real benefit of the file set library lies in its combinator functions.
421+
422+
423+
275424<!--
276425
277426Mention lib.cleanSource, it's kind of the only function there's no good replacement for yet
0 commit comments