もうcabal hellは怖くない、Stackを使ってみるよ!
はいはいどうも、最近はずっとドラクエやってます。 ちゅーんさんです。
人生ではじめてプレイしたRPGってドラクエ6だった気がします。
スマフォ版で久々にプレイしたのですが、やっぱりアレです。
バーバラちゃん、いいですね。
あ、ひとまずラスボス倒したので、今は3やってます。
ポカパマズさああああああん!!!!
はい。
というわけで、 この記事はちゅーんさんがイマドキのHaskellのビルドツールである、 stackを2日くらいぐりぐりいじって覚えた使い方をまとめようと思います。
ざっくりと、stackってどんなもんよ、みたいな話は
http://qiita.com/tanakh/items/6866d0f570d0547df026
の前半を読むとだいたいわかりますので、 ここでは実用的な具体例はさておき、とにかく動かしてみたい人に向けて、 「ここに書かれてる通りに色々やったら、なんとなくstackの使い方が分かった気がするー。」 くらいの感じのチュートリアルになるように頑張ります。頑張りました。
2015/7/13 18:19 投稿当時、本記事でsolver内のパッケージを使用する際に、stack.yamlへの追記を行っている場所がありましたが、solverに含まれないパッケージを使用する場合にのみ設定が必要とのご指摘を頂きました。現在は修正済です。
インストール
ここ
https://github.com/commercialhaskell/stack
のHow to installを参照しましょう。
自分の場合、Ubuntu14.04なので、ターミナルから
$ wget -q -O- https://s3.amazonaws.com/download.fpcomplete.com/ubuntu/fpco.key | sudo apt-key add - $ echo 'deb http://download.fpcomplete.com/ubuntu/trusty stable main'|sudo tee /etc/apt/sources.list.d/fpco.list $ sudo apt-get update && sudo apt-get install stack -y
でなんか良くわからないけど入りました。てへぺろ。
実行プログラムを作る
準備
まず、適当なフォルダを作って、次のような手順でstack new
コマンドを実行します。
$ mkdir firstApp $ cd firstApp $ stack new
すると、以下のような最低限のファイルがひと通り揃ったプロジェクトファイルが出来上がります。
. ├── app │ └── Main.hs ├── LICENSE ├── new-template.cabal ├── Setup.hs ├── src │ └── Lib.hs ├── stack.yaml └── test └── Spec.hs 3 directories, 7 files
基本的には、これをベースにいい感じに書き換えてやればOKみたいです。
まず、以下のファイルをリネームします。
new-template.cabal
->firstApp.cabal
Lib
->FirstApp.hs
で、この2ファイルとMain.hs
を、それぞれ次のように書き換えます。
src/FirstApp.hs:
module FirstApp ( message ) where message :: String message = "Hello, Stack!"
app/Main.hs:
module Main where import FirstApp main :: IO () main = putStrLn message
firstApp.cabal:
name: firstApp version: 0.1.0.0 synopsis: Initial project template from stack description: Please see README.md homepage: http://github.com/name/project license: BSD3 license-file: LICENSE author: Your name here maintainer: your.address@example.com -- copyright: category: Web build-type: Simple -- extra-source-files: cabal-version: >=1.10 library hs-source-dirs: src exposed-modules: FirstApp build-depends: base >= 4.7 && < 5 default-language: Haskell2010 executable firstApp hs-source-dirs: app main-is: Main.hs ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base , firstApp default-language: Haskell2010 test-suite firstApp-test type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Spec.hs build-depends: base , firstApp ghc-options: -threaded -rtsopts -with-rtsopts=-N default-language: Haskell2010 source-repository head type: git location: https://github.com/name/project
cabalファイルはちょっと解りづらいですが、パッケージ名やファイル名等をビルド出来るようにリネームしたので、 それに合わせてパッケージ名やモジュール名などを編集しただけです。
実際には、synopsisとかcategoryとか、開発するプロジェクトに合わせて色々設定しましょう。
stack build
stack build
は、プロジェクトをビルドするコマンドです。
これによって、インストールされていない依存パッケージ等が解決されます。
後述のstack ghci
等を使う前に、一回は実行する必要があるみたいですので、とりあえず実行しましょう。
cabalファイルの設定に不備があれば、この段階で指摘されるので修正していきます。
stack ghci
無事ビルドが成功したらstack ghci
を実行すると、FirstAppモジュールが読み込まれた状態でGHCiが起動します。
こういう時に実行されるGHCのバージョンとかがどうやって決まるのかは、今の所ちゃんと調べてません。solver(Stackageのスナップショット)の設定によるんだと思われます。
$ stack ghci Configuring GHCi with the following packages: firstApp GHCi, version 7.8.4: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Loading package firstApp-0.1.0.0 ... linking ... done. [1 of 1] Compiling FirstApp ( /home/tune/Documents/Program/Haskell/StackTutorials/Tutorial/firstApp/src/FirstApp.hs, interpreted ) Ok, modules loaded: FirstApp. *FirstApp> message "Hello, Stack!"
尚、この時に対応するバージョンのGHCがインストールされていない場合はエラーになります。
その場合、stack setup
するだけで対応するGHCが簡単にインストールされますが、
stack build
でも自動的に解決されますので、基本的にこのコマンドを使う事は無いでしょう。
うーん、階層の深い所にある、小さなモジュールだけGHCiでロードして動作確認したい場合ってどうするのが良いんですかね?
stack runghc
んで、main
を実行してみたいのであれば、stack runghc
コマンドを使います。
$ stack runghc app/Main.hs Hello, Stack!!
こんな感じで、小さい単位で実行出来るのはsrc
配下のライブラリ部分だけなので、
app
以下のフォルダは必然的に最小限になるようです。
stack install
で、出来上がったアプリケーションをインストールしたい場合は、stack install
コマンドを使います。
$ stack install ...略... Installed executables to /home/tune/.local/bin/: - firstApp $ firstApp Hello, Stack!
実行ファイルにパスが通っていれば、普通にコマンドラインから実行できるようになります。
ライブラリを作る/使う
ライブラリを作る
ライブラリを作る場合も、基本的な考え方は同じで、 cabalファイルをライブラリ用に書き換えでやればOKな感じですね。
まず、firstApp
と同じ感覚でプロジェクトを作成しましょう。
$ mkdir firstLib $ cd firstLib $ stack new
で、次のようにリネームしたり、appフォルダを削除したりしまして…
$ tree . ├── firstLib.cabal ├── LICENSE ├── Setup.hs ├── src │ └── FirstLib.hs ├── stack.yaml └── test └── Spec.hs
各ファイルを以下のようにして書き換えます。
src/FirstLib:
module FirstLib ( firstLib ) where firstLib :: IO () firstLib = putStrLn "`firstLib` called!"
firstLib.cabal
name: firstLib version: 0.1.0.0 synopsis: Initial project template from stack description: Please see README.md homepage: http://github.com/name/project license: BSD3 license-file: LICENSE author: Your name here maintainer: your.address@example.com -- copyright: category: Web build-type: Simple -- extra-source-files: cabal-version: >=1.10 library hs-source-dirs: src exposed-modules: FirstLib build-depends: base >= 4.7 && < 5 default-language: Haskell2010 test-suite firstLib-test type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Spec.hs build-depends: base , firstLib ghc-options: -threaded -rtsopts -with-rtsopts=-N default-language: Haskell2010 source-repository head type: git location: https://github.com/name/project
後は、stack build
でビルドして、stack ghci
で動作確認してみましょう。
で、これをStackageに上げるためには、stack upload
を使うみたいなんですが、
この辺はまだ試していないので良くわからんです(`・ω・´)キリッ
作成したライブラリを使う
さて、今作ったfirstLib
をfirstApp
から使う方法を説明します。
firstApp
側のstack.yaml
と、firstApp.cabal
それぞれに、firstLib
に依存している事を記述します。
cabalファイルはご存知の通りプロジェクトをビルドするための情報ですが、stack.yamlはstackがパッケージの依存関係等を安全に解決するための設定ファイルです。
stack.yaml
flags: {} packages: - '.' - '../firstLib/' extra-deps: [] resolver: lts-2.17
firstApp.cabal
...略... executable firstApp hs-source-dirs: app main-is: Main.hs ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base , firstApp , firstLib default-language: Haskell2010 ...略...
で、app/Main.hs
でFirstLib
をimport
しーの…
app/Main.hs
module Main where import FirstApp import FirstLib main :: IO ()の main = do putStrLn message firstLib
stack build
でビルドしーの、stack runghc
で実行。
$ stack build $ stack runghc app/Main.hs Hello, Stack! `firstLib` called!
良い感じですね。
Stackageのライブラリを使う
試しに、lensをfirstApp
から使えるようにしてみましょう。
んで、最初solverのパッケージを使うためには単純にstack.yamlに記述が必要だと思っていたのですが、 どうやらcabalファイルにパッケージ名を追記すればOKなようです。
firstApp.cabal
...略... executable firstApp hs-source-dirs: app main-is: Main.hs ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base , firstApp , firstLib , lens default-language: Haskell2010 ...略...
もう一度、現段階のstack.yamlの設定を見てみましょう。
stack.yaml
flags: {} packages: - '.' - '../firstLib/' extra-deps: [] resolver: lts-2.17
resolver
の設定がlts-2.17
となっていますが、これはこのプロジェクトと対応したsolverのバージョンです。
Stackageのサイト内にある、該当するsolverのHoogle検索でlensに関する演算子を検索してみましょう。
https://www.stackage.org/lts-2.17/hoogle?q=%28.~%29
ここから、LensのHaddockに飛ぶと、バージョンが4.7.0.1
になっています。
このようにして、自動的にインストールされるパッケージのバージョンを知る事ができます。
んで、ビルドして実行。
勝手にlensをStakageからダウンロードして、インストールしてくれます。
$ stack build $ stack runghc app/Main.hs Hello, Stack! `firstLib` called! --------------------------- 222 ("Hoge","Stack","Fuga")
Lensは巨大なライブラリなのでビルドには時間がかかりますが、 同じsolverのライブラリ群はどのような組み合わせでビルドしても、依存関係で詰まる事は無い事がわかっているので、 コーヒー片手にすごいH本でも読みながらまったり待ちましょう。
テストを書く
そういえば、なんやかんや型のおかげであんまりテストを必要に感じる事がなくて cabal経由でテストを試してみた事が無かったわけですけど、 いつ必要になるかわかりませんし、stackの練習も兼ねて作ってみましょう。
今回テストを追加してみるのは、firstLib
の方です。
そういえば、firstLib
の関数をIOにしてしまってましたね…、
面倒くさいので、テストしやすいようにメッセージ部分を切り離します。
src/firstLib.hs
module FirstLib ( firstLib , libMessage ) where firstLib :: IO () firstLib = putStrLn libMessage libMessage :: String libMessage = "`firstLib` called!"
で、意味の無いテストですが、HUnitを使ってこのメッセージの内容をチェックするテストを書いてみます。
test/Spec.hs
...略... test-suite firstLib-test type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Spec.hs build-depends: base , firstLib , HUnit ghc-options: -threaded -rtsopts -with-rtsopts=-N default-language: Haskell2010 ...略...
test/Spec.hs:
import Test.HUnit import FirstLib messageTest :: Test messageTest = "MessageTest" ~: libMessage ~=? "`firstLib` called!" main :: IO () main = runTestTT messageTest >>= print
これで、stack test
を実行すれば、このテストを実行する事ができます。
$ stack test firstLib-0.1.0.0: test (suite: firstLib-test) Cases: 1 Tried: 1 Errors: 0 Failures: 0 Counts {cases = 1, tried = 1, errors = 0, failures = 0}
OKですね。
まとめ
とゆーわけで、ざっくりとstackの使い方をまとめてみました。
- cabal sandboxよりはだいぶ使いやすい
- ライブラリ間の依存関係に悩まされなくて済む
- GHCのバージョンが上がってもプロジェクトが死なないで済む
とゆー感じで、ちょっと触った感触だと、かなり魅力的なビルドツールと言えます。
あと、既存のプロジェクトをstackで管理できるようにするstack init
なんてコマンドもあるみたいですが、
これは近々に必要なので試してみようと思います。気が向いたらまた記事にするかもしれません。
そういえば、他の言語だと、こういう問題ってどうやって解決してるんでしょう?
それから、複数のsolverに対応したい場合とかって、どうすれば良いんですかねー(´・ω・`)
とかまぁ、色々気になる点はありますが、 慣れてくるとなかなか良い感じなので、愛用していこうと思いました。まる。
ではではノシノシ