Nixpkgs Overlays

Nixpkgs Overlays

This Presentation

  • Why adding overlays?
  • How to use them?
  • How do they work?
  • What can we do with them?

NixOS Is Awesome

  1. Declarative
  2. Composable

Nixpkgs before Overlays

  1. Declarative
  2. Composable

Before Overlays

  • packageOverride
  • overridePackages
  • import <nixpkgs> {} // { … }

All replaced by Overlays!

Overlays are Composable

Composable (i.e. f · g · h) Implies:

  1. Same syntax for all overlays.
  2. Replace/Add/Remove packages without Nixpkgs modifications.
  3. List overlays under ~/.config/nixpkgs/overlays/.

Empty Overlay

self: super:

{
}     

Nixpkgs Internal

stdenvAdapters = self: super: …;
trivialBuilders = self: super: …;
stdenvBootstappingAndPlatforms = self: super: …;
platformCompat = self: super: …;
splice = self: super: …;
allPackages = self: super: …;
stdenvOverrides = self: super: …;
configOverrides = self: super: …;

Examples

self: super:

{
  google-chrome = super.google-chrome.override {
    commandLineArgs =
      ''--proxy-server="https=127.0.0.1:3128;http=127.0.0.1:3128"'';
  };
}     

https://spof.px.io/~alex/posts/2017-05-15-nixos-overlay.html

Examples

self: super:

{
  nix = super.nix.override {
    storeDir = "${<nix-dir>}/store";
    stateDir = "${<nix-dir>}/var";
  };
}     

http://yann.hodique.info/blog/nix-in-custom-location/

Examples

self: super:

{
  VidyoDesktop =
    super.callPackage ./pkgs/VidyoDesktop { };
}     

https://github.com/mozilla/nixpkgs-mozilla/vidyo-overlay.nix

2 Arguments

  • self: Fix-point result.
  • super: Result of the composition before this file

Self

  • Used for derivation result.

Super

  • Used for functions. (callPackage, lib, …)
  • Used to override recipes.

Bad Example (2 issues)

self: super: rec {
  fakeClosure = self.writeText "fake-closure.nix" ''
    …
  '';
  fakeConfig = self.writeText "fake-config.nix" ''
    (import ${fakeClosure} {}).config.nixpkgs.config
  '';
}     

https://github.com/NixOS/nixpkgs/issues/25264

Bad Example (1 issues)

{ python }:
self: super:
{
  "execnet" =
    python.overrideDerivation super."execnet" (old: {
      buildInputs = old.buildInputs
        ++ [ self."setuptools-scm" ];
    });
}     

https://github.com/garbas/pypi2nix#convert-generated-requirements-nix-into-nixpkgs-overlay

Bad Example (2 issues)

{ pkgs ? import <nixpkgs> {} }:
let
  projectOverlay = self: super: {
    customPythonPackages =
      (import ./requirements.nix {
        inherit pkgs;
      }).packages;
  };
in
  import pkgs.path {
    overlays = [ projectOverlay ];
  }   

https://stackoverflow.com/… https://github.com/garbas/pypi2nix

Better Example

shell.nix:

{ pkgsPath ? <nixpkgs> }:
import pkgsPath {
  overlays = [ import ./default.nix; ];
}   

default.nix:

self: super:
{
  customPythonPackages =
    (import ./requirements.nix {
      inherit self;
    }).packages;
}     

Bad Example (1 issue)

self: super:
let inherit (super) callPackage; in
{
  radare2 = callPackage ./pkgs/radare2 {
    inherit (super.gnome2) vte;
    lua = super.lua5;
  };
}     

https://gitli.stratum0.org/tnias/overly/blob/master/overly-overlay.nix

Compose function


let
  foo = { … };
  bar = { … };
in
  foo // bar
      

Compose function


let
  foo = super: …;
  bar = super: …;
in
  foo {} // bar (foo {})
      

Compose function


let
  foo = super: …;
  bar = super: …;
  extend = lhs: rhs: lhs // rhs lhs;
in
  extend (extend {} foo) bar;
      

Compose function

lib:
let
  foo = super: …;
  bar = super: …;
  extend = lhs: rhs: lhs // rhs lhs;
in
  lib.foldl extend {} [ foo bar ];
      

Compose function

lib:
let
  foo = self: super: …;
  bar = self: super: …;
  extend = lhs: rhs: lhs // rhs lhs;
in
  lib.fix (self:
    lib.foldl extend {} (
      lib.map (x: x self) [ foo bar ]));
      

Aggregate

self: super:
let
  extend = lhs: rhs: lhs // rhs lhs;
  compose = list:
    super.lib.foldl extend super (
      super.lib.map (x: x self) list);
in
  compose [
    (import ./foo-overlay.nix)
    (import ./bar-overlay.nix)
  ]   

https://github.com/mozilla/nixpkgs-mozilla/blob/master/default.nix

Bad Example (1 issue)

self: super:
{
  lib = {
    firefoxVersion = … ;
  };
  latest = {
    firefox-nightly-bin = … ;
    firefox-beta-bin = … ;
    firefox-bin = … ;
    firefox-esr-bin = … ;
  };
}

Always Extends

self: super:
{
  lib = (super.lib or {}) // {
    firefoxVersion = … ;
  };
  latest = (super.latest or {}) // {
    firefox-nightly-bin = … ;
    firefox-beta-bin = … ;
    firefox-bin = … ;
    firefox-esr-bin = … ;
  };
}

https://github.com/mozilla/nixpkgs-mozilla/blob/master/firefox-overlay.nix

Conventions

  • { /* reproducible packages */ }
  • { lib = /* utility functions */; }
  • { latest = /* builtins.fetchurl */; }
  • { latest = /* imports from derivation */; }
  • { devEnv = /* nix-shell environments */; }

Conventions

What Can We Do?

  • Fetch from the network.
  • Parse files containing checksums.
  • Generate derivation content.

Firefox (Fetch & Parse)

with builtins; let
  firefox_versions =
    fromJSON (readFile (fetchurl
      https://product-details.mozilla.org/1.0/firefox_versions.json));

  …

  chksum = "${dir}/firefox-${version}.en-US.${system}.checksums";
  fileContent = readFile (fetchurl chksum);

  # file content:
  # <hash> sha512 62733881 firefox-56.0a1.en-US.linux-x86_64.tar.bz2
  # <hash> sha256 62733881 firefox-56.0a1.en-US.linux-x86_64.tar.bz2
  file = "firefox-${version}.en-US.${system}.tar.bz2";
  sha512 = head (match ".*[\n]([0-9a-f]*) sha512 [0-9]* ${file}[\n].*"
    fileContent);
in
  …

Rust (Fetch)

let
  # See https://github.com/rust-lang-nursery/rustup.rs/blob/master/src/rustup-dist/src/dist.rs
  defaultDistRoot = "https://static.rust-lang.org";
  manifest_v1_url = {
    dist_root ? defaultDistRoot + "/dist",
    date ? null,
    staging ? false,
    # A channel can be "nightly", "beta", "stable", "\d{1}.\d{1}.\d{1}", or "\d{1}.\d{2\d{1}".
    channel ? "nightly"
  }:
    if date == null && staging == false
    then "${dist_root}/channel-rust-${channel}"
    else if date != null && staging == false
    then "${dist_root}/${date}/channel-rust-${channel}"
    else if date == null && staging == true
    then "${dist_root}/staging/channel-rust-${channel}"
    else throw "not a real-world case";

  manifest_v2_url = args: (manifest_v1_url args) + ".toml";
in
  …

TOML Sample

[pkg.rust.target.x86_64-unknown-linux-gnu]
available = true
url = "https://…/rust-nightly-x86_64-unknown-linux-gnu.tar.gz"
hash = "b4329a83a909e279b32f1ee84512cc6ef9a5197347ed527bdc8781e956e44bc5"

[[pkg.rust.target.x86_64-unknown-linux-gnu.components]]
pkg = "rustc"
target = "x86_64-unknown-linux-gnu"

Rust (TOML Tokenizer)

let
  layout_pat_opt = "[ \n]*";
  token_pat = ''=|[[][[][a-zA-Z0-9_."*-]+[]][]]|[[][a-zA-Z0-9_."*-]+[]]|[a-zA-Z0-9_-]+|"[^"]*"'';
  tokenizer = str:
    let
      # Nix 1.12 has the builtins.split function which allow to tokenize
      # a file quickly by iterating with a simple regexp.
      layoutTokenList = split "(${token_pat})" str;
      isLayout = s: match layout_pat_opt s != null;
      filterLayout = list:
        filter (s:
          if isString s then
            if isLayout s then false
            else throw "Error: Unexpected token: '${s}'"
          else true) list;
      removeTokenWrapper = list:
        map (x: assert tail x == []; head x) list;
    in
      removeTokenWrapper (filterLayout layoutTokenList);

in
  …

Rust (TOML Parser)

let
  parserInitState = { idx = 0; elem = {}; };
  readToken = state: token:
    # assert trace "Read '${token}'" true;
    if state.idx == 0 then
      # … skip for slide size issues …
    else if state.idx == 1 then
      assert token == "=";
      state // { idx = 2; }
    else
      assert state.idx == 2;
      state // {
        idx = 0;
        elem = state.elem // {
          "${state.name}" = tokenToValue token;
        };
      };

  # Mutate the state while iterating over the list of tokens.
  parser = str:
    foldl' readToken parserInitState (tokenizer str);
in
  …

Rust (Dispatch)

let
  # Intersection of rustup-dist/src/dist.rs listed platforms
  # and stdenv/default.nix.
  hostTripleOf = system: { # switch
    "i686-linux"      = "i686-unknown-linux-gnu";
    "x86_64-linux"    = "x86_64-unknown-linux-gnu";
    "armv5tel-linux"  = "arm-unknown-linux-gnueabi";
    "armv6l-linux"    = "arm-unknown-linux-gnueabi";
    "armv7l-linux"    = "armv7-unknown-linux-gnueabihf";
    "aarch64-linux"   = "aarch64-unknown-linux-gnu";
    "mips64el-linux"  = "mips64el-unknown-linux-gnuabi64";
    "x86_64-darwin"   = "x86_64-apple-darwin";
    "i686-cygwin"     = "i686-pc-windows-gnu"; # or msvc?
    "x86_64-cygwin"   = "x86_64-pc-windows-gnu"; # or msvc?
    "x86_64-freebsd"  = "x86_64-unknown-freebsd";
  }.${system} or (throw "Rust overlay does not support ${system} yet.");
in
  …

Nixpkgs after Overlays

  1. Declarative
  2. Composable

Next: S.O.S.

Simple Override Strategy:

  • Recusive update operator.
  • Declarative packages.
  • Type friendly syntax.

Questions?