mirror of
https://codeberg.org/mhwombat/nix-book.git
synced 2025-12-27 16:54:57 +08:00
255 lines
7.4 KiB
Text
255 lines
7.4 KiB
Text
= Modifying the flake
|
||
|
||
== The Nix development shell
|
||
|
||
Let's make a simple modification to the script. This will give you an
|
||
opportunity to check your understanding of flakes.
|
||
|
||
The first step is to enter a development shell.
|
||
|
||
////
|
||
The user would have done this in an earlier section.
|
||
However, my adoc0 scripts clean up after themselves, so we need to do it again.
|
||
$ git clone https://codeberg.org/mhwombat/hello-flake
|
||
$ cd hello-flake
|
||
////
|
||
|
||
....
|
||
$ nix develop
|
||
....
|
||
|
||
The `flake.nix` file specifies all of the tools that are needed during
|
||
development of the package. The `nix develop` command puts us in a shell
|
||
with those tools. As it turns out, we didn't need any extra tools
|
||
(beyond the standard environment) for development yet, but that's
|
||
usually not the case. Also, we will soon need another tool.
|
||
|
||
A development environment only allows you to _develop_ the package.
|
||
Don't expect the package _outputs_ (e.g. executables) to be available
|
||
until you build them. However, our script doesn't need to be compiled,
|
||
so can't we just run it?
|
||
|
||
....
|
||
$ hello-flake
|
||
....
|
||
|
||
That worked before; why isn't it working now? Earlier we used
|
||
`nix shell` to enter a _runtime_ environment where `hello-flake` was
|
||
available and on the `$PATH`. This time we entered a _development_
|
||
environment using the `nix develop` command. Since the flake hasn't been
|
||
built yet, the executable won't be on the `$PATH`. We can, however, run
|
||
it by specifying the path to the script.
|
||
|
||
....
|
||
$ ./hello-flake
|
||
....
|
||
|
||
We can also build the flake using the `nix build` command, which places
|
||
the build outputs in a directory called `result`.
|
||
|
||
....
|
||
$ nix build
|
||
$ result/bin/hello-flake
|
||
....
|
||
|
||
Rather than typing the full path to the executable, it's more convenient
|
||
to use `nix run`.
|
||
|
||
....
|
||
$ nix run
|
||
....
|
||
|
||
Here's a summary of the more common Nix commands.
|
||
|
||
[width="100%",cols="<17%,<83%",options="header",]
|
||
|===
|
||
|command |Action
|
||
|`nix develop` |Enters a _development_ shell with all the required
|
||
development tools (e.g. compilers and linkers) available (as specified
|
||
by `flake.nix`).
|
||
|
||
|`nix shell` |Enters a _runtime_ shell where the flake's executables are
|
||
available on the `$PATH`.
|
||
|
||
|`nix build` |Builds the flake and puts the output in a directory called
|
||
`result`.
|
||
|
||
|`nix run` |Runs the flake's default executable, rebuilding the package
|
||
first if needed. Specifically, it runs the version in the Nix store, not
|
||
the version in `result`.
|
||
|===
|
||
|
||
== Introducing a dependency
|
||
|
||
Now we're ready to make the flake a little more interesting.
|
||
Instead of using the `echo` command in the script, we can use the Linux `cowsay`
|
||
command.
|
||
Here's the modified `hello-flake` file.
|
||
|
||
////
|
||
$ sed -i 's/echo/cowsay/' hello-flake
|
||
////
|
||
|
||
[source,nix,linenums,highlight=3]
|
||
.hello-flake
|
||
....
|
||
$# cat hello-flake
|
||
....
|
||
|
||
Let's test the modified script.
|
||
|
||
....
|
||
$ ./hello-flake # Fails
|
||
....
|
||
|
||
What went wrong? Remember that we are in a _development_ shell. Since
|
||
`flake.nix` didn't define the `devShells` variable, the development
|
||
shell only includes the Nix standard environment. In particular, the
|
||
`cowsay` command is not available.
|
||
|
||
[#hello-flake-dependency]
|
||
To fix the problem, we can modify `flake.nix`.
|
||
We don't need to add `cowsay` to the `inputs` section because it's included in `nixpkgs`,
|
||
which is already an input.
|
||
However, we also want it to be available in a development shell.
|
||
The highlighted modifications below will accomplish that.
|
||
|
||
////
|
||
$ cp ../flake.nix.new flake.nix
|
||
////
|
||
|
||
[source,nix,linenums,highlight=15..19]
|
||
.flake.nix
|
||
....
|
||
$# cat flake.nix
|
||
....
|
||
|
||
Now we restart the development shell and see that the `cowsay` command is available.
|
||
Because we've updated source files but haven't ``git commit``ed the new version,
|
||
we get a warning message about it being "`dirty`".
|
||
It's just a warning, though; the script runs correctly.
|
||
|
||
....
|
||
$# echo '$ nix develop'
|
||
$# nix develop --command sh
|
||
$ which cowsay # is it available now?
|
||
$ ./hello-flake
|
||
$ exit
|
||
....
|
||
|
||
Let's try `nix run`.
|
||
|
||
....
|
||
$ nix run # Fails
|
||
....
|
||
|
||
What went wrong?
|
||
Recall that we only added `cowsay` to the development environment;
|
||
we didn't add it to the runtime environment.
|
||
The modified flake below will fix that.
|
||
|
||
////
|
||
$ cp ../flake.nix.new.2 flake.nix
|
||
////
|
||
|
||
[source,nix,linenums,highlight='37..39,42']
|
||
.flake.nix
|
||
....
|
||
$# cat flake.nix
|
||
....
|
||
|
||
Line 42 adds `cowsay` as a `buildInput` for the package.
|
||
That does make it available at build and runtime, but it doesn’t put it on the path,
|
||
so our hello-flake script wouldn’t be able to find it.
|
||
There are various ways to deal with this problem.
|
||
In this case we simply edited the script (lines 37-39) as we install it,
|
||
specifying the full path to cowsay.
|
||
|
||
[NOTE]
|
||
====
|
||
When you're packaging a program written in a more powerful language such as
|
||
Haskell, Python, Java, C, C#, or Rust,
|
||
the language build system will usually do all that is required
|
||
to make the dependencies visible to the program at runtime.
|
||
In that case, adding the dependency to `buildInputs` is sufficient.
|
||
====
|
||
|
||
This time `nix run` works.
|
||
|
||
....
|
||
$ nix run
|
||
....
|
||
|
||
Note, however, that `nix run` rebuilt the package in the Nix store and
|
||
ran _that_. It did not alter the copy in the `result` directory, as
|
||
we'll see next.
|
||
|
||
....
|
||
$ cat result/bin/hello-flake
|
||
....
|
||
|
||
If we want to update the version in `result`, we need `nix build` again.
|
||
|
||
....
|
||
$ nix build
|
||
$ cat result/bin/hello-flake
|
||
....
|
||
|
||
Let's `git commit` the changes and verify that the warning goes away. We
|
||
don't need to `git push` the changes until we're ready to share them.
|
||
|
||
....
|
||
$ git commit hello-flake flake.nix -m 'added bovine feature'
|
||
$ nix run
|
||
....
|
||
|
||
== Development workflows
|
||
|
||
If you're getting confused about when to use the different commands,
|
||
it's because there's more than one way to use Nix. I tend to think of it
|
||
as two different development workflows.
|
||
|
||
My usual, _high-level workflow_ is quite simple.
|
||
|
||
[arabic]
|
||
. `nix run` to re-build (if necessary) and run the executable.
|
||
. Fix any problems in `flake.nix` or the source code.
|
||
. Repeat until the package works properly.
|
||
|
||
In the high-level workflow, I don't use a development shell because I
|
||
don't need to directly invoke development tools such as compilers and
|
||
linkers. Nix invokes them for me according to the output definition in
|
||
`flake.nix`.
|
||
|
||
Occasionally I want to work at a lower level, and invoke compiler,
|
||
linkers, etc. directly. Perhaps want to work on one component without
|
||
rebuilding the entire package. Or perhaps I'm confused by some error
|
||
message, so I want to temporarily bypass Nix and work directly with
|
||
the compiler. In this case I temporarily switch to a _low-level
|
||
workflow_.
|
||
|
||
[arabic]
|
||
. `nix develop` to enter a development shell with any development tools
|
||
I need (e.g. compilers, linkers, documentation generators).
|
||
. Directly invoke tools such as compilers.
|
||
. Fix any problems in `flake.nix` or the source code.
|
||
. Directly invoke the executable. Note that the location of the
|
||
executable depends on the development tools – It probably isn't
|
||
`result`!
|
||
. Repeat until the package works properly.
|
||
|
||
I generally only use `nix build` if I just want to build the package but
|
||
not execute anything (perhaps it's just a library).
|
||
|
||
== This all seems like a hassle!
|
||
|
||
It is a bit annoying to modify `flake.nix` and ether rebuild or reload
|
||
the development environment every time you need another tool. However,
|
||
this Nix way of doing things ensures that all of your dependencies, down
|
||
to the exact versions, are captured in `flake.lock`, and that anyone
|
||
else will be able to reproduce the development environment.
|
||
|
||
////
|
||
Good adoc0 scripts clean up after themselves.
|
||
$ cd .. ; rm -rf hello-flake # clean up
|
||
////
|