This blog series will consist of short, minimal walkthroughs for using
Nix to both package existing software and develop new software. The
series is aimed at people who have heard of Nix; who may have seen some
git repos containing files like default.nix
, flake.nix
, shell.nix
and module.nix
; and who maybe have been using NixOS for a little
while.
In this series I will cover the purpose of the aforementioned files and how they relate to each other. The following posts are planned:
- Building a Nix package using
nix-build
(this post) nix-shell
: Creating a Nix development shell- Flakes: Building a Nix package using
nix build
- Flakes: Creating a Nix development shell using
nix shell
- Creating a NixOS module
- Creating a Home Manager module
This list is kept up-to-date for any changes that may occur.
default.nix and nix-build
Suppose you are having a bad day. Wouldn't it be nice to receive an encouraging message from a friendly ASCII-art cow? Well, have I got just the bash script for you.
#!/usr/bin/env bash
messages=(
"You got this!"
"Trust me bro it's gonna get better"
"Everyone has bad days, don't let it get you down!"
)
# This shell script syntax is pretty cursed right?
random_index=$(($RANDOM % ${#messages[@]}))
random_message=${messages[$random_index]}
cowsay $random_message
The script is saved as src/encouraging_cowsay.sh
, so let's run it!
bash src/encouraging_cowsay.sh
_________________________________________
/ Everyone has bad days, don't let it get \
\ you down! /
-----------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
Now we would like to share this wonderful script with our fellow Nix
users. In order to do that, we create a file called default.nix
with
the following contents:
{ pkgs ? (import <nixpkgs> {}) }:
pkgs.stdenv.mkDerivation {
pname = "encouraging_cowsay";
version = "0.1";
src = ./.; # The current directory
# Dependencies of encouraging_cowsay.sh itself
buildInputs = with pkgs; [
cowsay
];
# Packages used while building the Nix package.
# We use makeWrapper's bash function 'wrapProgram' during the installPhase below.
nativeBuildInputs = with pkgs; [
makeWrapper
];
installPhase = ''
# $out is the Nix store directory for our Nix package
mkdir -p $out/bin
cp encouraging_cowsay.sh $out/bin/encouraging_cowsay
chmod +x $out/bin/encouraging_cowsay
# This needs further explanation. Read on learn more.
wrapProgram $out/bin/encouraging_cowsay --prefix PATH : \
${pkgs.lib.makeBinPath [ pkgs.cowsay ]}
'';
}
We can now run nix-build
in order to build our package. This generates
a directory result
in our current working directory, which is a
symlink to a directory in the Nix store. Remember $out
from the
installPhase
?
tree -la
.
├── default.nix
├── encouraging_cowsay.sh
└── result -> /nix/store/4y72xfcqlsbb44ij813ybq51mf6np9wp-encouraging_cowsay-0.1
└── bin
├── encouraging_cowsay
└── .encouraging_cowsay-wrapped
3 directories, 4 files
Creating a wrapper
Did you notice that result/bin
contained two files, even though we
packaged just one shell script? This is because we have generated a
so-called wrapper; a script that will call our encouraging_cowsay.sh
script, now renamed to encouraging_cowsay-wrapped
, such that our
script can find its dependency cowsay
in the PATH
environment
variable. Let's take a look at the newly generated encouraging_cowsay
script, i.e. the wrapper:
#! /nix/store/q1c2flcykgr4wwg5a6h450hxbk4ch589-bash-5.2-p15/bin/bash -e
PATH=${PATH:+':'$PATH':'}
PATH=${PATH/':''/nix/store/rc7kpqwb8z5ch37ysv5yk9yg5hl5bkdj-cowsay-3.7.0/bin'':'/':'}
PATH='/nix/store/rc7kpqwb8z5ch37ysv5yk9yg5hl5bkdj-cowsay-3.7.0/bin'$PATH
PATH=${PATH#':'}
PATH=${PATH%':'}
export PATH
exec -a "$0" "/nix/store/4y72xfcqlsbb44ij813ybq51mf6np9wp-encouraging_cowsay-0.1/bin/.encouraging_cowsay-wrapped" "$@"
This looks a bit messy, but if we filter it down to its core components, three things happen:
- We add
cowsay
, i.e.encouraging_cowsay
's sole dependency, to the variablePATH
. - We export
PATH
as an environment variable. - We use the
exec
command to run our originalencouraging_cowsay
script, now present in the Nix store as.encouraging_cowsay-wrapped
, while implicitly inheriting the environment variablePATH
.-a "$0"
means: execute this command with name$0
, i.e. the name of the script:encouraging_cowsay
.$@
means: all positional parameters (e.g.$1
,$2
,$3
, etc.). Hence, we are passing all parameters that were passed to the wrapper script along to the wrapped script.
For reference,
/nix/store/af21v8lrn58a0fc3wqxq11ri5y42rljg-encouraging_cowsay-0.1/bin/.encouraging_cowsay-wrapped
looks like this:
#!/nix/store/q1c2flcykgr4wwg5a6h450hxbk4ch589-bash-5.2-p15/bin/bash
messages=(
"You got this!"
"Trust me bro it's gonna get better"
"Everyone has bad days, don't let it get you down!"
)
# This shell script syntax is pretty cursed right?
random_index=$(($RANDOM % ${#messages[@]}))
random_message=${messages[$random_index]}
cowsay $random_message
Notice that Nix has converted the shebang to a Nix store path to bash, but has otherwise left our script untouched.
What's next?
I think it'll be no surprise for you to hear that most real-world Nix
packages are more complicated than what we have done here. Next time,
let's create a Nix development shell such that we can make
encouraging_cowsay
a bit more interesting!