cabal の build-tool-depends というフィールドが便利だったので紹介します。

テストを書くときに hspec-discovertasty-discover などを使う場合、このフィールドを設定しておくことで依存している実行ファイルを自動的にダウンロードして使ってくれます。

$ cabal --version
cabal-install version 3.0.0.0
compiled using version 3.0.0.0 of the Cabal library 

サンプルプロジェクトの構成

全体のディレクトリ構成は以下のようになっているとしましょう。

$ tree .
.
├── example.cabal
├── src
│   └── Lib.hs
├── stack.yaml
└── test
    ├── LibSpec.hs
    └── Spec.hs

それぞれのファイルの内容は以下のようになっています。

-- src/Lib.hs
module Lib (someFunc) where

someFunc :: IO ()
someFunc = putStrLn "someFunc"
-- test/LibSpec.hs
module LibSpec (spec) where

spec :: Spec
spec = undefined
-- test/Spec.hs
{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
-- example.cabal
cabal-version: 2.4
name:          example
version:       0.1.0.0

library
  exposed-modules:  Lib
  hs-source-dirs:   src
  build-depends:    base >=4.7 && <5
  default-language: Haskell2010

test-suite example-test
  type:             exitcode-stdio-1.0
  main-is:          Spec.hs
  hs-source-dirs:   test
  other-modules:    LibSpec
  build-depends:
    , base     >=4.7 && <5
    , example

  default-language: Haskell2010
# stack.yaml
resolver: lts-14.20
packages:
- .

このような状況で cabalstack がどのように振舞うか見ていきましょう。

現時点では hspec-discover の実行ファイルは存在していません。

$ hspec-discover
bash: hspec-discover: コマンドが見つかりません

stack

hspec-discover の実行ファイルが無い状態で stack test を実行すると以下のようにエラーになります。

$ stack test
...

ghc: could not execute: hspec-discover

hspec-discover をインストールした場合

$ stack install hspec-discover

$ hspec-discover
Usage: hspec-discover SRC CUR DST [--module-name=NAME]

上記のようにインストールした場合は、問題無く動作します。

$ stack test
...

example/test/LibSpec.hs:3:9: error:
    Not in scope: type constructor or class ‘Spec’
  |         
3 | spec :: Spec
  |         ^^^^

hspec-discover がファイルを自動的に見つけてくれたので、コンパイルエラーになりました。

次に進む前に実行ファイルを削除しておきます。

$ rm ~/.local/bin/hspec-discover

build-depends に追加した場合

example.cabal ファイルの build-depends に追加した場合はどうなるのでしょうか?

test-suite example-test
  type:             exitcode-stdio-1.0
  main-is:          Spec.hs
  hs-source-dirs:   test
  other-modules:    LibSpec
  build-depends:
    , base     >=4.7 && <5
    , example
    , hspec-discover  -- 新しく追加した

  default-language: Haskell2010

では stack test を実行してみます。

$ stack test
...

example/test/LibSpec.hs:3:9: error:
    Not in scope: type constructor or class ‘Spec’
  |         
3 | spec :: Spec
  |         ^^^^

実行ファイルがローカル環境に存在しなくても自動的に hspec-discover が動作しています。

そのため、stack プロジェクトでは hspec-discover を依存関係 (build-depends) に追加するだけで良い感じにテストが進みます。

cabal

同様に cabal の場合も確かめてみましょう。(example.cabal ファイルから hspec-discover を削除しておきます)

$ cabal test
...

ghc: could not execute: hspec-discover

stack と同様のエラーになりました。

hspec-discover をインストールした場合

$ cabal install hspec-discover

$ hspec-discover
Usage: hspec-discover SRC CUR DST [--module-name=NAME]

同様に cabal test を実行してみます。

$ cabal test
...

test/LibSpec.hs:3:9: error:
    Not in scope: type constructor or class ‘Spec’
  |
3 | spec :: Spec
  | 

stack の場合と同じように、ちゃんと動いていますね。

build-depends に追加した場合

まずはバイナリファイルを削除しておきます。

$ rm ~/.cabal/bin/hspec-discover

example.cabal ファイルは先ほどと同じように build-dependshspec-discover を追加した状態です。

テストを実行してみましょう。

$ cabal test
...

ghc: could not execute: hspec-discover

stack と異なる結果になりましたね・・・。僕は今までこの結果への対処法は hspec-discover をインストールしておくしかないと思っていました。

しかし、build-tool-depends をフィールドを利用することで stack と同じ挙動になるということがわかりました。

実際に試してみましょう。

build-tool-depends に追加した場合

build-tool-depends に追加するとこんな感じです。

test-suite example-test
  type:             exitcode-stdio-1.0
  main-is:          Spec.hs
  hs-source-dirs:   test
  other-modules:    LibSpec
  build-depends:
    , base     >=4.7 && <5
    , example

  build-tool-depends:                 -- 新たに追加した行
    , hspec-discover:hspec-discover   -- 新たに追加した行

  default-language: Haskell2010

build-depends<package_name> という指定方法でしたが、build-tool-depends では <package_name>:<exe_name> という指定方法になっている点に注意です。

バージョン制約が必要な場合は build-depends と同じように指定できます。

hspec-discover:hspec-discover ^>=2.7

これでテストを実行してみましょう。

$ cabal test
...

test/LibSpec.hs:3:9: error:
    Not in scope: type constructor or class ‘Spec’
  |
3 | spec :: Spec
  | 

これで stack と同じような挙動になりました!happy!

まとめ

こんな便利なフィールドがあるなんて全然知りませんでした・・・。

  • build-tool-depends を指定しておけば、実行ファイルが無くても大丈夫
  • stack は初心者に優しい

GHC-8.8.2 がリリースされましたね。

参考リソース