08/22/2018
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
e.g. /usr/local is mutable and changes cannot be undone
Package A
Package B
Package C
Package D
The Dreaded Diamond
/nix/store is immutable
# 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
nix-store -q --graph $(nix-store --realise /nix/store/...-python3.6-pandas-0.22.0.drv) | dot -Tpng
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;
}
{ 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
{ 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
]
---
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"