Nix Tips

Just a random collection of things to reference about nix.

Minimal nix derivation

with import <nixpkgs> {};
stdenv.mkDerivation {
  name = "env";
  buildInputs = [
    bashInteractive
  ];
}

This is useful for writing a quick and dirty, non-language specific default.nix as a starting point to potentially packaging something.

If you need some tooling for a project, drop the tools in buildInputs, then when you nix-shell, you’ll have them available. Like:

with import <nixpkgs> {};
stdenv.mkDerivation {
  name = "env";
  buildInputs = [
    awscli python36 nodejs-8_x
  ];
}

mkShell

If you only want a shell environment, you can use mkShell in a shell.nix, which is slightly shorter:

with import <nixpkgs> {};
mkShell {
  buildInputs = [
    awscli python36 nodejs-8_x
  ];
}

And also supports the inputsFrom parameter, if you want to pull build inputs from other packages

{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
  # this will make all the build inputs from hello and gnutar
  # available to the shell environment
  inputsFrom = with pkgs; [ hello gnutar ];
  buildInputs = [ pkgs.gnumake ];
}

https://nixos.org/nixpkgs/manual/#sec-pkgs-mkShell

Pin nixpkgs

You’ll likely want to do this for any non-trivial project, as it insulates the project from any changes (or breakages) from the global nixpkgs and ensures the same version of nixpkgs for the project is used by every one.

niv

You should probably just use niv to do this.

https://github.com/nmattia/niv

See the bootstrapping section to start.

nix >= 2.0

If you don’t want to use niv and you only need to support nix > 2.0, you can use builtins.fetchTarballThe builtin function supports the a sha256 param on the function, so it doesn’t have to constantly refetch the tarball to check for updates.

. If you need to support < 2.0 nix versions (or a mix), see the next section.

{ rev ? "92f0d31b949bcf018262252408b8a69376a6cb68"
, sha256 ? "08ir579xsh7414i9y7jaislr2d56q716ayv2jlrrbpsx38gly453"
}:

let
  nixpkgsPath = builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
    inherit sha256;
  };

  pkgs = import nixpkgsPath {
    config = {
      # do whatever you want
      # packageOverrides = oldPkgs: let newPkgs = oldPkgs.pkgs; in {...}
    };
  };
in
# do stuff with pkgs

You could also make nixpkgs a parameter and import the source inline, which is more compact.

{ rev ? "92f0d31b949bcf018262252408b8a69376a6cb68"
, sha256 ? "08ir579xsh7414i9y7jaislr2d56q716ayv2jlrrbpsx38gly453"
, nixpkgs ? import (builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
    inherit sha256;
  }) { config = {} }
}:

# do stuff with nixpkgs

nix < 2.0

For pre-2.0 nix support, you could use this fetch-nixpkgs helper or use fetchFromGitHub.

With fetchFromGitHub:

{ hostPkgs ? import <nixpkgs> {}
, rev ? "92f0d31b949bcf018262252408b8a69376a6cb68"
, sha256 ? "08ir579xsh7414i9y7jaislr2d56q716ayv2jlrrbpsx38gly453"
, nixpkgs ? import (hostPkgs.fetchFromGitHub {
    owner = "NixOS"; repo = "nixpkgs"; inherit rev sha256;
  }) { config = {} }
}:

# do stuff with nixpkgs

In environment

You can also set NIX_PATH to a location to use for <nixpkgs>, which if set in some file sourced by the project (say a .env or .envrc), pins the version.

Either a specific commit:

export NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/628ba24e77b2c6f82f4c2d9e8a31d93fa06f2743.tar.gz:.

Or a channel:

export NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/nixos-19.03-small.tar.gz:.

nix-shell as an interpreter

The nix manual has a nice section on this.

Older, but longer tutorial on this.

Package script dependencies:

#! /usr/bin/env nix-shell
#! nix-shell -i bash -p opusTools -p graphicsmagick -p file -p parallel -p flac

# do script things as normal, guaranteed the specified tools are available

Run Haskell as a script:

#! /usr/bin/env nix-shell
#! nix-shell -i runghc -p "haskellPackages.ghcWithPackages (ps: with ps; [])"

main :: IO ()
main = putStrLn "Hello World!"

Pairs well with something like turtle, e.g.,

#! /usr/bin/env nix-shell
#! nix-shell -i runghc -p "haskellPackages.ghcWithPackages (ps: with ps; [turtle])"

{-# LANGUAGE OverloadedStrings #-}

import Turtle

data Command = IncreaseVolume Int | DecreaseVolume Int deriving (Show)

parser :: Parser Command
parser
    =   fmap IncreaseVolume
            (subcommand "up" "Turn the volume up"
                (argInt "amount" "How much to increase the volume") )
    <|> fmap DecreaseVolume
            (subcommand "down" "Turn the volume down"
                (argInt "amount" "How much to decrease the volume") )

main = do
    x <- options "Volume adjuster" parser
    case x of
        IncreaseVolume n -> printf ("Increasing the volume by "%d%"\n") n
        DecreaseVolume n -> printf ("Decreasing the volume by "%d%"\n") n

Write your bash scripts…in Haskell!

Example taken from the end of the last section of the Turtle tutorial.

nix + direnv

https://github.com/direnv/direnv/wiki/Nix

Just have a default.nix or shell.nix setup for you project. And an .envrc containing:

use nix

After a direnv allow in the directory, every time you move into the directory you’ll have a nix-shell environment setup for you automatically, in your own familiar shell.

Combine this with an editor plugin for direnv and as you move around in your editor, your project dependencies will be setup for you automatically. Very convenient.

The upstream nix function in direnv is pretty simple. You’ll probably want to use a modified one to speed up direnv, particularly if you have it integrated with you editor. See the section on a persistent and cached shell for nix with direnv.

If you’re feeling adventurous, look into https://github.com/target/lorri instead of using the nix-shell direnv stuff directly.

Language-specific things

Many languages have to tooling to automate packaging with nix, e.g., cabal2nix, pypi2nix, etc. For a more complete list see the wiki page.

This support is uneven across languages, largely dependent on how many nix users there are who give their time to improve and maintain that languages infrastructure. Some languages are also more or less hostile to packaging in the nix way, some have more non-determinism or poor dependency tracking/specification, which complicate making easy tooling for it.

Chapter 9 of the nixpkgs manual has more details on a numbers of different languages, development tips, troubleshooting, etc.

Haskell

The Haskell infrastructure in nix is extensive, the section in the manual is a useful read.

Basic dev environment

Probably the quickest way to get going developing a Haskell package is with haskellPackages.developPackage:

Have a cabal file for your project, then your default.nix can just be:

{ pkgs ? import <nixpkgs> {} }:

pkgs.haskellPackages.developPackage {
  root = ./.;
}

This automatically calls cabal2nix, makes it easy to specify overrides, and will drop you into a development shell when nix-shell is used (no need for a separate shell.nix).

If you want to fix a compiler version, say the default is too new for you:

{ pkgs ? import <nixpkgs> {}
, ghcVersion ? "ghc842"
}:

pkgs.haskell.packages."${ghcVersion}".developPackage {
  root = ./.;
}

There’s a little more on this in the manual, particularly for when you need to override a dependencies packaged version.

A more complete (and complicated) example:

{ pkgs ? import <nixpkgs> {}
, ghcVersion ? "ghc842"
}:

pkgs.haskell.packages."${ghcVersion}".developPackage {
  root = ./.;

  # `source-overrides` are overrides on which source code to fetch for a
  # package, here we can specify *what* to build for a dependency
  source-overrides = {
    # if you need to use a different version from Hackage than what is in
    # the current package set, can just specify the version and it will be
    # fetched automatically (gets turned into a call to the `callHackage`
    # function)
    HUnit = "1.5.0.0";

    # if you need a Haskell package that is not on Hackage, say either an
    # unpublished version or a fork, just fetch the repo
    servant-swagger = pkgs.fetchFromGitHub {
      owner = "haskell-servant";
      repo = "servant-swagger";
      rev = "9c0ceb9f5cd00a47e922ba5de4be5ad591d99a0a";
      sha256 = "1qkpj40sgy0nidd9av4rvd8754lh6zhakdqa05jgkzizf37wfz10";
    };

    # if you have a local checkout of a package, you can specify the path to it,
    # this might commonly be a submoduled package, but you can specify paths
    # outside the current project, though this of course only works for you personal
    # local setup, but sometimes that's all you need
    my-local-package = ../my-local-package;
  };

  # `overrides` are overrides to the package derivations themselves, so it's
  # here we can specify changes to *how* dependencies are built, generally
  # using helper functions from the nix haskell library, see all the options:
  # https://github.com/NixOS/nixpkgs/blob/f3282c8d1e0ce6ba5d9f6aeddcfad51d879c7a4a/pkgs/development/haskell-modules/lib.nix
  #
  # "overriding" a set of things is a common pattern in nix, for clarity here
  # I've used a more verbose style, but:
  #
  # `newHaskellPkgs` is conventionally called `self` and is the overridden set
  # after it's been resolved, which you sometimes need
  #
  # `oldHaskellPkgs` is conventionally called `super`, it's basically the set of
  # things you are overriding
  overrides = newHaskellPkgs: oldHaskellPkgs: {
    # sometimes you need to use a package that has a broken test suite for
    # whatever reason, here in overrides you can use the helper `dontCheck` to
    # skip the test suite when building the package
    hasktags = pkgs.haskell.lib.dontCheck oldHaskellPkgs.hasktags;

    # sometimes packages have overly restrictive version bounds, the
    # `doJailbreak` helper can tell the build to ignore them so it can be built
    # against the versions you have in your package set
    async = pkgs.haskell.lib.doJailbreak oldHaskellPkgs.async;

    # sometimes you need to patch a package to build or work right with you
    # package set
    some-package = pkgs.haskell.lib.appendPatch oldHaskellPkgs.some-package ./fix-some-package.patch;
  };

  # this is a spot you can modify the derivation for the package itself,
  # usually will be applying one haskell lib function like in `overrides`
  modifier = pkgs.haskell.lib.buildStrictly;
}

If you want to add some development tools to the nix-shell environment, one way you could do it is with a shell.nix containing:

{ pkgs ? import <nixpkgs> {} }:
let
  package = import ./default.nix { inherit pkgs; };
in
  package.overrideAttrs (oldAttrs: {
    buildInputs = oldAttrs.buildInputs ++ [
      pkgs.haskellPackages.cabal-install
      pkgs.haskellPackages.ghcid
      # ...
    ];
  })

Though keeping the right compiler version/package set in sync between the dev tools and the package environment from developPackage could be challenging since the package set from developPackage isn’t exposed. I’m not sure as I don’t use this setup.

For this and other reasons I generally recommend the next development setup, but developPackage is quick when you need it.

Improved setup

While haskellPackages.developPackage is pretty easy, for projects with more than one package, or if you just want more flexibility, or explicitness, you can break apart what developPackage is doing.

In your default.nix, extend the Haskell package set to include the project packages:

{ pkgs ? import <nixpkgs> {} }:

let
  haskellPackages = pkgs.haskellPackages.extend (pkgs.haskell.lib.packageSourceOverrides {
    # your Haskell packages for the project
    foo = ./foo;
    bar = ./bar;
    # for a simple project with only one package at the top-level, this might be
    # baz = ./.;
  });
in {
  inherit pkgs haskellPackages;
  # same as if we'd written:
  # pkgs = pkgs;
  # haskellPackages = haskellPackages;

  # make the project package a top level attributes for easier building, e.g.,
  # nix-build -A foo
  #
  # instead of
  #
  # nix-build -A haskellPackages.foo
  foo = haskellPackages.foo;
  bar = haskellPackages.bar;
}

Then utilize the haskellPackages.shellFor function to build a more configurable development environment in your shell.nix:

let
  project = import ./. {};
in
  project.haskellPackages.shellFor {
    # dependencies for the listed packages will all be available in the shell
    packages = p: [p.foo p.bar];

    # build a local hoogle database for all the dependencies of the given
    # packages, the hoogle executable in the shell environment will be set to
    # use it automatically
    # withHoogle = true;

    # list any other things you want available in the shell
    buildInputs = [
      project.haskellPackages.cabal-install
      # project.haskellPackages.ghcid
      # ...whatever other Haskell dev tools you need

      # project.pkgs.niv
      # project.pkgs.gnumake
      # project.pkgs.terraform
      # ...whatever other general dev tools you need
    ];
  }

Adapted from this comment. This is a simplified example, just trying to get the basic idea across.

You can get fancier, automatically pickup the project packages from the cabal.project, and such, see this gist for an example.

See the documentation on haskellPackages.shellFor and haskell.lib.packageSourceOverrides for more on what is actually going on.

A more complete (and complicated) example equivalent to the developPackage one:

{ pkgs ? import <nixpkgs> {} }:

let
  haskellPackages = pkgs.haskellPackages.extend (pkgs.lib.composeExtensions
    # these are "source overrides", that is, overrides on which source code to
    # fetch for a package, here we can specify *what* to build
    (pkgs.haskell.lib.packageSourceOverrides ({
      # your Haskell packages for the project
      foo = ./foo;
      bar = ./bar;

      # if you need to use a different version from Hackage than what is in
      # the current package set, can just specify the version and it will be
      # fetched automatically (gets turned into a call to the `callHackage`
      # function)
      HUnit = "1.5.0.0";

      # if you need a Haskell package that is not on Hackage, say either an
      # unpublished version or a fork, just fetch the repo
      servant-swagger = pkgs.fetchFromGitHub {
        owner = "haskell-servant";
        repo = "servant-swagger";
        rev = "9c0ceb9f5cd00a47e922ba5de4be5ad591d99a0a";
        sha256 = "1qkpj40sgy0nidd9av4rvd8754lh6zhakdqa05jgkzizf37wfz10";
      };

      # if you have a local checkout of a package, you can specify the path to it,
      # this might commonly be a submoduled package, but you can specify paths
      # outside the current project, though this of course only works for you personal
      # local setup, but sometimes that's all you need
      my-local-package = ../my-local-package;
    }))

    # these are overrides to the package derivations themselves, so it's here we
    # can specify changes to *how* things are built, generally using helper
    # functions from the nix haskell library, see all the options:
    # https://github.com/NixOS/nixpkgs/blob/f3282c8d1e0ce6ba5d9f6aeddcfad51d879c7a4a/pkgs/development/haskell-modules/lib.nix
    #
    # "overriding" a set of things is a common pattern in nix, for clarity here
    # I've used a more verbose style, but:
    #
    # `newHaskellPkgs` is conventionally called `self` and is the overridden set
    # after it's been resolved, which you sometimes need
    #
    # `oldHaskellPkgs` is conventionally called `super`, it's basically the set of
    # things you are overriding
    (newHaskellPkgs: oldHaskellPkgs: {
      # you can tweak the build flags for you packages here if needed
      foo = pkgs.haskell.lib.buildStrictly oldHaskellPkgs.foo
      bar = pkgs.haskell.lib.buildStrictly oldHaskellPkgs.bar

      # sometimes you need to use a package that has a broken test suite for
      # whatever reason, here in overrides you can use the helper `dontCheck` to
      # skip the test suite when building the package
      hasktags = pkgs.haskell.lib.dontCheck oldHaskellPkgs.hasktags;

      # sometimes packages have overly restrictive version bounds, the
      # `doJailbreak` helper can tell the build to ignore them so it can be built
      # against the versions you have in your package set
      async = pkgs.haskell.lib.doJailbreak oldHaskellPkgs.async;

      # sometimes you need to patch a package to build or work right with you
      # package set
      some-package = pkgs.haskell.lib.appendPatch oldHaskellPkgs.some-package ./fix-some-package.patch;
    })
  );

in {
  inherit pkgs haskellPackages;
  # same as if we'd written:
  # pkgs = pkgs;
  # haskellPackages = haskellPackages;

  # make the project package a top level attributes for easier building, e.g.,
  # nix-build -A foo
  #
  # instead of
  #
  # nix-build -A haskellPackages.foo
  foo = haskellPackages.foo;
  bar = haskellPackages.bar;

  # say foo is an executable, statically linking it to it's Haskell libs can
  # reduce the closure size and simplify some things, e.g., non-nix deploys,
  # though fully  static Haskell executables take some more work, see:
  # https://github.com/NixOS/nixpkgs/issues/43795
  foo-static = pkgs.haskell.lib.justStaticExecutables haskellPackages.foo;
}

haskell.nix

haskell.nix is an alternate Haskell infrastructure for nixpkgs. Its own motivation section details why it exists, but generally, the goal is to have nix doing more of the work natively (instead of relying on things like cabal2nix), which increases flexibility, control, and simplicity.

I’ve not used it personally, but it’s neat and seems a worthwhile endeavor to experiment with what else is possible.

Other nix-haskell stuff

Learning Nix

https://learnxinyminutes.com/docs/nix/

https://github.com/tazjin/nix-1p/blob/master/README.md

https://nixos.org/nixos/nix-pills/

Nix: Under the hood

Good talk covering the basics of how nix works, simply.

https://youtu.be/GMQPzv3Sx58

Test your NixOS config

nix repl '<nixpkgs/nixos>'

You can poke at your config without having to nixos-rebuild, with TAB complete.

The config attribute holds your system config. pkgs allows you to look at the packages in that version of the system.

https://twitter.com/IotaSamurai/status/1045220406792048640

https://nixos.wiki/wiki/Nix-repl

NixOS options

There are a lot of configuration options supported by NixOS. The options search page on the website is useful to find a) if the thing you want to configure has been packaged as a service in NixOS and b) what knobs you can turn for it.

One nice thing about the search tool is that it links directly to the source file for the service, so you can inspect what is actually happening with the option if you want to.

Note that currently, the website search tool only shows options for the latest stable release, if you are living on the edge, you’ll have to search nixpkgs yourself, either grep a local copy of the repo, search GitHub, or use nixos-option.

nixos-option evaluates your NixOS config and will display all the attributes in a given attribute set available for it. For example:

nixos-option services.postgresql

Will show all the attributes you can set to configure the postgres service. It unfortunately does not display the description when listing the attributes (though hopefully is at least broadly clear in the name itself), but if you specify a specific one, like:

nixos-option services.postgresql.dataDir

Then it will display more complete documentation for it, including the current value in your configuration, the default, a description of what the option is for, and a file path for module it’s declared in.

The website search is nicer, but nixos-option works offline and can be helpful as it does display the currently set value for your configuration. The repl is usually better to explore your evaluated configuration, given it has TAB complete, but sometimes nixos-option is more useful.

Finding Nix packages

nix search <name> or https://nixos.org/nixos/packages.html

Cheatsheet

The NixOS cheatsheet has a bunch of stuff that is useful to look over, including a table comparing how you would do something in Ubuntu and the equivalent command on NixOS.

It also has a bunch of short little snippets about how to do various things, cross-compile a package, and other similar tasks.

Get source hash

So anytime something is fetched in a nix expression, we want the hash of the result (the sha256 attribute) for reproduciblity and security; to know it hasn’t been changed from what we expect (either from a malicious attack or simple error) and to make checking if we even need to go fetch anything in the first place (i.e., we’ve already fetched this version so the source so can just use the local copy).

There are some nix-prefetch-* tools to help with getting that hash, the most commonly used one is probably nix-prefetch-url.

Just give it a URL (usually to a tarball), it will fetch it, add it to the nix store and print the hash for your expression.

For the hash to use in a fetchTarball call, you can just the tool directly.

For the hash to use in a fetchFromGitHub call, you will want to add the --unpack flag, as this will check against the hash of the content of the archive, not the hash of the tarball itself.

For example:

nix-prefetch-url --unpack https://github.com/NixOS/nix/archive/1f795f9f44607cc5bec70d1300150bfefcef2aae.tar.gz

There are other tools, nix-prefetch-git and nix-prefetch-github, equivalent to above would be:

nix run -f '<nixpkgs>' nix-prefetch-github -c nix-prefetch-github --rev 1f795f9f44607cc5bec70d1300150bfefcef2aae NixOS nix

You can also just take another hashOr use lib.fakeSha256 or generate a random hash to use, e.g., uuidgen | sha256sum.

, change one character, then try to build the thing, nix will error telling you the hash it expected, which you can then use and build again. This is hacky, and best to be avoided.

See the relevant sections in the manual.

LaTeX

Arbitrary, one-off environments: nix-shell -E 'with import <nixpkgs> {}; mkShell { buildInputs = [(texlive.combine { inherit (pkgs.texlive) scheme-small standalone latexmk cm-super; })]; }'

Misc.