Open10

入門Nix

natsukiumnatsukium

始めに

Nixは、再現性、宣言的、信頼性に重きをおいたパッケージマネージャーです。また、NixOSというLinuxディストリビューションの基盤となっている技術でもあります。

  • 再現性
  • 宣言的
  • 信頼性

Nixをインストールすると上記のほかにもnixpkgsと呼ばれるパッケージコレクションにアクセスできることも大きな利点となります。
下図に示すように、nixpkgsは世界最大のパッケージ数を誇っており、その数は2024年1月現在90000弱となっています。
この数は非常に人気のあるArch Linuxの公式リポジトリとAURを合わせたものを上回ります。
repology

https://repology.org/repositories/statistics

natsukiumnatsukium

この記事の目的

目指すもの

これまでNixについて、興味はあるけれど敷居を感じてなかなか手を出せなかった方、
とりあえず入れてみたけれど何もわからず途方に暮れてしまった方が、Nixをパッケージマネージャーとして、
開発環境として少し使えるようになることを目指します。
正直Nixは1記事で解説できるほど簡単ではないため、それ以上のことは望めませんが、
この記事を出発点として自分の欲しい情報にアクセスできるようになれば幸いです。

目指さないもの

この記事ではNixOSやHome Managerといったシステムの構成管理には触れません。
また、サードパーティのプロジェクトについても基本的に言及しません。
これらはNixに入門する段階においては過度に複雑であり、移り変わりが激しく、
設定例を提示してもすぐに陳腐化すると考えているためです。
それらについては別の機会に紹介します。
Nixの基礎が身につけば自然とひとり立ちできるようになるでしょう。

natsukiumnatsukium

Nixのインストール

NixはLinux、macOSの各環境で利用できます。BSDのサポートもありますが、積極的にはメンテナンスされていないので慣れるまではいずれかで始めるべきです。
WindowsであればWSLから使用できます。

もしNixをまだインストールしていなければ、2024年1月現在だとnix-installerを使うと良いでしょう。
公式のインストーラとの主な違いは、後述するNix Flakesの機能がオプトインで有効になっていることと、アンインストーラが賢く、不要であればきれいに削除できる点です。

公式のインストーラを使ってNixをインストールし、Nix Flakesを有効化していなければ、~/.config/nix/nix.confに次の行を追加してください。

~/.config/nix/nix.conf
experimental-features = flakes nix-command
natsukiumnatsukium

nix runを使った一度きりのコマンド実行

あなたはUnixpornにはまりました。よく見かけるOSのロゴとともにシステムの情報を表示するプログラムはscreenfetchというようです。
そこでさっそくパッケージマネージャーを使ってインストールしてみました。
しかし実行してみると参考にしたセットアップとは少し違うようです。
もう少し調べてみるとneofetchというプログラムも見つけました。ほかにもfastfetchnitchというプログラムもあるようです。
気になるのでそれぞれインストールしてみることにしました。
結局neofetchが気に入りましたが、スクリーンショットを撮ったあとは不要になり、アンインストールしました。

このような使用頻度の少ないパッケージや、比較のために実行したいがインストールするまでもないというパッケージはnix runコマンドを使うのが便利です。
上記の例であれば、それぞれ以下のコマンドで実行できます。

# nix run nixpkgs#screenfetch
# nix run nixpkgs#neofetch
# nix run nixpkgs#fastfetch
# nix run nixpkgs#nitchtype neofetch
type: Could not find 'neofetch'

❯ nix run nixpkgs#neofetch
          ▗▄▄▄       ▗▄▄▄▄    ▄▄▄▖            natsukium@kilimanjaro 
          ▜███▙       ▜███▙  ▟███▛            --------------------- 
           ▜███▙       ▜███▙▟███▛             OS: NixOS 24.05.20240125.4fddc9b (Uakari) x86_64 
            ▜███▙       ▜██████▛              Host: ASUSTeK COMPUTER INC. TUF GAMING B660-PLUS WIFI D4 
     ▟█████████████████▙ ▜████▛     ▟▙        Kernel: 6.6.9-xanmod1 
    ▟███████████████████▙ ▜███▙    ▟██▙       Uptime: 22 hours, 22 mins 
           ▄▄▄▄▖           ▜███▙  ▟███▛       Packages: 1642 (nix-system), 1063 (nix-user) 
          ▟███▛             ▜██▛ ▟███▛        Shell: bash 5.2.21 
         ▟███▛               ▜▛ ▟███▛         Resolution: 3440x1440 
▟███████████▛                  ▟██████████▙   DE: Hyprland (Wayland) 
▜██████████▛                  ▟███████████▛   WM: sway 
      ▟███▛ ▟▙               ▟███▛            Terminal: kitty 
     ▟███▛ ▟██▙             ▟███▛             Terminal Font: Liga HackGen Console NF 14.0 
    ▟███▛  ▜███▙           ▝▀▀▀▀              CPU: 12th Gen Intel i5-12400F (12) @ 4.400GHz 
    ▜██▛    ▜███▙ ▜██████████████████▛        GPU: NVIDIA GeForce RTX 3080 12GB 
     ▜▛     ▟████▙ ▜████████████████▛         Memory: 5281MiB / 96383MiB 
           ▟██████▙       ▜███▙
          ▟███▛▜███▙       ▜███▙                                      
         ▟███▛  ▜███▙       ▜███▙                                     
         ▝▀▀▀    ▀▀▀▀▘       ▀▀▀▘

❯ type neofetch
type: Could not find 'neofetch'

neofetchがインストールされていない状態を維持しつつコマンドを実行できる一例として私の端末の入出力の一部を例示しました。

このように実行できるコマンドは以下から検索できます。
https://search.nixos.org/packages

また、nixpkgs以外のリポジトリからコマンドを取得できます。
たとえば、人気のあるエディタの1つであるNeovimを実行してみましょう。

❯ nix run "github:neovim/neovim/ab3a7fc3e3c0166f5792526699f035dd9e057ee9?dir=contrib" -- --version
NVIM v0.10.0-dev-ab3a7fc
Build type: Release
LuaJIT 2.1.1693350652
Run "nvim -V1 -v" for more info

このように任意のリビジョンを指定して実行できます。
7367838359bfb5fadf72ea2aeea2f84efb34590e

nodejsに馴染みのある方ならnpxのようなものだと思ってもらえると良いです。

natsukiumnatsukium

nix profileによるコマンドの管理

ここではNixを一般的なパッケージマネージャーのように使う方法を見ていきます。

nix profile install git
nix profile install vim
nix profile list
nix profile upgrade

アップグレードをした結果、パッケージに不具合が見つかった場合はロールバックを行うことができます。

nix profile list
nix profile rollback
natsukiumnatsukium

nix-shellを使った移植性の高いスクリプトの作成

シェルスクリプトの移植性を高めたいのなら、Nixを使うべきです。

example.sh
#!/usr/bin/env nix-shell -i bash -p jq

私は仕事柄Pythonのスクリプトを書くことが多くあります。
書き捨てのスクリプトに対して、標準のパッケージ以外のものを使うのは少し億劫ですがこれもNixが解決してくれます。
https://nixos.org/manual/nixpkgs/unstable/#running-python-scripts-and-using-nix-shell-as-shebang

次のようなスクリプトを考えてみましょう。

#!/usr/bin/env python3
import numpy as np

a = np.array([1,2])
b = np.array([3,4])
print(f"The dot product of {a} and {b} is: {np.dot(a, b)}")

このスクリプトは

#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages(ps: [ ps.numpy ])"
import numpy as np

a = np.array([1,2])
b = np.array([3,4])
print(f"The dot product of {a} and {b} is: {np.dot(a, b)}")

しかしこれでもまだ環境は再現できません。
参照しているnixpkgsのハッシュが一意に定まっていないためです。
そこでさらに一行追加します。

#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.numpy ])"
#!nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/e51209796c4262bfb8908e3d6d72302fe4e96f5f.tar.gz

import numpy as np
a = np.array([1,2])
b = np.array([3,4])
print(f"The dot product of {a} and {b} is: {np.dot(a, b)}")
natsukiumnatsukium

速習 nix lang

Nixを使っているとnixpkgs上のパッケージが古くなっていることに気付いたり、開発版を使いたくなったり、
パッチを当てたくなったりするかもしれません。
ここではそうした要望をかなえるためパッケージを作成、上書きするのに必要な最小限のnix langに入門します。

nix langは非正格で動的型付けな関数型言語です。
nix langはパッケージング用のDSLという側面をもつため、Haskellをはじめとする関数型言語に触れたことのない方でもそれほど苦労しないはずです。

また、Nixにはスクリプト言語にはお馴染みのreplが付属しているため、動作の確認をするにはnix replコマンドが便利です。
以下、nix-repl>はreplへの入力を表します。

attribute sets

Nix langで一番遭遇する型はこのattribute setsです。

{}
{ x = 1; }

ネストしたattrsetsは次のようにメソッドチェインのような形で呼び出すことができます。

repl
nix-repl> x = { y = { z = 1; }; }
nix-repl> x.y.z == 1
true

function

Nixでは関数は以下のように表現します。

x: x + 1

パッケージングでよく見かけるのは、attribute setsを引数にとり、attribute setsを返す関数でしょう。
以下に典型的な関数を例示します。

{ pkgs ? import <nixpkgs> { } }: {
    package = pkgs.hello;
}

デフォルト引数、import<>によるパスの表記

fixed point

不動点コンビネータ
これまで関数型言語に触れたことがなければ見慣れないものかもしれません。

https://github.com/NixOS/nixpkgs/blob/69fb977d7a702530ebf27714dbedd7e80f31e09e/lib/fixed-points.nix#L66-L75

不動点コンビネータ自体の解説は私には荷が重く、この記事の趣旨から外れるため割愛します。
ここで抑えておきたいのは、上記の簡単な関数によって再帰を評価できる点です。

nix-repl> fix = f: let x = f x; in x
nix-repl> pkgs = self: { a = 3; b = 4; c = self.a + self.b; }
nix-repl> fix pkgs
{ a = 3; b = 4; c = 7; }

fix関数に関数fを与えるとf x = xを満たすxが手に入ります。
f x = xなのでffx = xを満たすのはもちろん、fff...f xももちろんxです。

したがって上記の例においてfix pkgsfix (pkgs (pkgs (pkgs (...))))と等価です。
このことを念頭に置いてfix pkgsの処理を順を追って展開すると、

fix pkgs
fix (pkgs pkgs)
fix ((self: { a = 3; b = 4; c = self.a + self.b; }) pkgs)
fix { a = 3; b = 4; c = pkgs.a + pkgs.b; }
{ a = 3; b = 4; c = 7; }
natsukiumnatsukium

overrideとoverlayを駆使したパッケージの変更

この節ではnixpkgsに収録されているパッケージをカスタマイズする方法を紹介します。

override

overrideはパッケージの引数を上書きします。
依存パッケージを変更したり、ビルドに関わるフラグを選択したりといった操作は主にこちらを使います。

overrideAttrs

overrideAttrsはパッケージの戻り値を上書きします。
ほとんどすべてのパッケージのバージョンを変更したり、パッチを当てたり、ソースをフォークに変更したり
といった操作は主にこちらを使うことになります。

pkgs.hello.overrideAttrs (self: super: {
    super.name = "hello2"
})

ここで、overrideはhelloのメソッドに見えるかもしれませんが、ただのhelloが返す属性の1つであることに注意してください。

overlay

override, overrideAttrsを使うことでパッケージを自在に更新できるようになりました。
ただしこのままではそのパッケージが末端のパッケージであるとき以外にはあまり役に立ちません。
もしライブラリや複数のパッケージから必要とされるアプリケーションを上書きしたいのなら、これから紹介するoverlayの出番です。

nixpkgsの構造を考えてみましょう。
nixpkgsのトップレベルには以下のdefault.nixが存在しており、pkgs/top-level/impure.nixを呼び出します。
https://github.com/NixOS/nixpkgs/blob/8f6d58e773383880e2ce4601286da34bb867efe3/default.nix#L28

より詳しく知りたい方は以下の資料を参照してください。
lib.fixedPoints - Nixpkgs manual
Chapter 17. Nixpkgs Overriding Packages - Nix Pills
Fixpoints, attribute sets and overlays - Tobi's blog

natsukiumnatsukium

Nix flakesを使った再現性の高い開発環境の構築

この節ではNix flakesを扱います。
flakeはnix-channelに依存しない堅牢な環境を提供します。
先に述べたnix-shellを使ったシェルスクリプトの例では、出力を一意に定めるためにはnixpkgsのコミットハッシュを明示する必要がありました。
これは

flakeはいまだにexperimentalとなっていますが、2024年現在、事実上デファクトスタンダードとなっています。
少なくとも個人レベルで使用するなら利用を検討するのが良いでしょう。
https://discourse.nixos.org/t/why-are-flakes-still-experimental

これまでの節でもすでにflakeは登場しており、
コマンドとしてはnix-fooというのがレガシーなもの、nix fooというのがflakeを利用したものとなっています。

natsukiumnatsukium

最後に

Nixとうまく付き合うにはすべてをNixで完結しようとするのではなく、ときには諦めることも重要です。
Nixは銀の弾丸ではありません。不必要に複雑な作業が求められることも多いでしょう。
使用するOSに付属のパッケージマネージャーと併用することも検討してください。
小さく導入することでNixの得手不得手が見えてくるでしょう。