Nix Flake+direnvでプロジェクトのパッケージ管理
突然なんですけど、「mise」人気ですよね。ArchLinuxを利用していた頃は自分も使っていました。
ただ、NixOSを利用するようになってからは、全部Flake+direnvでええやんとなってしまったのでmiseを使わなくなりました。
利点としては10万を越える豊富なパッケージが利用できることと、高い自由度があると思います。
まあ「nixわけわかめだからtomlとかで設定できるmiseが良いんじゃ」って言われそうなんですけど、nix言語は関数の生えたJSONらしいのでそんな難しくないです。
...冗談はともかく、今はノリと勢いとAIでなんとかなるので雰囲気を掴んでその良さを知ってもらえたらうれしいなって思います。どちらかと言えばNixOS以外のユーザー向けです。
flake.nixの作成
ゼロから書くのは大変面倒なので、テンプレートを使うのが楽です。連携用のシステムであるnix-direnvには専用のテンプレートが配布されているので、以下でテンプレートから作成します。
$ nix flake new -t github:nix-community/nix-direnv hello-flake
今いるディレクトリに展開したい場合
$ nix flake init -t github:nix-community/nix-direnv
これでhello-flakeディレクトリが作成され、その中にflake.nixと.envrcが配置されます。
とりあえずターミナルでhello-flakeディレクトリに移動し、以下のコマンドを実行します。
$ cd hello-flake
# gitの初期化
$ git init
$ echo ".direnv" >> .gitignore
$ git add .
# Direnvを有効化
$ direnv allow
gitを初期化する理由
flakeはgitの管理システムに一部依存しており、管理対象となっているファイルのみを評価します。管理対象を絞らないことで、後々困る(flake.nixからファイルを参照できないor余計なファイルを参照してしまう)ことがあるので初期化しておくことをお勧めします。
初回だけちょっと時間がかかります。
これでnix develop相当の処理が実行され、パスが通るようになります。
ざっくりとした挙動
-
flake.nixが評価され、devShells.defaultが呼び出される -
devShells.defaultで指定されたパッケージがインストール - 環境変数
PATHにインストールされたパッケージのパスが追加される
※パッケージ以外の設定もそんな感じで反映されます。
この内容から気付いた人もいるかもしれませんが、nix developコマンド自体も仮想環境を立ち上げているとかではなく、単にbashを起動してパスを通しているだけです(結構勘違いされがち)
パッケージの追加
ちょっとだけflake.nixの内容をいじってみましょう。
18行目のbashInteractiveの後にhelloパッケージを追加してみます。
- devShells.default = pkgs.mkShell { packages = [ pkgs.bashInteractive ]; };
+ devShells.default = pkgs.mkShell { packages = [ pkgs.bashInteractive pkgs.hello ]; };
ターミナルで一度Enterを押すなりするとhelloパッケージがインストールされ、helloコマンドが利用できるようになります。
$ hello
Hello, world!
こんな感じで好きにパッケージを追加することができます。
有効なパッケージは公式の検索サイトで探すことができます。
miseとは異なり、細かいバージョンを指定するのは難しいですが、flake.lockにより定義からインストールされるパッケージのバージョンは固定されるので、チームで同じ環境を共有することができます。
更新は以下のコマンドで行います。
$ nix flake update
flake.nixのフォーマット
プロジェクト管理ではないですが、色々書いていると乱雑になるので、早めに設定しておくと幸せになれます。
pkgs = nixpkgs.legacyPackages.${system};
in
{
+ formatter = pkgs.nixfmt-tree;
$ nix fmt
カスタムスクリプトの追加
miseのタスクランナーが人気らしいので、flakeでもできるよってのをアピールしておきます。
nixpkgsには、パッケージだけでなく便利なユーティリティもたくさん含まれています。
その中でもpkgs.writeScriptBinを使うと簡単にスクリプトを追加できます。
例えば、GitHub Actionの履歴を取得する場合
packages = [
pkgs.bashInteractive
pkgs.hello
+ (pkgs.writeScriptBin "gh-actions-history" "gh run list --limit 100")
];
これでまたターミナルでEnterを一度押すとgh-actions-historyコマンドが利用できるようになります。
$ gh-actions-history
STATUS TITLE WORKFLOW BRANCH EVENT ID ELAPSED AGE
✓ 🐛 Use roo-... build co... main push 20206750647 5h8m1s about 2 ...
...
実態はただのシェルスクリプトなので、複数行に及ぶ宣言も可能です。
PCにインストールしていない、packages内で追加したパッケージも使えます。
(pkgs.writeScriptBin "gh-actions-history" "gh run list --limit 100")
+ pkgs.jq
+ (pkgs.writeScriptBin "show-api-paths" ''
+ PATHS=$(jq -r '.paths | keys[]' openapi.json)
+ for url in $PATHS; do
+ echo $url
+ done
+ '')
];
# 例: https://gist.github.com/biggates/4955d608379a8b1b3224e815c7dd0dc9
# $ curl -L -o openapi.json https://gist.githubusercontent.com/biggates/4955d608379a8b1b3224e815c7dd0dc9/raw/0f69ccfb49181f17f2e2c1f5caedc345f1f40af5/petstore_oas3_requestBody_example.json
$ show-api-paths
/pet
/pet/findByStatus/MultipleExamples
/pet/findByStatus/singleExample
環境変数の設定
これに関してはDirenv側で.envとかを読み込むようにしてもいいんですが、一応flake.nix側で設定する方法も紹介しておきます。
例えばFOOという環境変数を設定したい場合、以下のようにします。
packages = [
...
];
+env = {
+ "FOO" = "BAR";
+};
再度ターミナルでEnterを押してから以下のような出力が得られるはずです。
$ echo $FOO
BAR
初回セットアップの自動化
ディレクトリに入った時に特定の処理を実行したい場合、shellHookを使うと便利です。
例えば、とりあえずpnpm installを実行したい場合、以下のようにします。
env = {
...
};
+ shellHook = ''
+ if [ ! -d "node_modules" ]; then
+ echo "初回セットアップ: pnpm install を実行しています..."
+ pnpm install
+ fi
+ '';
これでディレクトリに入るたびにnode_modulesディレクトリが存在するか確認し、なければpnpm installを実行します。
shellの名の通り、普通のシェルスクリプトが書けるので、好きにカスタマイズできます。
Editor連携
これはNix Flakeの機能というよりdirenvの機能ですが、お好みのEditor/IDEを経由してこれらのパッケージや環境変数にアクセスすることも可能です。
代表的なプラグインでは以下のようなものがあります。
VSCode: direnv
IDEA: Direnv Integration
おわりに
こんな感じでmiseでできることは大体できると思います。
「こんなんmiseとか別の便利ツールではできるんやがFlakeではどうなの?」とか、「ここがよくわからん!」みたいなのがあれば教えてください。
応用編として外部flakeを使うとnix fmtで全ファイルのフォーマットができたりとか無限の可能性があるんですが、まあそれはまた別の機会に。
Discussion