Nix
https://scrapbox.io/files/6770dc2794cc9029a81dde40.png
What is Nix?
純粋関数型パッケージマネージャ
Nix, the purely functional package manager
再現性、宣言的、信頼性
10 年以上の歴史を持つ成熟したツール
A package manager keeps track of what software is installed on your computer, and allows you to easily install new software, upgrade software to newer versions, or remove software that you previously installed.
パッケージには、以下のようなものが含まれる
実行可能ファイル
共有ライブラリ
静的アセット(テキスト、音声、フォント、etc...)
パッケージマネージャを経由してソフトウェアをインストールすると、いい感じの場所にいい感じに配置してくれる
実行可能ファイル -> /usr/bin
共有ライブラリ -> usr/lib
静的アセット(テキスト、音声、フォント、etc...) -> usr/share
プログラミング言語のパッケージマネージャの場合、パッケージは専用のディレクトリで一元管理される場合が多い
Nix が必要な理由
依存関係の衝突が原理的に発生しない
暗黙的依存がないことを機械的に保証
Re: What is Nix?
再現可能(Reproducible)
宣言的(Declarative)
信頼できる(Reliable)
純粋関数的ビルド
「再現性(Reproducible)」を実現する要
数学の関数とプログラミングの関数をは全く異なる概念
処理の集まり
数学の関数は、「入力が定まると出力が一つに定まる」関係
プログラミングの文脈で数学の関数の話をしたいときに #純粋関数 という 純粋関数として関数が扱われるようなプログラミング言語のことを #純粋関数型言語 という 特徴
副作用がない
参照透過性がある
純粋関数的ビルド
理想的なビルドシステムが備えるべき性質はぴったり純粋関数の性質に当てはまる
パッケージのビルドを関数に見立てると、
$ f(ビルドの入力) = ビルド成果物
ビルドの入力
依存関係
ソースコード
ビルドスクリプト
環境変数
システムアーキテクチャ
etc...
暗黙的依存はビルドの副作用
Nix では明示的にビルドの入力として指定したものだけがビルドに影響を及ぼす
外部の要素は一切干渉することができない
参照透過性を持つ
同じビルド入力に対して、常に同じビルド成果物を返す
https://scrapbox.io/files/677121ef8df54491dbd56ecc.png
サンドボックスとはホストシステムから隔離・保護された環境
Nix は入力として指定されたものだけをサンドボックス内に導入、ビルドを実行
サンドボックス外へのアクセスは副作用として制限されている
インターネットアクセスも禁止されている
副作用
レスポンスの冪等性も保証されていないため
Nix のビルドシステムには Fetcher という脱出口的な機能がある
再現性を損なわずにインターネットからリソースを取得する仕組み
Fetcher を使う前にダウンロード予定のソースのハッシュ値を指定
もし異なる場合には即座にビルドを失敗させる
ローカルストア(Nix ストアの一種)
依存関係を共有可変状態ではなく、不変の分離された状態で管理
依存関係の衝突を回避
Nix パッケージ管理機構
実体は /nix/store というディレクトリ
Nix でビルドしたものはすべてこのディレクトリに格納される
格納されたものは Store Object と呼ばれる
e.g. curl
/nix/store/dzs2chgxcwzpwplcw6wvv8nzkn01yr7y-curl-8.6.0-bin/bin/curl に格納されている場合
code:text
# (ハッシュ値)-(パッケージ)の形
dzs2chgxcwzpwplcw6wvv8nzkn01yr7y-curl-8.6.0-bin
# ハッシュ
dzs2chgxcwzpwplcw6wvv8nzkn01yr7y
# パッケージ
curl-8.6.0-bin
code:md
/nix/store/dzs2chgxcwzpwplcw6wvv8nzkn01yr7y-curl-8.6.0-bin/
└── bin/
└── curl
https://scrapbox.io/files/677124bd6e2252c16be5ab17.png
ハッシュ値のおかげで依存関係の衝突を防ぐことができる
code:text
/nix/store/0mjq6w6cx1k9907vxm0k5pk7pm1ifib3-curl-8.4.0-bin
/nix/store/c58hy8bh832hd9m4hkslk71zl98g7h7n-curl-8.2.1-bin
/nix/store/dzs2chgxcwzpwplcw6wvv8nzkn01yr7y-curl-8.6.0-bin
/nix/store/j1yhiywlyh13ayzx46lzh7h1y7cq9p9c-curl-8.5.0-bin
/nix/store/vcvcpdn0bspcl722qkwp2s72wws9gw7s-curl-7.72.0-bin
/nix/store/x23aqwc39pp4zx5iiz0mqyh5mnvrz43z-curl-8.6.0-bin
依存関係の管理
ビルド時依存
ビルドの入力を名前やバージョンといった曖昧なものは使わない
ストアパスで指定する
これによりビルドの再現性が保証される
ビルドシステムによる暗黙的依存の排除によって完全な依存関係のツリーが構築される
実行時依存
ストアパスが指定される
グローバルなファイルパスを参照せず、個別で一意なストアパスを指定する
トレードオフ
Nix ストアは厳密にパッケージを区別するためストレージを圧迫しがち
依存関係地獄の一種
Nix ストア
ローカルストア
SSH ストア
ダミーストア
ローカルデーモンストア
バイナリキャッシュストア
「Nix ストア」はローカルストアのことを指すことが多い
ストアパスと決定的ビルド
ビルドの入力を元にハッシュ値を生成し、ビルド成果物の識別子(ストアパス)としている
逆にストアパスが同じであれば同じビルド成果物が得られる
つまりビルド実行時にすでに同じストアパスが存在していれば、ビルド実行しなくても OK だからスキップ
Nix のビルドは再現可能、つまり予測可能
このようなビルドのことを決定的ビルドという
Substituter
ローカルストアに加えて Substituter という追加の Nix ストアを利用可能
Substituter を利用した場合の Nix のビルドの手順になる
1. ストアパスの生成
2. ローカルストアにビルド済みのストアオブジェクトが存在するか確認
存在すればビルドをスキップ
3. Substituter にビルド済みストアオブジェクトが存在するか確認
存在すればビルドをスキップ
Substituter のストアオブジェクトをローカルストアに取得
4. ローカルストアにも Substituter にもどちらもにも存在しない場合はビルド実行
Substituter はローカルストアを拡張するように振る舞う
バイナリキャッシュストア
バイナリキャッシュストア はビルド済みストアオブジェクト(バイナリキャッシュ)の提供に特化した内部フォーマットを持つ Nix ストア
バイナリキャッシュストアにも種類がある
ローカルバイナリキャッシュストア
HTTP バイナリキャッシュストア
S3 バイナリキャッシュストア
バイナリキャッシュストアを Substituter に指定すると、ローカルでビルドすることなく直接ビルド成果物をダウンロード可能
Nix 公式パッケージリポジトリ #Nixpkgs は https://cache.nixos.org/ で Nixpkgs のビルド済みのバイナリを提供している Nixpkgs のバイナリキャッシュストアは、デフォルトで Substituter に追加されている
Nixpkgs からのパッケージインストールは非常に高速
Substituter とバイナリキャッシュストアによって、ソースコードをローカルビルドマシンで得られる結果と、インターネットから直接ビルド済みバイナリを取得して得られる結果が等しい
Nix Archive
Nix のビルドシステムは決定的である一方、一般的なアーカイブフォーマットは非決定的
パディングを追加
ファイルをソートしない
タイムスタンプを追加
Nix ストアでハッシュ計算を行うためには、シリアライズがビットレベルで一意でなければならない
決定的なアーカイブフォーマットとして NAR が開発された
Nix のバイナリキャッシュストアをホスティングできる CI サービス
セルフホスト可能なバイナリキャッシュサーバ
nix copy
ストアオブジェクトを別の Nix ストアにコピーするコマンド
たとえどんなに複雑な依存関係であっても、Nixが完全な依存関係ツリー(Closures)を把握しているため安全に移送可能
リモートビルド
1. ローカルマシンでビルドの入力を指定
2. リモートマシンでビルド実行
3. リモートマシンのビルド結果をローカルマシンにコピー
ビルドプロセスだけをリモートマシンに委譲できる
Delivation
Nix ではビルド時に Derivation を使用する
簡単に言えば、パッケージのビルド手順に関するとても厳密なレシピ
ビルドシステムに与える入力や出力先のストアパスが情報として含まれている
store derivation
Derivation の低レベル表現
.drv ファイルのこと
通常のパッケージ同様、ストアオブジェクトとして一意なストアパスが与えられている
実際に Nix でパッケージをビルドする際は、高レベル表現である Nix 式(Nix expression) から store derivation を生成する
Nix 言語
Nix 式の記述には Nix 言語を用いる
Nix のコンセプト「宣言的」の実現のための #DSL Nix 言語でパッケージを定義して store derivation を生成すれば、あとは Nix が自動的に依存関係を解決し、ビルドを実行してくれる
宣言的ビルド
Nix 言語が store derivation にコンパイルされるわけではない
Nix 言語の用途
パッケージ定義
開発環境の定義
その他(サードパーティ)
Instantiation
Nix 式から store derivation を生成することを Instantiation という
Nix 言語のビルトイン関数 derivation を使用する
Nix 言語の特徴
純粋関数型言語
遅延評価
ドメイン特化の機能
動的型付け
Nix 言語のデータ型
プリミティブ
文字列
数値
パス
論理値
Null
リスト
Attribute Set
パス
Nix 言語ではファイルパスを文字列ではなく、1 つのデータ型として扱う
./ から始まるパスは、このパスが記述された Nix 言語ファイルからの相対パス
#UNIX のファイルパスと同じように扱うことができる ./example.txt, ../exapmle.txt
Attribute Set(AttrSet)
名前と値のペアの集合
構造体やオブジェクトに似た概念
code:nix
{
x = 1;
y = 2;
}
recursive を意味する rec キーワードを用いて、AttrSet 内の値を参照可能
code:nix
rec {
x = 1;
y = x;
}
. でフィールドにアクセス
code:nix
# a = { x = 1; y = 2; };
a.x # -> 1
Nix 言語の関数
Nix 言語では 1 ファイルが 1 つの関数になっている必要がある
code: nix
引数: 返り値
code:nix
number: number + 1;
関数を評価するには関数名にスペースを開けて引数を指定
code:nix
add number: number + 1;
add 1;
分割代入
code:nix
# args = { x = 1; y = 2; } の場合、返り値は { x = 2; y = 1; }
args: {
x = args.y;
y = args.x;
}
引数が AttrSet であれば分割代入が可能
code:nix
# args = { x = 1; y = 2; } の場合、返り値は { x = 2; y = 1; }
{ x, y }: {
x = y;
y = x;
}
... で引数の不要な部分を無視できる
code:nix
# 引数が{ x = 1; y = 2; } の場合、返り値は { y = 1; }
{ x, ... }: {
y = x;
}
let-in 構文
関数ないで変数を宣言
code:nix
# 引数が { x = 1; y = 2; } の場合、返り値は { a = 2; b = 3; }
{ x, y }: let
add = number: number + 1;
a = add x;
b = add y;
in {
a = a;
b = b;
}
定数
引数がない場合は定数ファイルになる
code:nix
123
code:nix
{
x = 1;
y = 2;
}
ビルトイン関数
readFile
ファイルを文字列として読み込む関数
引数にはパスを指定する
code:nix
readFile <パス>
Nix のビルトイン関数は基本的に読み取り専用
書き込み系の関数はない
(derivation 関数が Nix ストアへの書き込みを発生させる)
fetchurl
最も原始的な Fetcher
code:nix
fetchurl {
url = "<URL>";
sha256 = "<ダウンロード予定のコンテンツのSHA256ハッシュ>";
}
fromJSON, fromTOML, toJSON, toXML
import 関数
import は外部の Nix ファイルの関数を呼び出すビルトイン関数
code:md
./
├─main.nix
└─sub/
├─default.nix
└─imported.nix
code:imported.nix
{ x, y }: {
a = x;
b = y;
}
code:main.nix
let f = import ./sub/imported.nix;
in
f { x = 1; y = 2; }: # -> { a = 1; b = 2; }
default.nix
default.nix は特別なファイル
エントリーポイント的な
code:md
./
├─main.nix
└─sub/
├─default.nix
└─imported.nix
import でファイルではなく、ディレクトリを指定するとそのディレクトリに含まれる default.nix を import する
code:main.nix
import ./sub { x = 1; y = 2; }
derivation 関数
AttrSet を返す
副作用として store derivation を生成する
副作用のために Nix 言語の純粋性が損なわれるように見えるが、Nix 言語と Nix ストアによっていい感じに内部動作が隠蔽されているため見かけ上は純粋が保たれているそう
code:nix
derivation {
name = "<パッケージ名>";
system = "<システムアーキテクチャ>";
builder = "<ビルドを実行する実行可能ファイルのパス>";
}
derivation はプリミティブな関数なので、実際のビルドの際には Nixpkgs から提供されているライブラリを用いる
Nixpkgs
公式の パッケージライブラリ
GitHub リポジトリで管理
Nixpkgs の実体
Nixpkgs 自体が 1 つの Nix 式
実際に Nixpkgs を利用する際には Nix 言語で Nixpkgs をインポートする
言ってしまえば、Nixpkgsは「パッケージのビルド用関数が約 9 万個収録された Nix 言語のライブラリ」
Flakes
Nix 言語の依存関係管理システム
https://scrapbox.io/files/6771e97a0e8746365ef81653.png
Flakes は Nix の高レベル表現世界の依存関係管理システム
Flake
Flakes では、Nix 言語のパッケージに相当するものを Flake と呼ぶ
Flakes は Git の併用を前提としている
Flake の実体は flake.nix という特別なファイルをルートには位置した git リポジトリ
package.json, Cargo.toml, pyproject.toml のような立ち位置
flake.nix は Nix 言語のエントリポイントとして振る舞う
構造はとてもシンプル
プロジェクトが依存する Flake(Inputs)
プロジェクトが出力する Nix 式(Outputs)
Flake を評価したとき、Nix は inputs に指定されている Flake を取得し、それらの ouputs を現在評価している Flake の outputs の引数に渡す
依存解決もシンプル
依存関係の固定
flake.lock ファイルで inputs のバージョンを固定
Flakes は Git のコミットハッシュによって依存関係を固定
つまり、Flake(≒パッケージリポジトリ)のバージョンがコミットレベルで同一
同じ Nix 式は同じ Derivation を出力し、同じ Derivation は同じパッケージを出力する
inputs
code:nix
{
inputs = {
# GitHubリポジトリを指定
example-github.url = "github:オーナー/リポジトリ/ブランチ";
# Flakeのアーカイブを指定
# Flakeのローカルディレクトリを指定
example-directory.url = "path:/path/to/flake";
};
}
基本的には GitHub リポジトリを指定する
URL でアーカイブを指定することもできる
outputs
code:nix
{
inputs = {
# 依存するFlake
};
outputs = inputs: {
packages."<システムアーキテクチャ>"."<パッケージ名>" = derivation;
devShells."<システムアーキテクチャ>"."<devShellの名前>" = derivation;
formatter."<システムアーキテクチャ>"."<パッケージの名前>" = derivation;
templates."<テンプレートの名前>" = {
path = "<ストアパス>";
description = "テンプレートの説明";
};
# ...その他多数
};
}
packages
nix build
nix run
devShells
宣言的な開発環境構築機能
nix develop
すでにホストマシンにインストールされているパッケージをそのまま利用可能
開発環境では純粋性よりも利便性を優先
ビルド時には純粋な環境
フォーマッタ
nix fmt