stackとNixを使って安定したビルド環境を作る
2016-03-12
問題点
stackはいいんだけれど
私はHaskellを使ったプロジェクトのビルドにはstackをよく使います。
stackを使う恩恵としてビルドが安定するのはもちろんあるのですが、
それ以外にもcabalを使っていた頃と比べて煩瑣なコマンドの入力が減っているので
ターミナル上の操作も随分楽になりました。(主にcabal configureが邪魔だと思っていた)
Haskellの依存パッケージをビルドするのにこのstackを使うことで快適になりました。
しかしそれでもまだ時々ストレスになることがあります。
Cの呼び出しを含むようなパッケージをビルドする場合の依存関係の解決です。
FFIを含むパッケージの依存関係
例えばhdbc-sqlite3はsqlite3のヘッダファイルを
インポートするようなC呼び出しのコードを含んでいます。
stackではこのsqlite3のヘッダファイルの解決まではしてくれません。
自分でsqlite3をシステムにインストールするなどしてロードできるようにしてあげる必要があります。
開発環境のシステムにグローバルにインストールすることでこの依存を解決できるかもしれません。 ただし新しいインストールよってシステム上の他の依存関係が壊れることもあります。 開発目的で必要なライブラリをグローバルにインストールするのは避けたいものです。
サンドボックスを作ってそのプロジェクトに必要なCライブラリだけを インストールできないものでしょうか。
解決策の一つに Nixを使う というアプローチがあります。
解決方法
Nix パッケージマネージャ
"純粋関数型パッケージマネージャ"と呼ばれるNixを使うことで こうした外部ライブラリへの依存周りの問題を解決することができます。 (何をもって"純粋関数型"とするかは謎ですが公式はそう自称しています)
NixはOSX,
Linuxディストリビューション上で利用できるパッケージマネージャです。
Nixでは新しいパッケージをインストールしても既存のパッケージの
依存関係を上書きしない ため、
各パッケージの依存関係が不整合になって壊れることがありません。
これはパッケージとその依存パッケージが それぞれ分離されてインストールされている ことによる恩恵です。 この 分離 の特徴によってHaskellプロジェクトが依存する外部ライブラリは、 システムグローバルの領域とは 分離 されてそのプロジェクト用にインストールされます。
そして何よりNixはstackと相性がよいです。
stack v0.1.10.0からこのNixとの統合機能が導入されたためです。
Nixの導入
環境情報
OS: Linux debian-jessie 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u3 (2016-01-17) x86_64 GNU/Linux
Nix: 1.11.2
ghc: 7.10.3
stack: 1.0.2
cabal: 1.22.9.0
導入手順
Nixとstackを統合するためには
Nixのトップページにある「Get Nix」の手順でインストールすることができます。
Vagrantの仮想マシンdebian/jessie64でインストールする例を示します。
Nixのインストール
$ curl https://nixos.org/nix/install | sh
Nixの提供するコマンドへとパスを通す
$ . /home/vagrant/.nix-profile/etc/profile.d/nix.sh
bashrcやzshrcなど、お使いのシェルに応じて
ログイン時に上記のコマンドを実行するようにしてください。
3. Haskell開発に必要な最低限のパッケージをインストール
$ nix-env -iA nixpkgs.ghc nixpkgs.stack nixpkgs.cabal-install
この時点でGHCをバイナリでインストールできるので、
stack setupは不要になります。
stackとの統合
ビルド前の準備
例えば外部ライブラリに依存するHaskellプロジェクトであるhdbc-sqlite3をビルドしたいとしましょう。
今までであれば使い慣れたパッケージマネージャでsqlite3をインストールするのですが、
ここではシステムグローバルにインストールせずにそれをやる方法を考えます。
git clone https://github.com/hdbc/hdbc-sqlite3.git
cd hdbc-sqlite3
stack.yamlを生成しましょう。
# 2016/03/12 時点では Nixのパッケージリポジトリに
# lts-5.5がないのでlts-5.4を明示します。
$ stack init --resolver lts-5.4
Using cabal packages:
- HDBC-sqlite3.cabal
Downloaded lts-5.4 build plan.
Caching build plan
Fetched package index.
Populated index cache.
Initialising configuration using resolver: lts-5.4
Writing configuration to file: stack.yaml
All done.
ビルド
とりあえず何も考えずにビルドしようとすると失敗します。
$ stack build
HDBC-sqlite3-2.3.3.1: configure
Configuring HDBC-sqlite3-2.3.3.1...
setup-Simple-Cabal-1.22.5.0-ghc-7.10.3: Missing dependency on a foreign
library:
* Missing C library: sqlite3
This problem can usually be solved by installing the system package that
provides this library (you may need the "-dev" version). If the library is
already installed but in a non-standard location then you can use the flags
--extra-include-dirs= and --extra-lib-dirs= to specify where it is.
-- While building package HDBC-sqlite3-2.3.3.1 using:
/home/vagrant/.stack/setup-exe-cache/x86_64-linux/setup-Simple-Cabal-1.22.5.0-ghc-7.10.3 --builddir=.stack-work/dist/x86_64-linux/Cabal-1.22.5.0 configure --with-ghc=/home/vagrant/.nix-profile/bin/ghc --with-ghc-pkg=/home/vagrant/.nix-profile/bin/ghc-pkg --user --package-db=clear --package-db=global --package-db=/home/vagrant/.stack/snapshots/x86_64-linux/lts-5.4/7.10.3/pkgdb --package-db=/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux/lts-5.4/7.10.3/pkgdb --libdir=/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux/lts-5.4/7.10.3/lib --bindir=/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux/lts-5.4/7.10.3/bin --datadir=/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux/lts-5.4/7.10.3/share --libexecdir=/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux/lts-5.4/7.10.3/libexec --sysconfdir=/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux/lts-5.4/7.10.3/etc --docdir=/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux/lts-5.4/7.10.3/doc/HDBC-sqlite3-2.3.3.1 --htmldir=/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux/lts-5.4/7.10.3/doc/HDBC-sqlite3-2.3.3.1 --haddockdir=/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux/lts-5.4/7.10.3/doc/HDBC-sqlite3-2.3.3.1 --dependency=HDBC=HDBC-2.4.0.1-9022dccb33fa7027f85b22fa779bd1cb --dependency=base=base-4.8.2.0-0d6d1084fbc041e1cded9228e80e264d --dependency=bytestring=bytestring-0.10.6.0-c60f4c543b22c7f7293a06ae48820437 --dependency=mtl=mtl-2.2.1-3af90341e75ee52dfc4e3143b4e5d219 --dependency=utf8-string=utf8-string-1.0.1.1-df4bc704a473da34292b0ea0e21e5412 --enable-tests --enable-benchmarks
Process exited with code: ExitFailure 1
sqlite3という外部ライブラリが見つからないというメッセージが出力されています。
まだNixとの統合が設定されていないためです。
stack と Nix の統合
stack.yamlに下記のような設定を追記することでstackは依存性の解決にNixも使うようになります。
nix:
enable: true
packages: [ sqlite ]
packages: [ sqlite ]の部分はこのプロジェクトが依存する外部ライブラリの名前を
スペース区切り で列挙します。
注意点
cabalファイルではsqlite3という名前のライブラリを参照するように宣言していますが、
これをそのままstack.yamlに記載すると
パッケージ名が発見できずエラー となってしまいます。
Nixのパッケージリポジトリ上はsqliteと定義されているためです。
stack.yamlに記述するパッケージ名はNixパッケージシステム上で有効なsqliteという名前で指定する必要があります。
$ stack build
〜略〜
Preprocessing library HDBC-sqlite3-2.3.3.1...
[1 of 7] Compiling Database.HDBC.Sqlite3.Types ( Database/HDBC/Sqlite3/Types.hs, .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3/Types.o )
[2 of 7] Compiling Database.HDBC.Sqlite3.Utils ( .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3/Utils.hs, .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3/Utils.o )
[3 of 7] Compiling Database.HDBC.Sqlite3.Statement ( .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3/Statement.hs, .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3/Statement.o )
[4 of 7] Compiling Database.HDBC.Sqlite3.ConnectionImpl ( Database/HDBC/Sqlite3/ConnectionImpl.hs, .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3/ConnectionImpl.o )
[5 of 7] Compiling Database.HDBC.Sqlite3.Connection ( Database/HDBC/Sqlite3/Connection.hs, .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3/Connection.o )
[6 of 7] Compiling Database.HDBC.Sqlite3.Consts ( .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3/Consts.hs, .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3/Consts.o )
[7 of 7] Compiling Database.HDBC.Sqlite3 ( Database/HDBC/Sqlite3.hs, .stack-work/dist/x86_64-linux-nix/Cabal-1.22.5.0/build/Database/HDBC/Sqlite3.o )
In-place registering HDBC-sqlite3-2.3.3.1...
HDBC-sqlite3-2.3.3.1: copy/register
Installing library in
/home/vagrant/hdbc-sqlite3/.stack-work/install/x86_64-linux-nix/lts-5.4/7.10.3/lib/x86_64-linux-ghc-7.10.3/HDBC-sqlite3-2.3.3.1-J7BRkfYWClZD8ttodrMiVd
Registering HDBC-sqlite3-2.3.3.1...
Completed 8 action(s).
無事コンパイルできました。
この過程でsqlite3をインストールしながらビルドされますが、
グローバルにはインストールされません。
このプロジェクトのビルド時にスコープを絞ってロードされます。
試しにNixでシステムグローバルにインストールされたパッケージを一覧化します。
$ nix-env -q
cabal-install-1.22.9.0
ghc-7.10.3
nix-1.11.2
stack-1.0.2
明示的にコマンドでインストールしたもののみ列挙されています。
sqliteはあくまでもこのプロジェクト用にインストールされているのが解りました。
有効なパッケージ名の検索
Nixのリポジトリ上でどんなパッケージが有効なのか探すための方法は、
「リポジトリをひたすら検索すること」です。
自分で書いていて「なんかここは微妙だな」と思えてきました……。
公式ページでは.bashrcなどに下記の関数を書いて、
パッケージ検索を少し楽にするといいよと言っていました。
nix? () {
nix-env -qa \* -P | fgrep -i "$1"
}
このコマンドではNixのパッケージをリストアップして特定の文字列で絞り込んでいます。
早速nix?コマンドを使ってみましょう。
試しにgccで検索すると…… (結構時間かかります)
$ nix? gcc
nixpkgs.avrgcclibc avr-gcc-libc
nixpkgs.distccMasquerade distcc-masq-gcc-4.9.3
nixpkgs.gcc-arm-embedded-4_7 gcc-arm-embedded-4.7-2013q3-20130916
nixpkgs.gcc-arm-embedded-4_8 gcc-arm-embedded-4.8-2014q1-20140314
nixpkgs.gcc-arm-embedded gcc-arm-embedded-4.9-2015q1-20150306
nixpkgs.gcc_debug gcc-debug-wrapper-4.9.3
nixpkgs.gcc44 gcc-wrapper-4.4.7
nixpkgs.gcc45 gcc-wrapper-4.5.4
nixpkgs.gcc46 gcc-wrapper-4.6.4
nixpkgs.gcc48 gcc-wrapper-4.8.5
nixpkgs.gcc49 gcc-wrapper-4.9.3
nixpkgs.gcc gcc-wrapper-4.9.3
nixpkgs.gcc_multi gcc-wrapper-4.9.3
nixpkgs.gcc5 gcc-wrapper-5.3.0
nixpkgs.gccgo48 gccgo-wrapper-4.8.5
nixpkgs.gccgo gccgo49-wrapper-4.9.3
nixpkgs.xorg.gccmakedep gccmakedep-1.0.3
左の列はNixリポジトリの名前空間上のパッケージ識別子です。
右の列はパッケージの名前です。
こうしてパッケージを名前で検索できます。 でもなんかコレジャナイ……。
パッケージの説明はこんな感じで表示できます。
$ nix-env -qa --description gcc-wrapper-5.3.0
gcc-wrapper-5.3.0 GNU Compiler Collection, version 5.3.0 (wrapper script)
Nixのコマンドがよく解らないという方は下記のチートシートが参考になるかもしれません。
まとめ
stackを使って外部ライブラリに依存するプロジェクトをビルドする場合は、
Nixと統合することでシステム全体にインストールされたパッケージを破壊するリスクをなくすことができます。
stackとNixの統合はとても簡単ですが、
Nix上での適切なパッケージ名を知らないと正しく依存関係を解決できません。
参考記事