Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File sets tutorial #802

Merged
merged 51 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0b39689
WIP: source file selection tutorial
infinisil Nov 19, 2023
197782d
Updates
infinisil Nov 19, 2023
836b6cd
Updates
infinisil Nov 19, 2023
239f2d9
Updates
infinisil Nov 20, 2023
6582681
Expand on file sets
infinisil Nov 20, 2023
de1e742
Rewrite to be about file sets
infinisil Nov 21, 2023
988945c
Move file
infinisil Nov 21, 2023
02ae281
Complete content
infinisil Nov 21, 2023
33ff09d
Polish
infinisil Nov 21, 2023
436b4aa
Briefly mention problems with builtin features
infinisil Nov 21, 2023
f8427e5
Minor change
infinisil Nov 21, 2023
bd3a71f
Update source/tutorials/file-sets.md
infinisil Nov 21, 2023
17e859e
more informative title
fricklerhandwerk Nov 22, 2023
285fd17
shorten introduction
fricklerhandwerk Nov 22, 2023
6a61be9
clarify wording
fricklerhandwerk Nov 22, 2023
7e7c6d7
reword learning objective
fricklerhandwerk Nov 22, 2023
8402ecf
Basics -> File sets
fricklerhandwerk Nov 22, 2023
06caaae
clarify wording
fricklerhandwerk Nov 22, 2023
d388d6d
add link to command reference
fricklerhandwerk Nov 22, 2023
76905ab
shorten introduction to `trace`
fricklerhandwerk Nov 22, 2023
9fd9700
use log ellipsis as in the other tutorials
fricklerhandwerk Nov 22, 2023
5ca3739
shorten introduction to path arguments
fricklerhandwerk Nov 22, 2023
568925e
move tip before the other notes
fricklerhandwerk Nov 22, 2023
97bbe4f
restructure notes on copying files
fricklerhandwerk Nov 22, 2023
fb6f630
add link to flakes documentation, add emphasis
fricklerhandwerk Nov 22, 2023
767155b
trim down phrasing
fricklerhandwerk Nov 22, 2023
152953e
A local directory -> Example project
fricklerhandwerk Nov 22, 2023
51a78a0
channel -> channel branch
fricklerhandwerk Nov 22, 2023
a6e5f26
package.nix -> build.nix
fricklerhandwerk Nov 22, 2023
501a5f7
add instruction to add a source file
fricklerhandwerk Nov 22, 2023
ba4989c
follow convention from niv guide
fricklerhandwerk Nov 22, 2023
efe4ba4
simplify conclusion
fricklerhandwerk Nov 22, 2023
3d6ac0a
review pass on tutorial sections
fricklerhandwerk Nov 22, 2023
fa037e6
fix omission
fricklerhandwerk Nov 22, 2023
429bbb0
Update source/tutorials/file-sets.md
infinisil Nov 22, 2023
16db564
Update source/tutorials/file-sets.md
infinisil Nov 22, 2023
993bbc1
Apply suggestions from code review
infinisil Nov 22, 2023
8e3032a
Merge pull request #806 from fricklerhandwerk/source-file-selection-r…
infinisil Nov 22, 2023
a2ac364
Add Git to Vale's vocab
infinisil Nov 22, 2023
bc00f00
More consistency in using diffs and prompts
infinisil Nov 22, 2023
0d60fac
inputs -> sources
infinisil Nov 22, 2023
3befcc4
Apply suggestions from code review
infinisil Nov 22, 2023
3b2dfd8
Minor changes after review
infinisil Nov 22, 2023
b3f67ce
Combine two paragraphs
infinisil Nov 22, 2023
67b688a
Don't use nix.conf
infinisil Nov 22, 2023
e4700af
Update source/tutorials/file-sets.md
infinisil Nov 22, 2023
6afd231
Some updates
infinisil Nov 22, 2023
69e6dd6
Some changes from a full read
infinisil Nov 22, 2023
9ecfd01
Remove unecessary non-fileset part
infinisil Nov 22, 2023
fb3fb0c
Update source/tutorials/file-sets.md
infinisil Nov 23, 2023
b6700a9
Review in meeting
infinisil Nov 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions source/tutorials/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ These sections contains series of lessons to get started.
first-steps/index.md
nix-language.md
Packaging existing software <packaging-existing-software.md>
source-file-selection.md
nixos/index.md
cross-compilation.md
```
241 changes: 241 additions & 0 deletions source/tutorials/source-file-selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
(source-file-selection)=
# Source file selection
<!-- Note on title choice: While there's more uses outside of sources, it's by far the most prominent one -->

To build a local project in a Nix derivation, its source files must be accessible to the builder.
But 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),
it won't have access to the local project files by default.

To make this work regardless, the Nix language has certain builtin features to copy local paths to the Nix store,
whose paths are then accessible to derivation builders [^1].

[^1]: Technically only Nix store paths from the derivations inputs can be accessed,
but in practice this distinction is not important.

These builtin features are very limited in functionality and are not recommended for anything non-trivial. For more advanced use cases, the file set library should be used instead.

In this tutorial you'll learn both how to use the builtins and the file set library.

## Setting up a local experiment

To experiment with source file selection, we'll set up a local project.

To start out, create a new directory, enter it, and set up `niv` to manage the Nixpkgs dependency:
infinisil marked this conversation as resolved.
Show resolved Hide resolved
```shell-session
$ mkdir select
$ cd select
$ nix-shell -p niv --run "niv init --nixpkgs nixos/nixpkgs --nixpkgs-branch nixos-unstable"
```

<!-- TODO: Switch to 23.11 once out -->
:::{note}
For now we're using the nixos-unstable channel, since no stable channel has all the features we need yet.
:::

Then create a `default.nix` file with these contents:
```nix
{
system ? builtins.currentSystem,
sources ? import ./nix/sources.nix,
}:
let
pkgs = import sources.nixpkgs {
# Ensure purity
config = { };
overlays = [ ];
inherit system;
};
in
pkgs.callPackage ./package.nix { }
```

In this tutorial we'll experiment with different `package.nix` contents, while keeping `default.nix` the same.

For now, let's have a simple `package.nix` to verify everything works so far:

```nix
{ runCommand }:
runCommand "hello" { } ''
echo hello world
''
```

And try it out:
```shell-session
$ nix-build
infinisil marked this conversation as resolved.
Show resolved Hide resolved
this derivation will be built:
/nix/store/kmf9sw8fn7ps3ndqs31hvqwsa35b8l3g-hello.drv
building '/nix/store/kmf9sw8fn7ps3ndqs31hvqwsa35b8l3g-hello.drv'...
hello world
error: builder for '/nix/store/kmf9sw8fn7ps3ndqs31hvqwsa35b8l3g-hello.drv'
failed to produce output path for output 'out'
```

We could also add `touch $out` to make the build succeed,
but we'll omit that for the sake of the tutorial, since we only need the build logs.
This also makes it easier to build it again, since successful derivation builds would get cached.
From now on we'll also make build outputs a bit shorter for the sake of brevity.

## Builtin coercion of paths to strings

The 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.

Let's create a local `string.txt` file:
```
$ echo "This is a string" > string.txt
```

:::{note}
Flakes in Git directories requires git add.
:::

The two main ways to coerce paths to strings are:
- Interpolating paths in strings. To try that, change your `package.nix` file to:
```nix
{ runCommand }:
runCommand "file-interpolation" { } ''
(
set -x
cat ${./string.txt}
)
''
```

:::{note}
Interpolation into bash scripts generally requires [`lib.escapeShellArg`](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.strings.escapeShellArg) for correct escaping.
In this case however, the interpolation results in a Nix store path of the form `/nix/store/<hash>-<name>`,
and all valid characters of such store paths don't need to be escaped in bash.
:::

- Using paths as derivation attributes. To try that, change your `package.nix` file to:
```nix
{ runCommand }:
runCommand "file-interpolation" {
stringFile = ./string.txt;
} ''
(
set -x
cat "$stringFile"
)
''
```

:::{note}
Nowadays using the explicit `env` attribute is recommended to set environment variables.
`env` doesn't implicitly coerce paths to strings, so it requires using string intepolation instead:
infinisil marked this conversation as resolved.
Show resolved Hide resolved
```nix
{ runCommand }:
runCommand "file-interpolation" {
env.stringFile = "${./string.txt}";
} ''
(
set -x
cat "$stringFile"
)
''
```
:::

These all do the same when built:
```shell-session
$ nix-build
building '/nix/store/9fi0khrkmqw5srjzjsfa0b05hf8div4c-file-interpolation.drv'...
++ cat /nix/store/j5lwpnlfrngks3bpidfr5hcrhgq0fy78-string.txt
This is a string
```

As you can see, the `string.txt` file was hashed and added to the store,
which then allowed the build to access it.

The underlying functionality is the same as `nix-store --add` on an absolute path:
```shell-session
$ nix-store --add $(pwd)/string.txt
/nix/store/j5lwpnlfrngks3bpidfr5hcrhgq0fy78-string.txt
```

## Built-in path coercion on directories

This path coercion also works on directories the same as it does on files, let's try it out:

```nix
{ runCommand, tree }:
runCommand "directory-interpolation" {
# To nicely show path contents
nativeBuildInputs = [ tree ];
} ''
tree ${./.}
''
```

Running it gives us:
```shell-session
$ nix-build
building '/nix/store/6ybg4v48xy8azhrnfdccdmhd2gr938f5-directory-interpolation.drv'...
/nix/store/xdfchqpfx20ar9jil9kys99wc6hnm9zx-select
|-- default.nix
|-- nix
| |-- sources.json
| `-- sources.nix
|-- package.nix
`-- string.txt
```

But here we can get into some subtle trouble:
- Note how the name of the store path ends with `-select`.
So the name of the local directory influenced the result.

This means that whenever you rename the project directory
or a collegue runs it in a different directory name,
infinisil marked this conversation as resolved.
Show resolved Hide resolved
you're going to get different build results!

- All files in the directory are unconditionally added to the Nix store.

This means that:
- Even if your build only needs a few files,
changing _any_ file in the directory requires rebuilding the derivation,
potentially wasting a lot of time.

- If you have any secrets stored in the current directory,
they get imported into the Nix store too, exposing them to all users on the system!

## `builtins.path`

The above problems can be fixed by using [`builtins.path`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-path) instead.
It allows customising the name of the resulting store path with its `name` argument.
And it allows selecting the files that should be included with its `filter` argument.

```nix
builtins.path {
name = "source";
path = ./.;
filter = pathString: type:
baseNameOf pathString != "default.nix";
```

However, this function is notoriously hard to use correctly by itself.

<!--

Mention lib.cleanSource, it's kind of the only function there's no good replacement for yet

Section on file sets:
- Tracing file sets in nix repl
- Coercing file sets from paths
- Using files from a file set as a derivation source
- Migrate/integrate with lib.source-based filtering

A file structure to show?
- `.envrc`:
- `README.md`
- `Makefile`
- `nix`
- `package.nix`
- `sources.nix`
- `sources.json`
- `src`
- `main.c`
- `main.o`
- `.gitignore`
- `.git`

-->