Nix Demo
Daniel Wheeler
08/22/2018
Dependency Hell
Existing package and system configuration management tools suffer from an imperative model, where system administration actions such as package upgrades or changes to system configuration files are stateful: they destructively update the state of the system.
Actions are not reproducible - they depend on the state of the original system
Nix* (not *nix)
- Nix (package manager)
- Nix (language)
- NixOS (linux distro)
- Nixpkgs (collection of Nix package recipes)
- NixOps (tools for deploying Nix and NixOS)
- Hydra CI system
- Guix (GNU form of Nix based on Scheme)
Timeline
- Started in 2003
- NixOS usable in 2007
- NixOps usable in 2013
- NixCon 2015, 2017, 2018
- Nixpkgs on GitHub since 2011
- 150,126 commits
- 34,559 closed pull requests
- 7,671 closed issues
- 1,761 contributors
The current state of things
- Traditional package managers are built upon a mutable model
-
e.g. /usr/local is mutable and changes cannot be undone
-
- Upgrades are dangerous
- Rollbacks are only supported via snapshots
- Testing in different configurations is extremely difficult
Package A
Package B
Package C
Package D
The Dreaded Diamond
Why Nix?
- Functional package manager
- Functional programming language for packaging
-
/nix/store is immutable
- Actions are independent of state
- Rollback to any state
- Multiple versions side-by-side
- Deterministic & reproducible builds
Functional Programming
- Mathematical definition of a function -- not a subroutine, but a mathematical expression
- Functions do not have side effects (pure)
- Referential transparency -- evaluate to same result in any context
f \left( \;\;\; \right)
# Nix Language Demo
We are going to demo the Nix language using the REPL. Fire up the nix-repl for interactive Nix commands.
$ nix repl
Some basic syntax and types.
> 1 + 2
3
> true || false
true
> if 3 < 4 then "aa" else "bb"
"a"
There is no floating-point type in Nix.
> builtins.typeOf 1
"int"
> builtins.typeOf { a = 1; }
"set"
> { a = 1; b = 2; }.b
2
Nix has recursive sets.
> rec { a = 2; b = a; }
Nix has a primitive type for paths.
> ./hello_world.txt
/home/wd15/git/nix-presentation/hello_world.txt
> builtins.typeOf /bin/which
"path"
Nix is dynamically typed not statically typed (like Haskell). Nix is
strongly typed (not weakly typed like Javascript).
However, Nix is a purely functional language.
> f = x: x * x
> f 2
4
> (x: y: x + y) 2 3
5
Functions are first class citizens and you get currying for free. Nix functions only take one argument.
> g = x: y: x / y
> h = g 9
> h 3
3
Nix functions are automatically curried (like in Haskell).
> /9
/9
is a path
Put nix expression in a file
$ more add.nix
3 + 1
> a = import ./add.nix
> a
4
Nix is lazy.
> { a = 1 / 0; }
{ a = error: division by zero, at (string):1:6
> { a = 1 / 0; b = 2; }.b
2
Nix is pure and lazy. No loops, but uses recursion for looping. We
don't have time to get into this now.
## Impurity in Nix.
Read from a file
> data = builtins.readFile ./hello_world.txt
> data
"Hello World!\n"
Write to a file
> builtins.toFile "foo.txt" data
"/nix/store/mm4fq57cw5gkjgn1c2yysgrq6cd1w6z2-foo.txt"
You can't write to anywhere other than the Nix Store where state
cannot be overwritten.
> builtins.toFile "foo.txt" (data + "blah")
"/nix/store/la07gyzhm57f1wxz7v3ypb4gyhnaglaj-foo.txt"
It writes it to a different file.
# Nix at the command line
Start off by making a new profile
$ nix-env -S $NIX_USER_PROFILE_DIR/demo
What does this do?
$ ls -altr ~/.nix-profile
This is the equivalent of a new environment with Conda.
$ nix-env --list-generations
We have no packages installed in this environment. No iterations. Every time we install
something with Nix we get a new iteration of a profile.
$ nix-env -i hello
installing ‘hello-2.10’
...
$ hello
Hello, world!
$ echo $PATH
How to find the path of what we've installed. Nix uses symlinks to segregate packages, profiles and environments.
$ which hello
$ find `which hello` -exec realpath {} +
$ ls -al /home/wd15/.nix-profile
$ ls -al /home/wd15/.nix-profile/
$ ls -al /home/wd15/.nix-profile/bin
To check what we have installed.
$ nix-env -q
hello-2.10
To check the generations.
$ nix-env --list-generations
1 2018-08-02 17:23:38 (current)
Let's install something else
$ nix-env -i python
$ nix-env --list-generations
1 2018-08-15 10:46:38
2 2018-08-15 10:47:22 (current)
$ nix-env -q
$ which python
$ ls -al /home/wd15/.nix-profile/bin
Every time we do a new action we get a new environment. Let's install numpy
$ nix-env -qaP | grep numpy
...
$ nix-env -i python2.7-numpy-1.14.1
installing ‘python2.7-numpy-1.14.1’
...
$ nix-env -q
hello-2.10
python2.7
python2.7-numpy-1.14.3
$ nix-env --list-generations
1 2018-08-02 17:23:38
2 2018-08-02 17:39:28 (current)
3 ...
$ python -c "import numpy"
We have Python but we can't import numpy. Numpy is only installed as a
library. It is not in the Python site-packages directory. Nix installs
Numpy as a regular library not a Python library so it is not accesible
from Python. To use both Numpy, we need to use `nix-shell`.
$ nix-shell --pure -p python27 -p python27Packages.numpy
Try using it
$ python -c "import numpy"
'1.14.3'
the links are set up using `PYTHONPATH`
$ echo $PYTHONPATH
$ exit
Let's install something else.
$ nix-env -i git
$ nix-env --list-generations
1 2018-08-02 17:23:38
2 2018-08-02 17:39:28
3 ...
4 2018-08-06 15:19:52 (current)
$ nix-env -G 1
$ nix-env -q
$ nix-env -G 4
$ nix-env -q
# Building Packages
We have a small script that we'd like to install.
$ more my-hello
I've also created a build script
$ more build.sh
First, load all of the nixpkgs
> :l <nixpkgs>
Then we create something called a "derivation". A "derivation" is a method to generate a new item in the store from other items already in the store.
> d = builtins.derivation { name = "my-hello"; builder = "${bash}/bin/bash"; args = [ ./build.sh ]; src = ./my-hello; system = builtins.currentSystem; coreutils = coreutils; blah = ./blah; }
> d
«derivation /nix/store/fa1vwhhlpwjw2hb4lmv7mz8800w9dk52-my-hello.drv»
> :b d
All the variables that are passed into the build.sh are only available
from Nix.
$ cat /nix/store/fa1vwhhlpwjw2hb4lmv7mz8800w9dk52-my-hello.drv
$ /nix/store/sbqk5hgacg3vm1p52mkk431k1l2q1v8f-my_hello/bin/my-hello
This expression can be written to a Nix script.
$ more default.nix
Nix build is basically two steps.
$ nix-instantiate default.nix
/nix/store/wvmri7vj7ff0rvn0k5gs7nfcg6f8l2d0-my_hello.drv
$ # Modify build.sh
$ nix-instantiate default.nix
/nix/store/wvmri7vj7ff0rvn0k5gs7nfcg6f8l2d0-my_hello.drv
$ nix-store --realize /nix/store/wvmri7vj7ff0rvn0k5gs7nfcg6f8l2d0-my_hello.drv
This can also be installed in the current environment.
$ nix-env -if default.nix
$ my-hello
Dependency Graph
nix-store -q --graph $(nix-store --realise /nix/store/...-python3.6-pandas-0.22.0.drv) | dot -Tpng
Python Builds
-
pypi2nix
$ pypi2nix -V "3.6" -r requirements.txt
$ nix-shell --pure requirements.nix
-
buildPythonPackage
{ nixpkgs, pypkgs }:
pypkgs.buildPythonPackage rec {
pname = "scipy";
version = "0.19.1";
src = pypkgs.fetchPypi {
inherit pname version;
sha256 = "1rl411bvla6q7qfdb47fpdnyjhfgzl6smpha33n9ar1klykjr6m1";
};
buildInputs = [
pypkgs.numpy
nixpkgs.pkgs.gcc
nixpkgs.pkgs.gfortran
];
ignoreCollisions=true;
catchConflicts=false;
doCheck = false;
}
Example: FiPy
{ nixpkgs, pypkgs, pysparse, preshellhook }:
let
gmsh = import ./gmsh.nix { inherit nixpkgs; };
skfmm = import ./skfmm.nix { inherit pypkgs; };
in
pypkgs.buildPythonPackage rec {
pname = "fipy";
version = "3.1.3.dev";
env = nixpkgs.buildEnv { name=pname; paths=buildInputs; };
buildInputs = [
pypkgs.pip
pypkgs.python
pypkgs.numpy
pypkgs.scipy
gmsh
skfmm
pysparse
pypkgs.matplotlib
pypkgs.tkinter
];
src=./..;
doCheck=false;
preShellHook = preshellhook version;
prePatch = preshellhook version;
meta = {
homepage = "https://www.ctcms.nist.gov/fipy/";
description = "A Finite Volume PDE Solver Using Python";
version = version;
license = nixpkgs.stdenv.lib.licenses.free;
};
catchConflicts=false;
postShellHook = ''
SOURCE_DATE_EPOCH=$(date +%s)
export PYTHONUSERBASE=$PWD/.local
export USER_SITE=`python -c "import site; print(site.USER_SITE)"`
export PYTHONPATH=$PYTHONPATH:$USER_SITE
export PATH=$PATH:$PYTHONUSERBASE/bin
#pip install --user package
'';
}
{ nixpkgs ? import ./nixpkgs_version.nix }:
let
pypkgs = nixpkgs.python27Packages;
pysparse = import ./pysparse.nix { inherit nixpkgs pypkgs; };
preshellhook = _: '' '';
in
import ./build.nix { inherit nixpkgs pypkgs pysparse preshellhook; }
{ nixpkgs ? import ./nixpkgs_version.nix }:
let
pypkgs = nixpkgs.python36Packages;
pysparse = null;
preshellhook = version: ''
2to3 --write . &> /dev/null;
2to3 --write --doctests_only . &> /dev/null;
sed -i 's/version = getVersion()/version="${version}"/' setup.py
'';
in
import ./build.nix { inherit nixpkgs pypkgs pysparse preshellhook; }
## After running nix-shell
##
## $ find . -type f -name '*.bak' -delete
## $ git checkout .
##
## to return FiPy back to Python 2.
Python 2
Python 3
Example: PFHub
{ nixpkgs }:
let
pkgs = nixpkgs.pkgs;
jekyll_env = nixpkgs.bundlerEnv rec {
name = "jekyll_env";
ruby = nixpkgs.ruby;
gemfile = ./Gemfile;
lockfile = ./Gemfile.lock;
gemset = ./gemset.nix;
};
pypkgs = nixpkgs.python36Packages;
pypi2nix = import ./requirements.nix { inherit pkgs; };
nbval = import ./nbval.nix { inherit pkgs; };
node = import ./node.nix { inherit pkgs; };
in
[
jekyll_env
nbval
pkgs.python36
pypkgs.pillow
pypkgs.numpy
pypkgs.toolz
pypkgs.matplotlib
pypkgs.flake8
pypkgs.pylint
pypi2nix.packages."pykwalify"
pypi2nix.packages."vega"
pypi2nix.packages."progressbar2"
python36Packages.pytest
pkgs.nodejs
node.mocha
node.coffeelint
node."assert"
node.surge
node.execjs
]
Nix on Travis CI
---
language: nix
env:
global:
- STORE=$HOME/nix-store
matrix:
- PYMKS_USE_FFTW=0
- PYMKS_USE_FFTW=1
cache:
directories:
- $STORE
before_install:
- sudo mkdir -p /etc/nix
- echo "binary-caches = https://cache.nixos.org/ file://$STORE" | sudo tee -a /etc/nix/nix.conf > /dev/null
- echo 'require-sigs = false' | sudo tee -a /etc/nix/nix.conf > /dev/null
before_cache:
- mkdir -p $STORE
- nix copy --to file://$STORE -f default.nix buildInputs
install:
- nix-build -A buildInputs
script:
- nix-shell --pure --command "export PYMKS_USE_FFTW=$PYMKS_USE_FFTW; py.test"
- nix-shell --pure --command "pylint --rcfile=.pylintrc pymks/fmks pymks/fmks/tests"
- nix-shell --pure --command "flake8 pymks/fmks pymkgs/fmks/tests"
- nix-shell --pure --command "black --check pymks/fmks pymks/fmks/tests"
References
Happy to help with Nix
Thanks!
Nix Demo
By Daniel Wheeler
Nix Demo
- 906