Functional Thursday - Nix

1. Nix 安裝

想嘗試使用 Nix 不一定要使用 NixOS,可以安裝 Nix 到 Linux 或 Mac 作業系統,就能使用 nix-env 和 nix-shell。

Single User Installation 會使 Nix Store 被執行 install 的使用者擁有,因此全系統只有該使用者可以用 Nix。(Nix 首頁沒提到 Multi User Installation,它藏在 Mamual 裡) (因為 Multi User Installation 比較難移除?)

2. Nix 基本概念

簡報

大部分內容來自 The Purely Functional Software Deployment Model 論文 Part I (重點整理)。

  • 背景
  • Nix Store
  • Nixpkgs
  • NixOS
  • nix-env
  • nix-shell

3. Nix 學習資源

入門 Guide、Cheatsheet

  • Nix Pills
    個人最推薦的入門指南,共有 20 篇短篇文章,從安裝、基本使用、原理,一路涵蓋到語言、如何寫套件、一些架構和設計模式,不僅教你如何用 Nix,讀完之後還能任意暴改你的系統以及包裝你自己的 package。
  • Cheatsheet - NixOS Wiki

Offical Manual

設計哲學

社群?

3. nix-env 基本使用

有沒有人剛剛在安裝 Nix 時遇到問題的?

Nix Pills, Chapter 3. Enter the Environment

4. Nix 語言

開 nix repl 玩一下 Nix 語言。

  • Primitives
    • 特別: path
  • Let Expressions
  • Identifier
    • 特別: identifier 可以包含 -,因為很多套件名稱裡面都有 -。但語言慣例還是用 camelCase。
  • Strings
    • Mulit line string
  • Lists
  • Set
  • Functions
  • Laziness
  • builtins.trace
  • Import

5. Writing Derivations

Hello world 範例

// hello.c
#include <stdio.h>

int main(int argc, char const *argv[])
{
    printf("Hello, Nix World!\n");
    return 0;
}
let
  pkgs = import <nixpkgs> { };
in

derivation {
  name = "hello";
  system = "x86_64-linux";
  builder = "${pkgs.bash}/bin/bash";
  args = [
    "-c"
    ''
      export PATH="${pkgs.coreutils}/bin:${pkgs.gcc}/bin"
      mkdir $out
      gcc "${./hello.c}" -o "$out/hello"
    ''
  ];
}
  • 描述如何 build 一個 Nix component 的 expression,在 Nix 語言下叫做 derivation。"Derivation: This is Nix-speak for a component build action, which derives the component from its inputs." 《The Purely Functional Software Deployment Model 》,p. 27。
  • 上例是最原始的 derivation 寫法,一般很少看到這樣寫,因為很多 build 環境都是通用的,所以定義套件時幾乎都用已經設好各種預設值的 high-level function,nixpkgs.stdenv.mkDerivation
  • <nixpkgs> 是 Nix 官方維護的 components 定義集,就像 Nix 的標準函式庫。
  • 注意連 builder 是哪個 shell 都是 build inputs 的一部分。
  • pkgs.bash 是個變數,轉成 string 會變成 build 的輸出路徑 (out path),所以我們可以寫 "${pkgs.bash}/bin/bash"
nix-repl> pkgs = import <nixpkgs> { }
nix-repl> "${pkgs.bash}"
"/nix/store/b9p787yqaqi313l9rr0491igjwyzqfmw-bash-4.4-p23"
  • 但是 Nix 的 derive 也是 lazy 的,所以 "/nix/store/b9p787yqaqi313l9rr0491igjwyzqfmw-bash-4.4-p23 不一定會真的存在,只是我們知道如果有被 build 好,就會在這個路徑下。

Makefile replacement (?)

寫過 Makefile 嗎?

// hello.c
#include <stdio.h>
#include "message.h"

int main(int argc, char const *argv[])
{
    printf("%s", message);
    return 0;
}
// message.h
extern const char message[];
// message.c
const char message[] = "Hello, Nix World!\n";

Makefile:

all: hello

hello: hello.c message.o
	gcc -I./ hello.c message.o -o hello

message.o: message.c message.h
	gcc -c message.c -o message.o

可以用 Nix 改寫:

let
  pkgs = import <nixpkgs> {};

  message = derivation {
    name = "message";
    system = builtins.currentSystem;
    builder = "${pkgs.bash}/bin/bash";
    args = [
      "-c"
      ''
        export PATH="${pkgs.coreutils}/bin:${pkgs.gcc}/bin"
        mkdir $out
        cp ${./message.h} $out/message.h
        gcc -c ${./message.c} -o $out/message.o
      ''
    ];
  };

  hello = derivation {
    name = "hello";
    system = builtins.currentSystem;
    builder = "${pkgs.bash}/bin/bash";
    args = [
      "-c"
      ''
        export PATH="${pkgs.coreutils}/bin:${pkgs.gcc}/bin"
        mkdir $out
        echo ${message}
        gcc -I${message} ${./hello.c} ${message}/message.o -o $out/hello
      ''
    ];
  };
in

hello

Real world Derivations

  • Generic Builders: stdenv.mkDerivations, etc.
  • 各種語言的 package manager 也會有人做出程度不一的 nix 版⋯⋯
    • Rust:
      1. buildRustPackage: 呼叫 Cargo,把整個專案的 dependencies 裝成一包 package⋯⋯
      2. buildRustCrate: 用 Nix 取代 Cargo 的安裝 package 部分:首先用 Cargo 取得專案的 depencies 列表,透過 Carnix 把列表轉換成 nix expression,用 Nix 把每個 dependency (crate) 安裝成 nix store 裡的 package,再拿 nix store 裡的那些 package 構築出原本 Cargo 會提供的 shell 環境。我覺得是比較漂亮、有充分利用 Nix 的解法。
    • Ruby: BundlerEnv (類似 2. buildRustCrate 的做法)
    • Nodejs:
      1. node2nix (類似 1. buildRustPackage) (?)
      2. 我寫的 npmjs2nix (類似 2. buildRustCrate 的做法)
        • npmjs2nixpackage-lock.json 轉成 npm-package.nix
        • mkNodePackageWithRuntime 建出 Node.js 執行環境 (主要是 NODE_PATH)。

6. Nix Shell

direnv (Nix · direnv/direnv Wiki · GitHub)

7. NixOS 實際使用

8. Nix User Packages and Nixpkgs design patterns

9. cache and CI

Select a repo