This commit is contained in:
Amy de Buitléir 2025-10-12 22:05:59 +01:00
parent 6a1eee3d27
commit 095401025c
8 changed files with 128 additions and 383 deletions

View file

@ -54,8 +54,6 @@ include::generic-flake/main.adoc[leveloffset=+1]
include::hello-flake-return/main.adoc[leveloffset=+1]
include::modify-hello-flake/main-generated.adoc[leveloffset=+1]
include::new-flake/main.adoc[leveloffset=+1]
include::recipes/main.adoc[leveloffset=+1]

View file

@ -1,49 +0,0 @@
{
description = "a very simple and friendly flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
devShells = rec {
default = pkgs.mkShell {
packages = [ pkgs.cowsay ];
};
};
packages = rec {
hello = pkgs.stdenv.mkDerivation rec {
name = "hello-flake";
src = ./.;
unpackPhase = "true";
buildPhase = ":";
installPhase =
''
mkdir -p $out/bin
cp $src/hello-flake $out/bin/hello-flake
chmod +x $out/bin/hello-flake
'';
buildInputs = [ pkgs.cowsay ];
};
default = hello;
};
apps = rec {
hello = flake-utils.lib.mkApp { drv = self.packages.${system}.hello; };
default = hello;
};
}
);
}

View file

@ -1,53 +0,0 @@
{
description = "a very simple and friendly flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
devShells = rec {
default = pkgs.mkShell {
packages = [ pkgs.cowsay ];
};
};
packages = rec {
hello = pkgs.stdenv.mkDerivation rec {
name = "hello-flake";
src = ./.;
unpackPhase = "true";
buildPhase = ":";
installPhase =
''
mkdir -p $out/bin
cp $src/hello-flake $out/bin/hello-flake
chmod +x $out/bin/hello-flake
# modify the hello-flake script so it can find cowsay
COWSAY=$(type -p cowsay)
sed "s_cowsay_"$COWSAY"_" --in-place $out/bin/hello-flake
'';
buildInputs = [ pkgs.cowsay ];
};
default = hello;
};
apps = rec {
hello = flake-utils.lib.mkApp { drv = self.packages.${system}.hello; };
default = hello;
};
}
);
}

View file

@ -1,256 +0,0 @@
= 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 doesnt put it on the path,
so our hello-flake script wouldnt 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.
We'll see another way of handling this in <<writeShellApplication>>.
[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
////

View file

@ -261,7 +261,28 @@ system:
}
....
So `lib.genAttrs [ "x86_64-linux" "aarch64-linux" ]`
followed by the first function will give us the development shell definitions for both systems,
and followed by the second function will give us the package definitions for both systems.
We can make the flake more readable with the following definitions.
[source,nix]
....
supportedSystems = [ "x86_64-linux" "aarch64-linux" ];
forAllSupportedSystems = nixpkgs.lib.genAttrs supportedSystems;
....
Now let's examine the definition of `nixpkgsFor.${system}`.
[source,nix]
....
nixpkgsFor = forAllSupportedSystems (system: import nixpkgs { inherit system; });
....
Putting everything together, we have a shiny new flake.
You may want to compare it carefully to the original version,
in order to reassure yourself that the definitions are equivalent.
////
$ cp ../flake-4.nix flake.nix
@ -276,12 +297,13 @@ $# cat flake.nix
Let's verify that it runs on our system.
....
$ git commit -am "refactored the flake"
$ nix run
....
You may want to compare the latest `flake.nix` carefully to the original version,
in order to reassure yourself that the definitions are equivalent.
// TODO In packages, add cow-hello and set default = cow-hello. Explain why.
// TODO Add an apps section and explain why.
////
Good adoc0 scripts clean up after themselves.

View file

@ -1,21 +0,0 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils, pandoc-columns }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
customGhc = pkgs.haskellPackages.ghcWithPackages (p: with p; [ extra ]);
in
{
devShells = rec {
default = pkgs.mkShell {
buildInputs = [ customGhc ];
};
};
}
);
}

View file

@ -0,0 +1,77 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"hello-nix": {
"flake": false,
"locked": {
"lastModified": 1757705465,
"narHash": "sha256-sJCQ9+8Dy+QF9ISaupp42+mGbuXtFyqbX85tWzeNPOI=",
"ref": "refs/heads/main",
"rev": "56044f61231c996e4ab795de1da89e5f79db3f4f",
"revCount": 5,
"type": "git",
"url": "https://codeberg.org/mhwombat/hello-nix"
},
"original": {
"type": "git",
"url": "https://codeberg.org/mhwombat/hello-nix"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1760301964,
"narHash": "sha256-5oeX7NvYHNslymyCmX9mLEmLp07a8ai522G8J4VrDrs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1b5c1881789eb8c86c655caeff3c918fb76fbfe6",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"hello-nix": "hello-nix",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -0,0 +1,27 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
hello-nix = {
url = "git+https://codeberg.org/mhwombat/hello-nix";
flake = false;
};
};
outputs = { self, nixpkgs, flake-utils, hello-nix }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
};
helloNix = import hello-nix { inherit pkgs; };
in
{
devShells = rec {
default = pkgs.mkShell {
packages = [ helloNix ];
};
};
}
);
}