NixによるHaskell開発環境の構築
Nix package manager によって Haskell のスクリプティングおよびパッケージ開発の環境構築をしていく。
こいついつも環境構築してんな
環境構築以外はブログに書くような一般性のあることをやっていないということで……。
ここで触れられないこと
- cabal.project によるマルチプロジェクトを扱う方法
- 外部の nix store を使うこと
- nix の新しいコマンド群 (nix コマンドのサブコマンド群)
- デプロイや公式 Docker イメージについて
- Stack integration
- IOHKのリソース
- NixOS
- その他 Nix の深い話
Nix
Nix がなにかよく分からないが様々な環境で使えるパッケージセットとして利用している。
そこだけ聞くと stackage みたいだが全然 Haskell に閉じたものではなくて普通の OS のパッケージマネージャのようなカバー範囲を持っている。 それでいて Haskell のライブラリ依存関係も相当なリソースを投入して安定に保たれているということのようだ。
自分は Mac と Ubuntu on WSL で同じ設定ファイルで Haskell の環境を構築できている。
さて Nix で検索すると NixOS というのが出てくるが OS を考えずにパッケージマネージャだけ利用することができる。
利用形態:
- Nix package manager のみ
- NixOS と Nix package manager
Nix ではパッケージセットを channel と呼び、これには何種類かある。
https://nixos.wiki/wiki/Nix_channels
Nix channel:
- nixpkgs-unstable (= nixpkgs)
- NixOS-xx.yy (NixOS-21.11 など)
NixOS channel は名前の通り NixOS 用であり Nix package manager だけのときに使うことは推奨されない。 つまり Nix package manager のみの利用のときには nixpkgs を使うことになる。 なお nixpkgs-stable という channel はないようだ。
プロジェクトの環境を固定したいときはこの channel のバージョンを固定すればいい。
https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs
スクリプティング
まず、Nix を使わなくても Haskell スクリプティングはできる。
cabal および stack のスクリプトはだめぽ氏による記事がとても参考になると思う。
https://zenn.dev/mod_poppo/scraps/e2891dbebb235d
Nix による方法は、まず Nix package manager をお使いの環境にインストールする。
https://nixos.org/download.html
するとシンプルなスクリプトは以下のように書ける (標準入力から1行読んで適当な色をつけて出力するスクリプト)。
> echo "ヤッホー" | ./yamabiko-nix.hs ヤッホー
nix-shell
は Nix の環境の中に入るためのコマンドだが、
shebang に書くことで特殊な動作をしてそのスクリプトを
Nix 環境の中で実行するように動く。
コマンドライン引数は複数行に分けて記述できる。
-p
/--packages
: 依存パッケージを指定。デフォルトの channel は nixpkgs が選ばれる。-i
/--interpreter
: このスクリプトを処理するコマンドを指定。
パッケージ指定が Nix Haskell の特徴的な記述になっている。 これは次のように構成されている。
pkgs
: nixpkgs のルートhaskell.packages.ghc921
: GHC 9.2.1 用の Haskell 依存関係の名前空間ghcWithPackages
: 依存ライブラリとGHCを同時に指定するための Nix 式 (関数)p: [p.ansi-terminal]
: 依存ライブラリのリストを返す無名関数 リストはスペース区切り
GHCバージョンにこだわりがない場合 haskell.packages.ghc921
を
haskellPackages
で置き換えてもいい。
選択可能なGHCバージョンは次のコマンドでわかる。
> nix-env -f '<nixpkgs>' -qaP -A "pkgs.haskell.compiler" pkgs.haskell.compiler.ghc8107 ghc-8.10.7 pkgs.haskell.compiler.ghc884 ghc-8.8.4 pkgs.haskell.compiler.ghc902 ghc-9.0.2 pkgs.haskell.compiler.ghc921 ghc-9.2.1 ...
また channel で管理されている依存ライブラリのバージョンは次のコマンドでわかる。
> nix-env -f '<nixpkgs>' -qaP -A "pkgs.haskell.packages.ghc921.ansi-terminal" pkgs.haskell.packages.ghc921.ansi-terminal ansi-terminal-0.11.1
ghcWithPackages
の実装は ここ にある。
GHCとライブラリを別々に指定してしまうとGHCがライブラリを見つけられないらしい。
ちゃんとした開発
haskell-language-server の導入
haskell-language-server はエディタが見るものだし、 複数のGHCバージョンに対応するものでもあるので、 他に使いそうなものと一緒にグローバルにインストールしてしまう。
次のファイルを $HOME/nix/haskell/default.nix
にでも用意して
nix-env -i
をたたくとグローバルにインストールされる。
(この default.nix は こちら を参考にさせてもらっている)。
> nix-env -i --file $HOME/nix/haskell/ ... building '/nix/store/knnnspc3dxxbk9nwv9rqznrrf01rrl8d-user-environment.drv'...
overlay
によって haskell-language-server にオプションを与えている。
supportedGhcVersions
は読んでの通り。
dynamic=true
については何のドキュメントも見つけられないが Template Haskell のコードを扱うなら必要なようだ。
ただしこのオプションを有効にすると haskell-language-server のインストールに余計に時間がかかるかもしれない。
オプションの実装は ここ
にある。
haskell-language-server 側のドキュメントでは ここ
に相当する処理をしていると思われる。
パッケージの設定
Nix での Haskell 開発について調べていると cabal2nix という名前がよく出てくるが、これを直接使う機会はあまりなさそうだ。 今は nixpkgs の Haskell module が cabal2nix をラップした便利関数をいろいろ提供していて、 それらを使うのが主流のようだ。 この記事でも cabal2nix を直接は使わない方法を説明する。
まずはいつものように cabal init
でパッケージを初期化する。
(--enable-nix
オプションは不要かもしれない)。
> mkdir test-package > cd test-package > nix-shell -p "pkgs.cabal-install" -p "pkgs.haskell.compiler.ghc921" --run "cabal --enable-nix init --exe"
初期化ができたら次のような default.nix
を用意する。
これで次のようにすればパッケージの executable が実行できる。
(nix-build
は初回だけでいいはず)。
> nix-build > nix-shell --run "cabal run" "Hello, Haskell!"
あとは普通に .cabal ファイルを編集して開発していけばいい。
さて、この default.nix
は nixpkgs の Haskell module が提供する
developPackage
を呼んで Haskell パッケージの開発のための設定を構築している。
source-overrides
で依存関係のオーバーライドができる。
Hackage のバージョンを指定する方法と tarball のURLを指定する方法がある。
modifier
では cabal のいくつかのオプションを設定できる。
設定項目の内訳は ここ にある。
なお、ghcBuild
引数に値を与えて別のGHCバージョンを指定することもできる。
(GHCと base のバージョンが合わないときは version-history を参照して調整する)。
> nix-shell --argstr "ghcBuild" "ghc902" --run "cabal run"
高度な依存関係オーバーライド
この項目を書くときに力尽きた。 依存関係オーバーライドはそれほど困難な道だった。 どうしてもオーバーライドしたいなら次の記事が参考になる。
Nix recipes for Haskellers – Sridhar Ratnakumar
How to override dependency versions when building a Haskell project with Nix
トラブルシューティング
nix-shell の立ち上がりが遅い
direnv Nix integration を導入するといいかもしれない。 direnv 自体は Nix と関係なく使えるひとつのツールだが、 Nix がこれと連携するのにはいくつかのオプションがある。
https://github.com/direnv/direnv/wiki/Nix
自分はシンプルそうな nix-direnv を使っている。
VSCode に direnv 拡張を導入すると VSCode からも direnv の環境が見えるようになる。 ただ過渡期にあるようで何を選ぶか注意が必要そうだ。
VSCode direnv 拡張は上から3番目のが公式かつ最新らしい。なかなかの罠。 pic.twitter.com/IOkIJBIaQk
— しょしー (@syocy) 2022年3月18日
cabal run で cabal 自体の出力を無視したい
これは Nix というより cabal の Tips だが次のようにするといいかもしれない。
> nix-shell --run "cabal build && \$(cabal list-bin test-package) | clip.exe"
Mac で nix-env -i が失敗する
すでに修正されたようだが Mac 環境で nix-env -i
が失敗することがある。
そのときは以下の issue にある方法で回避できることがある。
https://github.com/NixOS/nixpkgs/issues/163374
Mac で channel が消滅する
理由は分からないが Mac で channel 設定が消滅することがある
(nix-channel --list
が何も返さない)。
nixpkgs を自分で再設定する必要があるかもしれない。
> nix-channel --add https://channels.nixos.org/nixpkgs-unstable nixpkgs > nix-channel --update nixpkgs