Stackとは?
つい先日のことですが、Stackage界隈からstackというツールがリリースされました。リリースされたとはいえ、開発され始めたのがちょっと前のことですし、現在も盛んに機能が追加されているので、絶賛開発中であるとかそういったほうがいいかもしれません。
まだ開発の始まったばかりのツールなのに、なぜこんな紹介記事を書こうと思ったのかというと、このツールがHaskellの開発において極めて有用になることが確定的に明らかであって、すでに荒削りながらも、大変便利に使えているからなのです。そしてここで紹介することで、多くの読者の方に興味を持ってもらって、それで開発がさらに盛り上がっていくと嬉しいなあと、そう思った次第であります。
なお、stackの開発が始まる少し前に、stackage-cliを始めとするいくつかのツールがリリースされましたが、今後開発はstackに一本化されるようなので、これらのことは忘れましょう!
何をするものなのか?
Haskellのパッケージをビルドしたりインストールしたりするツールです。Haskellコミュニティーには、ビルドシステムとしてのCabalと、パッケージ管理ツールとしてのcabal-installがありましたが、stackはこのうちcabal-installを置き換えるものであって、パッケージングやバックエンドとしてはCabalを使っています。また、パッケージレポジトリとしてはHackageを使います。つまり、既存のインフラの上に、とてもうまく便利なツールを構築した形になっています。
何が嬉しいのか?
cabal-install を使っている人には、多かれ少なかれ不満を抱いている人が多いと思います。というか、十分満足している人のほうが少ないんじゃないかという気もします。stackはcabal-installのいろいろな問題を解決しています。
-
cabal hellに悩まされない
Stackageを使ってパッケージの依存関係を解決するので、バージョンをあげたらパッケージ解決がコケるようになったとか、アレをいれたらコレが入らぬとか、再インストールでアレが壊れたとか、そういうことがなくなります。依存関係は基本的に一通りに解決され、再インストールも基本的にありません(詳しくは後述)。パッケージが壊れないし、バージョン違いを再インストールする必要もないので、cabal-installを使っている時のように、sandboxを毎回作って、その中で毎回一から全部依存ライブラリビルドして…、ということがなくなります。コンパイル時間が短縮されて、結構バカにならない容量を食うのサンドボックスも必要無くなります。
-
複数バージョンのghc環境を簡単に共存させられる
複数のghcのバージョンを共有させるには、インストールする場所を分けて、参照するパッケージDBの場所を正しく設定したり、あまり簡単とは言えない状況にありましたが、stackを使えばセットアップから使い分けまでとても簡単にできます。何も設定する必要はありません。
-
複数のcabalパッケージからなるプロジェクトを簡単にビルドできる
cabal-installでは最近実装されたsandboxの、add-sourceを使って複数パッケージからなるプロジェクトをビルドしていましたが、stackでは特に何も設定せずに簡単にこういうプロジェクトをセットアップできます。複数パッケージの設定も単純なyamlファイルの編集で変えられるので、とても簡単です。
Stackageとは
Stackageとは、HackageのパッケージのStableなセットを作るプロジェクトのようです(あまり詳しくはないのですが)。
ここでいうStableなセットというのは、
- コンパイルが通る
- テストが通る
- 依存関係がセットの中で閉じている
- セットに含まれるのは、ひとつのパッケージに対してただひとつのバージョン
という感じで、特にクオリティーなどへの要求ではなく、とにかくここに含まれているものなら、同時に何をどれだけ使おうともコンパイルが通るということを保証するというもので、これはパッケージ開発者にとって大変ありがたい性質です。
このようなStableなセットのうち、なるべく大きく、なるべく新しいものが含まれるようなものを維持し続けるのが(多分)Stackageであり、LTS Haskellなんじゃないかなあと思います(LTS Haskellというのは、Stackageのスナップショットのうち、二週間に一度作られるものです)。
もちろん大量のパッケージのあるHacakgeから自動的にこういうことをするには膨大な計算リソースが必要だし現実的ではないので、現状では何人かの人が手動でメンテナンスしているようです。これは大変な作業だと思うので、頭の下がる思いなのですが、こういうのがあってこそHaskellの商用利用も加速するといったものだと思うので、ありがたく使わせていただくことにします。
Stackage自身は、これはただのHacakgeのパッケージの集合のスナップショットなので、特別なツールを使わずとも利用することができます。Stackageのトップページにも載っているように、cabal sandboxがあるプロジェクトのディレクトリで、cabal.config
をダウンロードするだけです。
$ wget https://www.stackage.org/lts/cabal.config
$ cabal update
$ cabal install
cabal.configには、バージョン付きパッケージの集合が列挙してあるだけです。
ではstackは何かというと、このStackageを用いてHaskellの開発を便利に行えるようにするツールです。Stackageでは、パッケージや依存しているパッケージのバージョンが固定されているので、本来はパッケージのコンパイルやインストールはただ一回だけで済むはずですが、実際にはcabalのsandboxで共有を行おうとすると、かなり面倒なことになります。また、Stacakge外のパッケージをうまく扱わないと、結局sandboxがおかしくなって、部分的にcabal hellを解決できていないことになります。
そこで、上に示したようなStackageのパッケージ集合としての性質を最大限に活かせるように設計されたのがstackです(多分)。これがあんまりにもうまく出来ていて、しっくりハマっているものですから、正直Stacakge初めて見た時は、こんなのそんなに役に立つんかな、とか思っていたんですが、今ではその構想の素晴らしさにただただ舌を巻いています。
と、そんなstackの使い方を見て行きましょう。
簡単な使い方
使い方はとても簡単なので、特にあんまり解説するべきこともないんですが、せっかくなので簡単なチュートリアルを書いてみようと思います。ちなみに、GitHubにおいてある動作イメージはこんな感じらしいです。
stack.yaml
を作る
プロジェクトのルートにstack.yaml
が必要です。とはいえこれは自動で生成できます。
$ stack init
これによって、カレントディレクトリ以下に存在する全ての.cabal
ファイルがスキャンされて、それらを含むstack.yaml
が生成されます。マッチする"resolver"が自動的に選択されます。
動作例を示してみます。
$ stack init
Writing default config file to: C:\Users\tanakh\Documents\GitHub\msgpack-haskell\stack.yaml
Basing on cabal files:
- C:\Users\tanakh\Documents\GitHub\msgpack-haskell\msgpack-rpc\msgpack-rpc.cabal
- C:\Users\tanakh\Documents\GitHub\msgpack-haskell\msgpack-idl-web\mpidl-web.cabal
- C:\Users\tanakh\Documents\GitHub\msgpack-haskell\msgpack-idl\msgpack-idl.cabal
- C:\Users\tanakh\Documents\GitHub\msgpack-haskell\msgpack-aeson\msgpack-aeson.cabal
- C:\Users\tanakh\Documents\GitHub\msgpack-haskell\msgpack\msgpack.cabal
Checking against build plan lts-2.15
Selected resolver: lts-2.15
Wrote project config to: C:\Users\tanakh\Documents\GitHub\msgpack-haskell\stack.yaml
ここではmsgpack-haskell
ディレクトリ以下の5つの.cabal
ファイルが走査されています。resolverとして、lts-2.15が選択されています。resolverとはなんぞやというのは次節で。
生成されたstack.yaml
は次のような内容になっています。
flags: {}
packages:
- msgpack-rpc/
- msgpack-idl-web/
- msgpack-idl/
- msgpack-aeson/
- msgpack/
extra-deps: []
resolver: lts-2.15
packagesの中に、ビルド対象のパッケージが列挙されます。resolverのところには、ビルドに使うresolverが入ります。
resolver
resolverとはなんぞやという話ですが、要するにStackageのsnapshotのことです。現在存在するのは
- lts-1.x系列
オワコン - lts-2.x系列
現在の安定版。GHC-7.8.4向け - nightly
毎日のスナップショット。GHC-7.10でコンパイルするなら今のところこれを使わないといけない
この三つの系列です。まだ出ていないのですが、GHC-7.10向けの3.x系列もそのうちリリースされるようです。デフォルトではlts-2系の新しいものから順に適合するかチェックされていきます。--resolver
オプションを指定することで、特定のresolverを強制することができます。
$ stack init --resolver=nightly-2015-06-24
stack.yaml
を編集する
また、stackではStackageに含まれていないパッケージも完全にうまく扱えるような機能が用意されています。それどころか、Hackageにアップされてないものも依存関係に取り込めるようになっています。
stack.yaml
のフォーマットを詳しく解説していたらすごい量になってしまいますし、将来的に陳腐化する気もしますので、https://github.com/commercialhaskell/stack/wiki/stack.yamlこちらを参照していただくとして、ここではめぼしいものを解説したいと思います。
extra-deps:
には、resolverに含まれない、あるいは含まれているけど別のバージョンを使いたいときなどに、それを明示的に指定できます。たとえば、stack自身のstack.yaml
では次のようになっています。
packages:
- .
extra-deps:
- optparse-simple-0.0.3
- path-0.5.1
- monad-unlift-0.1.1.0
resolver: lts-2.9
packages:
のところには、プロジェクト内のパッケージを列挙するのでした。
packages:
- dir1
- dir2
- dir3
ここにはディレクトリだけではなく、http上のtarballや、gitのレポジトリを書くこともできます。
packages:
- some-directory
- https://example.com/foo/bar/baz-0.0.2.tar.gz
- location:
git: git@github.com:commercialhaskell/stack
commit: 6a86ee32e5b869a877151f74064572225e1a0398
gitのレポジトリは、特定のコミットを参照する必要があるようです。これは多分時間とともに違うファイル内容を指すようになるのは、stackとして望ましい挙動ではないからでしょう。
この機能によって、未リリースのパッケージに依存するパッケージを開発したり、既存のパッケージにパッチを当てたものに依存するパッケージを開発したりするようなことが、とても簡単にできるようになっています。
その他、地味に嬉しい機能として、
extra-include-dirs:
- /opt/foo/include
extra-lib-dirs:
- /opt/foo/lib
extra-include-dirs
とextra-lib-dirs
を指定する機能があるようです。これまではcabal configureの時に手で毎回与える必要があったので、これが設定ファイルにかけるようになるとずいぶんはかどりそうです。
プロジェクトをビルドする
stack.yaml
ができたら、あとはstack build
を実行するだけです。
$ stack build
これだけで、ビルドに必要なことを全部やってくれます。依存ライブラリのインストールから、happy、alexなどのビルドに必要になるツール、はてはghc自体までダウンロードしてインストールしてくれます。Windowsでも、Linuxでも、適切なやつが落ちてきて、適切な場所にインストールされます。そしてこれらは、全て特別なディレクトリに置かれるので、システムが壊れる心配をする必要はありません。
依存するパッケージがresolverの中に見つからなかったとき、stackはcabalにフォールバックしてHackageのパッケージ全体を探します。
例えば、このように、
$ stack build
While constructing the BuildPlan the following exceptions were encountered:
-- Failure when adding dependencies:
optparse-declarative: needed (>=0.3), but not present in build plan, latest is 0.2.0
needed for package: hoe-1.1.0
-- While attempting to add dependency,
Could not find package optparse-declarative in known packages
Recommended action: try adding the following to your extra-deps in C:\Users\tanakh\Dropbox\project\hoe\stack.yaml
- optparse-declarative-0.2.0
You may also want to try the 'stack solver' command
どのパッケージを解決するのに失敗したのか、なぜ失敗したのか、そして我々はどうするべきかまでを、懇切丁寧に教えてくれます。stack.yaml
のextra-depsへ何を追加したらいいのかがyamlのフォーマットで出力されるので、大体はこれをコピペして貼り付ければビルドが通るようになります。便利すぎる。
また、stack build
のオプションとして--resolver=xxx
を与えると、stack.yaml
で指定したresolverの設定を上書きすることができるので、例えば、普段はresolverにnightly-xxxx-xx-xxなどを指定しておいてghc-7.10で開発し、リリースするときにstack build --resolver=2.15
としてやることでghc-7.8.4でもコンパイルできるかを簡単に試すことが出来ます。
プログラムを実行する
ビルドできたプログラムは、
$ stack exec <プログラムの名前>
で実行できます。ライブラリプロジェクトの場合は、
$ stack ghci
とすることによって、プロジェクトのモジュールを参照できる状態でghciが起動します。
$ stack runghc <haskellファイル>
とやれば同様にrunghcで実行できます。
$ stack test
でテストスイートを実行、
$ stack bench
でベンチマークを実行できます。
プログラム/ライブラリをインストールする
$ stack install <パッケージ名>
とやると、cabal install
の要領で、パッケージがインストールされます。インストールされたパッケージは、resolverに含まれるものならグローバルなところにインストールされ、そうでないものは、プロジェクトローカルなところにインストールされます(これはresolverに含まれないものがグローバルにインストールされると、バージョンを固定できなくなって、あとでcabal hellみたいなことになるからだと思います)。実行ファイルは~/.local/bin
にインストールされます。そういう仕組ゆえに、stack install
はどこで行ってもあまり違いがなく、cabal sandboxを使っている時のように、ここはsandboxのあった場所だろうか、などと常に意識する必要がなく、かなり楽になります。
パッケージをアップロードする
$ stack upload
cabal upload
と同じことをするものですが、cabal upload
と違って、
- tarballを自動的に作ってくれるので事前にcabal sdistでアーカイブを作ってたりファイルを指定する必要がない
- httpsによって安全な経路でアップロードしてくれる
- ユーザー名とパスワードを二回目以降覚えていてくれる
という実に痒いところに手が届く仕様になっています。
その他のコマンド
stack new
プロジェクトのひな形を作ってくれる。まだあんまり大したものは出てこないけど、それなりには便利。多分将来的にはずいぶん便利になる。
stack setup
指定したバージョンのghc環境をインストールしてくれる。とはいえ、これはresolverに紐付いたghcのバージョンが入ってなければ、stack build
が自動的にインストールしてくれるので、明示的にやる機会はあんまりないかもしれない。
stack update
パッケージのインデックスを更新する、cabal update
に対応するコマンドですが、resolverはパッケージのバージョンを固定するものですし、extra-depsもバージョンを完全に固定して使いますので、明示的に必要になるケースは殆ど無いと思います。ライブラリの解決ができなかったとき、自動的に最新のHackageのインデックスを見に行く仕様になっているので、そのへんも抜かりはなさそうです。
stack docker
dockerを使ってないのでわからないのですが、stackのdockerイメージが有るみたいで、それを必要に応じてとってきてくれたり、その中でビルド作業してくれたりするらしいです。今度使ってみます。
まとめ
というわけで、stackの紹介でした。説明してきたように、すでにずいぶん便利なツールなのですが、これからさらに便利になっていくと思いますので、どしどし使ってフィードバックを送りましょう。