mirror of
https://codeberg.org/mhwombat/nix-book.git
synced 2026-01-06 22:07:33 +08:00
311 lines
8.5 KiB
Text
311 lines
8.5 KiB
Text
= Bash
|
|
|
|
In this section we will create a very simple flake, and then make several improvements.
|
|
To follow along, open a new terminal shell.
|
|
Start with an empty directory and create a git repository.
|
|
|
|
....
|
|
$ mkdir my-project
|
|
$ cd my-project
|
|
$ git init
|
|
....
|
|
|
|
This will be a very simple development project,
|
|
so that we can focus on how to use Nix.
|
|
We want to package the following script.
|
|
|
|
////
|
|
$ cp ../cow-hello.sh .
|
|
////
|
|
|
|
[source,bash,linenums]
|
|
.cow-hello.sh
|
|
....
|
|
$# cat cow-hello.sh
|
|
....
|
|
|
|
Add the script to your directory and we will test it.
|
|
|
|
....
|
|
$ chmod +x cow-hello.sh
|
|
$ ./cow-hello.sh # Fails
|
|
....
|
|
|
|
This probably failed on your machine -- why?
|
|
The `cow-hello.sh` script depends on `cowsay`,
|
|
which isn't part of your default environment (unless you specifically configure Nix or NixOS to include it).
|
|
We can create a suitable temporary environment for a quick test:
|
|
|
|
....
|
|
$ nix shell nixpkgs#cowsay
|
|
$ ./cow-hello.sh # Succeeds
|
|
....
|
|
|
|
However, that approach becomes inconvenient once you have more dependencies.
|
|
Let's exit to the original (default) environment,
|
|
and see that `cowsay` is no longer available.
|
|
|
|
....
|
|
$ exit
|
|
$ ./cow-hello.sh # Fails again
|
|
....
|
|
|
|
For a single script like this, we could modify it by replacing the first line with a "Nix shebang",
|
|
as shown later in <<#shebang>>;
|
|
then the script would run in any Nix environment.
|
|
But for this exercise, we will package it as a "proper" development project.
|
|
|
|
== Defining the development environment
|
|
|
|
We want to define the development environment we need for this project,
|
|
so that we can recreate it at any time.
|
|
Furthermore, anyone else who wants to work on our project will be able to recreate
|
|
the same development environment we used.
|
|
This avoids complaints of "but it works on my machine!"
|
|
|
|
////
|
|
$ cp ../flake.nix .
|
|
////
|
|
|
|
Create the file `flake.nix` as shown below.
|
|
If you're not on an `x86_64-linux` system, modify the highlighted lines accordingly.
|
|
|
|
[NOTE]
|
|
====
|
|
If you're not sure of the correct string so use for your system,
|
|
run the following command.
|
|
|
|
nix eval --impure --raw --expr 'builtins.currentSystem'
|
|
====
|
|
|
|
[source,nix,linenums,highlight="10,12,14"]
|
|
.flake.nix (version 1)
|
|
....
|
|
$# cat flake.nix
|
|
....
|
|
|
|
The `description` part is just a short description of the package.
|
|
The `inputs` section should be familiar from <<#flake-inputs>>.
|
|
If we ignore parts of the long package names, the `outputs` section looks like this:
|
|
|
|
[source,subs=quotes]
|
|
....
|
|
devShells = {
|
|
x86_64-linux.default =
|
|
_blahblah_.mkShell {
|
|
packages = [
|
|
_blahblah_.cowsay
|
|
];
|
|
}; # mkShell
|
|
}; # devShells
|
|
....
|
|
|
|
This says, in effect, that to create a `default` shell for the `x86_64-linux` architecture,
|
|
call the `mkShell` command and tell it you need the `cowsay` package.
|
|
|
|
The code is rather wordy, with all the _this.that.the.other.thing_ stuff.
|
|
Also, the name `legacyPackages` suggests that we're not following current best practices.
|
|
(In fact, it could in some circumstances result in duplicate instances of nixpkgs.)
|
|
In <<#multi-arch>> we will refactor the code to eliminate some duplication, make it more readable,
|
|
and eliminate the references to `legacyPackages`.
|
|
For now, we will stick with the ugly, but straightforward, version.
|
|
|
|
Let's enter the shell.
|
|
|
|
....
|
|
$# echo '$ nix develop # Fails'
|
|
$# nix develop --command sh
|
|
....
|
|
|
|
Because we haven't added `flake.nix` to the git repository, it's essentially invisible to Nix.
|
|
Let's correct that and try again.
|
|
We'll also add the script to the git repository.
|
|
|
|
....
|
|
$ git add flake.nix cow-hello.sh
|
|
$# echo '$ nix develop'
|
|
$# nix develop --command sh
|
|
....
|
|
|
|
The warning is because the changes haven't been committed to the git repository yet.
|
|
We changed `flake.nix` of course, but when we ran `nix develop` it automatically created a `flake.lock` file.
|
|
Don't worry about the warnings for now.
|
|
We can see that `cowsay` is now available, and our script runs.
|
|
|
|
....
|
|
$ ./cow-hello.sh # Succeeds
|
|
....
|
|
|
|
== Defining the package
|
|
|
|
We created an appropriate development environment, and tested our script.
|
|
Now we are ready to package it.
|
|
Add the new lines below to `flake.nix`.
|
|
Again, change `x86_64-linux` if needed to match your system architecture.
|
|
|
|
////
|
|
$ cp ../flake-2.nix flake.nix
|
|
////
|
|
|
|
[source,nix,linenums,highlight="19..36"]
|
|
.flake.nix (version 2)
|
|
....
|
|
$# cat flake.nix
|
|
....
|
|
|
|
Let's test the package.
|
|
|
|
....
|
|
$ nix run
|
|
....
|
|
|
|
If we hadn't added `cow-hello.sh` to the git repository,
|
|
we would have an error about the file being missing.
|
|
|
|
|
|
[#multi-arch]
|
|
== Supporting multiple architectures
|
|
|
|
Congratulations!
|
|
Our package is popular, and people want to run it on `aarch64-linux` systems.
|
|
So now we need to add an entry for that to `packages`.
|
|
Of course we want to test it on the new architecture,
|
|
so we'll add an entry to `devShells` as well.
|
|
|
|
////
|
|
$ cp ../flake-3.nix flake.nix
|
|
////
|
|
|
|
[source,nix,linenums,highlight="18..23,44..59"]
|
|
.flake.nix (version 3)
|
|
....
|
|
$# cat flake.nix
|
|
....
|
|
|
|
Let's make sure it still runs on our system.
|
|
|
|
....
|
|
$ nix run
|
|
....
|
|
|
|
Of course, we should also test on an `aarch64-linux` system.
|
|
But the flake definition is rather long now.
|
|
If we need to add even more architectures... ugh.
|
|
Even worse, notice that the definitions for `x86_64-linux` are almost identical
|
|
to those for `aarch64-linux`.
|
|
All that replication makes maintenance more difficult.
|
|
What if we make a change to the definition for `aarch64-linux`,
|
|
and forget to make the change to `x86_64-linux`?
|
|
|
|
It's time to refactor the code to eliminate duplication and make it more readable.
|
|
|
|
In <<#genAttrs>>, I introduced the `lib.genAttrs` function,
|
|
and included a promising-looking example.
|
|
|
|
[source]
|
|
.Example
|
|
....
|
|
nix-repl> lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: "some definitions for ${system}")
|
|
{
|
|
aarch64-linux = "some definitions for aarch64-linux";
|
|
x86_64-linux = "some definitions for x86_64-linux";
|
|
}
|
|
....
|
|
|
|
This function generates an attribute set by mapping a function over a list of attribute names.
|
|
It takes two arguments.
|
|
The first argument is a list; the list elements will become names of values in the resulting attribute set.
|
|
The second argument is a function that, given the name of the attribute, returns the attribute's value
|
|
|
|
In the example, we used a list of system architecture names as the first argument.
|
|
For the second argument, we used a function that "pretended" to generate definitions.
|
|
|
|
What if we wrote a function which, given the name of the system architecture,
|
|
would generate the development shell definition for us,
|
|
and another function that would do the same for the package definition?
|
|
Applying `lib.genAttrs` and the list of system architecture names would give us
|
|
all the definitions we need for the outputs section.
|
|
|
|
The following function will generate a development shell definition.
|
|
We will write the definition of `nixpkgsFor.${system}` shortly
|
|
|
|
[source,nix]
|
|
....
|
|
system:
|
|
let pkgs = nixpkgsFor.${system}; in {
|
|
default = pkgs.mkShell {
|
|
packages = [ pkgs.cowsay ];
|
|
};
|
|
}
|
|
....
|
|
|
|
And this one will generate a package definition.
|
|
|
|
[source,nix]
|
|
....
|
|
system:
|
|
let pkgs = nixpkgsFor.${system}; in {
|
|
default = pkgs.stdenv.mkDerivation {
|
|
name = "cow-hello.sh";
|
|
src = ./.;
|
|
unpackPhase = "true";
|
|
buildPhase = ":";
|
|
installPhase =
|
|
''
|
|
mkdir -p $out/bin
|
|
cp $src/cow-hello.sh $out/bin
|
|
chmod +x $out/bin/cow-hello.sh
|
|
'';
|
|
buildInputs = [ pkgs.cowsay ];
|
|
}; # mkDerivation
|
|
}
|
|
....
|
|
|
|
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
|
|
////
|
|
|
|
[source,nix,linenums,highlight="18..23,44..59"]
|
|
.flake.nix (version 3)
|
|
....
|
|
$# cat flake.nix
|
|
....
|
|
|
|
Let's verify that it runs on our system.
|
|
|
|
....
|
|
$ git commit -am "refactored the flake"
|
|
$ nix run
|
|
....
|
|
|
|
// 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.
|
|
$ cd .. ; rm -rf my-project # clean up
|
|
////
|