Nix Package Manager の学習ログ
Nixの学習ログ。
Nix とはなにか?
- 純粋関数型パッケージマネージャ
- 純粋関数型言語でパッケージを記述する
- パッケージのビルドには暗黙的な入出力が存在しない
- システムのCコンパイラを使ったりしない
- ネットワーク上からファイルを落としたりしない
- 再現性の高いビルドを可能にする
インストール
普通の Linux システムなら sudo
可能なユーザで
curl -L https://nixos.org/nix/install | sh
最近の macOS ではルートファイルシステムが read-only になってるので手間がいろいろかかる。割愛。
Nix Store
Nix のパッケージとかは Nix Store に保存される。Nix Store は通常 /nix/store
である。Nix をもりもり使うようになるとここには大量のディレクトリが生成されるようになる。
手元のマシンで ls /nix/store | rg nodejs
してみた。その結果がこちらである。
257nngj1zpq2q3mnri4gr04givg2asrd-nodejs-10.19.0
2zgcxd0x487a98avbmnxmj1w8dplbhdr-nodejs-12.21.0
487v2h4zrqd12p9igc0cvm4gadkdx12i-nodejs-14.16.0
4ag2rpqjhyw3ycgf0dqca41abw3r6pq3-nodejs-15.11.0.drv
8fjdlhvajz4xbm9l3dn0xylga3ypsg6b-nodejs-14.15.1
d9gj8yajh49mhbfw1r27vajnw4bhnvh8-nodejs-15.12.0
dnk5123ynnz3x1cfgjdf661s26lr2qim-nodejs-15.11.0
gdskfb8af4689gcfj1c8i4vx0rgxcfkn-nodejs-15.12.0.drv
gwd2jf747lyjs3s02nd0g7llj4dg5qmy-nodejs-12.21.0.drv
j0n10j33k39n1sp78rmbcxhybpk5apwb-nodejs-14.15.3
j779rh2xzm8pb63g128w3a9ap9q6crv0-nodejs-14.16.0.drv
k7apd59j662ph6ijz2xv72h92k12kd5h-nodejs-14.15.1.drv
kir7s4ygwx4ff75a6pflvzxi8r6k2hf5-nodejs-14.16.0.drv
qyj7rg46f14jq37jbsvx63px4vmfxi0b-nodejs-10.19.0.drv
rwh3c0v1irhnclzlxyyb784agjgnxfmy-nodejs-14.15.3.drv
xcnr6wn2b3ndgfys3xwjdcd4cgchq6v5-nodejs-14.16.0
これらのディレクトリの先頭にあるサイバー感のある文字列は、パッケージの依存関係グラフを暗号学的ハッシュ関数に突っ込んだものらしい。よく見てみると、Node.js 14.16.0 の中でも 487v2h4zrqd12p9igc0cvm4gadkdx12i-nodejs-14.16.0
と xcnr6wn2b3ndgfys3xwjdcd4cgchq6v5-nodejs-14.16.0
という種類があることがわかる。これはつまり、何らかの依存関係が異なるということ。依存関係が異なるものを別々に管理することで、依存関係が複雑化して地獄が生まれるのを避けられる。
確かに自分は Node.js をよく使っているけれども、流石にこんな大量の Node.js を管理する趣味はない。それに何よりこんなに同一パッケージのかぶりが存在するとストレージが圧迫されてしまう。なので nix-collect-garbage
というコマンドが用意されている。これをするとどのパッケージからも使われていないパッケージを自動で削除できる。これも依存関係がすべて明示的に記述されていることの利点だろう。
GCC でコンパイルする
Cでプログラムを書いてコンパイルしてみる。コードはなんの変哲もない Hello world。
#include<stdio.h>
int main (void) {
printf("Hello, Nix!\n");
return 0;
}
ビルドするための Nix Derivation は default.nix というファイル名にすることが多い。
{ pkgs ? import <nixpkgs> {} }:
pkgs.stdenv.mkDerivation {
name = "hello-nix";
builder = ./builder.sh;
src = ./main.c;
}
このファイルは1つの関数である。引数と本体を分けるのは :
。{ pkgs ? import <nixpkgs> }
は JavaScript では ({ pkgs = require("nixpkgs") })
に相当する。Set―JS ならオブジェクトと呼ばれる―をパターンマッチングで分解し、pkgs
のデフォルト値として <nixpkgs>
をインポートしたものを使う。ちなみに import
は専用の構文ではなく組み込み関数。さすが純粋関数型パッケージマネージャというべきか。
pkgs.stdenv.mkDerivation
はパッケージ (derivation) を作る関数。引数として Set を渡している。Set のキーと値は =
で区切り、;
が必要。.builder.sh
と ./main.c
はパスらしい、文字列ではないみたいで、パッケージを扱う言語ならではかも。
パッケージをビルドする際には builder.sh
が実行される。
source $stdenv/setup
gcc $src -o hello-nix
mkdir -p $out/bin
cp ./hello-nix $out/bin
このスクリプトはシステムから隔離された環境で実行される。最初に source
しているのは基本的な環境変数。$src
とか $out
みたいなものが入っているはず。そして gcc でコンパイルしたファイルを出力ディレクトリにコピーして終わり。stdenv.mkDerivation
でビルドする場合はビルドする環境に gcc が自動的に用意される。
ビルドするにはこれらのファイルが入ったディレクトリで
nix-build
する。ファイル名を指定しないと default.nix
を実行する規約になっている。ビルドされたら result/bin/hello-nix
を実行してみよう。Hello, Nix!
と出力されるはず。result
はビルドされたパッケージが入っている Nix Store へのシンボリックリンクになっている。readlink
でたどると /nix/store/f0nbi18xpz8vbc86ba8kks6rb8r156xi-hello-nix
であることがわかった。
nix-shell
nix-shell という特殊な Bash が使える。これはそして新しいソフトを試したりするのに便利。nix-shell -p nodejs-15_x
とすると Node.js 15 のパスが通ったシェルが起動する。
たとえば突然さっきの nix-hello を gcc ではなく Clang でコンパイルしたい衝動に駆られたとしよう。そんなときは
nix-shell -p clang
を実行すれば Clang が使えるシェルに入れる。このシェルは隔離されていないのでシステムに入っているソフトウェアや main.c
などのファイルが問題なく使える。そのシェルでコンパイル
clang main.c -o hello-nix
すればいい。この作業で Clang はシステムにはインストールされていない。exit
して元のシェルに戻って clang
を実行することを試みてもエラーになるはずだ。なんだ…夢か。
TODO
- Node.js でなんかやる
- 一番得意なので
- F#/.NET でなんかやる
- やったことないので
- Rust でなんかやる
- Rustup との兼ね合いが気になるので
- 何かしら環境構築にめんどくささを持つソフトウェアが求められる
- Nix Expression Language の文法のちゃんと突っ込んだ説明をする
- Docker をやる
- VS Code Dev Container が動かなそうだけど大丈夫かな
- GitHub Actions
- CI/CD がバズワードなので
- niv
- Cachix
- ノートPCだとビルドしまくるのが困る