Derivations are recipes to build and distribute apps of any ecosystem in Nix. They are similar to Docker files but better, because the Nix programming language allows custom functions like mkDerivation
and mkShell
to hide complex details.
In this tutorial we get an overview over:
- Raw derivations to see why we need abstractions and helper functions. ➡
- The basic helper function
mkDerivation
to package apps for a Nix. ➡ - The helper function
writeShellScriptBin
to quickly package shell scripts as apps. ➡ - The helper function
mkShell
to declare a devolopment environments only. ➡ - Ecosystem specific helper functions. ➡
A Raw Derivation
A Nix derivation is a collection of the source code, dependencies, tools and everything else required to build an app or a package. It’s so generic that we can build almost any artifact from any ecosystem and distribute it via Nix. But with great power comes great amount of configuration. For example, the derivation for a Hello-World app already contains the following data:
$ nix show-derivation nixpkgs#hello
{
"/nix/store/qp724w90516n4bk5r9gfb37vzmqdh3z7-hello-2.10.drv": {
"inputSrcs": [
"/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh"
],
"inputDrvs": {
"/nix/store/d9d47vmkcpvz55l5vx4f0s4vry965ahj-stdenv-linux.drv": [
"out"
],
"/nix/store/l6jacnrdv1qkyaj6jvkrvczz9fkxf51b-bash-4.4-p23.drv": [
"out"
],
"/nix/store/lwmi9xcp2lzrap72ig56a5xi0qd2i5b3-hello-2.10.tar.gz.drv": [
"out"
]
},
..
"platform": "x86_64-linux",
"builder": "/nix/store/jdi2v7ir1sr6vp7pc5x0nhb6lpcmg6xg-bash-4.4-p23/bin/bash",
..
"env": {
"pname": "hello",
"version": "2.10"
"src": "/nix/store/3x7dwzq014bblazs7kq20p9hyzz0qh8g-hello-2.10.tar.gz",
"out": "/nix/store/0pisd259nldh8yfjvw663mspm60cn5v8-hello-2.10",
"buildInputs": "",
"nativeBuildInputs": "",
"propagatedBuildInputs": "",
"propagatedNativeBuildInputs": "",
..
}
}
}
Element | Note |
---|---|
$
|
Interactive shell prompt symbol allowing us to type terminal commands. |
nix
|
Standard terminal app from the Nix package manager to interact with Nix packages. It has several subcommands. ➡ Subcommands |
show-derivation
|
Subcommand to print the raw derivation of the given Nix package as Json. ➡ Docs |
nixpkgs#hello
|
Select the Nix package “hello” from the collections of Nix packages. ➡ Package |
We see references to other tools like the Bash shell (bash-4.4
) and Make (stdenv-linux
), the source code (hello-2.10.tar.gz
), some “default build script” (default-builder.sh
) and other meta information.
Writing all that information by hand would be annoying, so no one really does it. Instead we always see helper functions being used like the ones in the following sections.
mkDerivation
The most common helper function to write Nix derivations is mkDerivation
. With it everything is optional except the source code, and the package name and version of what we are bundling up:
# Example hello.nix file
let
pkgs = import <nixpkgs> {};
in
pkgs.stdenv.mkDerivation {
name = "hello-2.10";
src = pkgs.fetchurl {
url = "mirror://gnu/hello/${pname}-${version}.tar.gz";
sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i";
};
..
}
let .. in ..
- Define custom variables in the let-part to use in the in-part for more readable code ➡ Let explanation.
pkgs
- Custom variable for the huge collection of all available Nix packages (more than 80000) ➡ Pkgs structure.
import <nixpkgs> {}
- Load all available Nix packages ➡ Import explanation.
stdenv
- A collection of Nix helper functions.
mkDerivation
- A Nix helper function ➡ Nix function syntax.
fetchUrl
- A Nix helper function to download source code from the internet.
This helper function is configured to build a package with the build tool Make and the C compiler GCC by default (see builder guide and stdenv guide ). However, we can easily override those settings:
# conceptual "javaproject.nix" file
mkDerivation {
name = "myJavaProject-1.0.0";
src = ./src;
# override required dependencies
buildInputs = [ maven jdk ];
# override build commands
buildPhase = "mvn clean build";
installPhase = "mvn install";
}
A real Nix Java package file is actually slightly more complicated, but conceptually it’s just that ➡Java Nix Guide.
More: The Nix language basics
See: mkDerivation in-depth explanation
See: mkDerivation source code
See: Full “hello” Nix package code
Having a Nix derivation in a file like javaproject.nix
allows us two things: First, to build our app with Nix
$ nix-build javaproject.nix
which places our compiled app under a new ./result
folder. Second, to (theoretically) distribute our app via Nix to other users.
Interestingly however, we can also create a development environment with that file:
$ nix-shell javaproject.nix
places us into a shell environment where the required dependencies from buildInputs
are available:
[nix-shell:~]$ mvn --version
3.6.3
writeShellScriptBin
If we have a shell script that we want to package and distribute like an app, we can use a simpler variant of mkDerivation
:
# Example "shellscript.nix" file
let
pkgs = import <nixpkgs> {};
in
pkgs.writeShellScriptBin "greeter" ''
# Some bash script
echo Hi
''
let .. in ..
- Define variables in the let-part to use in the in-part. Use it for more readable code. ➡ Let explanation.
pkgs
- Variable for the collection of all available Nix packages (more than 80000). ➡ Pkgs structure.
import <nixpkgs> {}
- Load all available Nix packages ➡ Import explanation.
writeShellScriptBin
- A Nix helper function that takes two arguments, a name string and a script string ➡ Nix function syntax.
"greeter"
- Our custom executable name for the following shell script.
'' .. ''
- Double single quotes are for multiline strings in Nix. Here we use it to embedd a Bash script. ➡ Nix strings.
echo ..
- Terminal app that prints the following text to the terminal console.
writeShellScriptBin
doesn’t need anything but an executable name and the script text.
$ nix-shell shellscript.nix
places us into a shell environment where our greeter
“executable” is available:
[nix-shell:~]$ greeter
Hi
More: writeShellScriptBin examples
See: writeShellScriptBin source code
mkShell
Sometimes we just want to create a development environment and don’t want to build or distribute our app with Nix at all. In that case we quite often see the following simpler variant of mkDerivation
:
# Example "java-env.nix" file
let
pkgs = import <nixpkgs> {};
in
pkgs.mkShell {
# "override" required dependencies
buildInputs = [
pkgs.maven
pkgs.jdk
];
}
let .. in ..
- Custom variables in the let-part to use in the in-part for more readable code. Without “let” we would write
(import <nixpkgs> {}).mkShell { ...
➡ Let explanation. pkgs
- Custom variable for the huge collection of all available Nix packages (more than 80000). Think of it as a large Json object
{ maven:.., jdk:.., git:.. }
of packages. import <nixpkgs> {}
- Load all available Nix packages ➡ Import explanation.
stdenv
- A collection of Nix helper functions.
mkDerivation
- A Nix helper function ➡ Nix function syntax.
fetchUrl
- A Nix helper function to download source code from the internet.
buildInputs
- List of Nix packages that you want available in the shell environment.
[pkgs.maven pkgs.jdk]
- A list in the Nix language. Nix doesn’t use commas in between!
mkShell
doesn’t need a package name, a version or the source code; just the list of apps and tools we want to have in our development environment.
$ nix-shell java-env.nix
places us into a shell environment where the apps from buildInputs
are available:
[nix-shell:~]$ mvn --version
3.6.3
More: Development environments with nix-shell
More: Nix language basics
mkShell
is a simplifying wrapper around mkDerivation
➡ mkShell source code and an example of why it’s great to have a configuration programming language like Nix: we can have abstractions, customized to our use case, built on top of other abstractions. *cough* not possible with Docker, Ansible etc. *cough*.
Ecosystem specific variants
Nix can package and distribute apps, tools and artifacts from almost any ecosystem like Haskell, Javascript, Python, databases etc. Each ecosystem has its own compile tools, build tools, and quirks that we somehow have to embed into Nix. That’s why usually specialized variants of mkDerviation
emerge from the Nix community for each ecosystem.
For example, if we want to write a Haskell app, we nowadays use the following variant:
# Example haskellproject.nix file
let
pkgs = import <nixpkgs> { };
hPkgs = pkgs.haskellPackages;
in
hPkgs.developPackage {
root = ./.;
# match haskellProject.cabal file
name = "haskellProject";
}
let .. in ..
- Custom variables in the let-part to use in the in-part. Allows for more readable code. ➡ Let explanation.
pkgs
- Variable with all available Nix packages.
import <nixpkgs> {}
- Load all available Nix packages ➡ Import explanation.
haskellPackages
- A collection of Nix Haskell packages and helper functions.
developPackage
- A Nix helper function to get a list of required Nix Haskell packages from the Haskell-native
.cabal
file in the project folder ➡ Nix function syntax. root
- Specify the folder where to find the
.cabal
file and the source code. ./.
- Represents the current folder.
haskellProject.cabal
- A file listing all the needed Haskell packages (just like Node’s
package.json
or Java’spom.xml
). ➡ Cabal syntax.
developPackage
deals with the quirks of the Haskell compiler Ghc and its build tool Cabal. It hides the huge complexity we would have to deal with if we only had mkDerivation
. Similar helper functions exist for other ecosystems.
More: Setup Haskell development environments
See: Nix ecosystems list
See: developPackage source code