Stackでextra-depsを自動設定する方法の比較

Stackで不用意に依存関係を触って失敗したので、`extra-deps`を設定する方法についてちゃんと調べてみました。`stack solver`と`stack init --solver`の振舞いの相違について例を用いて説明します。

Stackのバージョンは0.1.10.0 i386です。執筆時点での`lts`の最新バージョンは6.11です。

また`stack`コマンドには適宜`--force`を付けていると読み替えてください。

# 入力例
元は`stack new solver-test new-template`で生成したものです。それを、Stackageに含まれない[`uri`パッケージ][pk-uri]と依存関係に起因するエラーの原因となる[`gtk`パッケージ][pk-gtk]を使うように編集したわざとらしい例になっています。

```text:solver-test.cabal
name:                solver-test
version:             0.1.0.0
synopsis:            Initial project template from stack
description:         Please see README.md
homepage:            https://github.com/githubuser/solver-test#readme
license:             BSD3
license-file:        LICENSE
author:              Author name here
maintainer:          example@example.com
copyright:           2016 Author name here
category:            Web
build-type:          Simple
-- extra-source-files:
cabal-version:       >=1.10

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

executable solver-test-exe
  hs-source-dirs:      app
  main-is:             Main.hs
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  build-depends:       base
                     , solver-test
                     , gtk
                     , uri
  default-language:    Haskell2010

test-suite solver-test-test
  type:                exitcode-stdio-1.0
  hs-source-dirs:      test
  main-is:             Spec.hs
  build-depends:       base
                     , solver-test
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  default-language:    Haskell2010

source-repository head
  type:     git
  location: https://github.com/githubuser/solver-test
```

```hs:app/Main.hs
module Main where

import Lib
import Graphics.UI.Gtk
import Text.URI

main :: IO ()
main = return ()
```
# とりあえずstack init
`stack init`すると次のようなメッセージが出力されます。

```
Writing default config file to: /home/.../dev/haskell/solver-test/stack.yaml
Basing on cabal files:
- /home/.../dev/haskell/solver-test/solver-test.cabal

Checking against build plan lts-6.10

* Build plan did not match your requirements:
    uri not found
    - solver-test requires -any


Checking against build plan lts-5.15

* Build plan did not match your requirements:
    uri not found
    - solver-test requires -any


Checking against build plan nightly-2016-08-06

* Build plan did not match your requirements:
    uri not found
    - solver-test requires -any


Checking against build plan nightly-2016-05-25

* Build plan did not match your requirements:
    uri not found
    - solver-test requires -any


Checking against build plan lts-6.11

* Build plan did not match your requirements:
    uri not found
    - solver-test requires -any


Checking against build plan lts-5.18

* Build plan did not match your requirements:
    uri not found
    - solver-test requires -any


There was no snapshot found that matched the package bounds in your .cabal files.
Please choose one of the following commands to get started.

    stack init --resolver lts-6.10
    stack init --resolver lts-5.15
    stack init --resolver nightly-2016-08-06
    stack init --resolver nightly-2016-05-25
    stack init --resolver lts-6.11
    stack init --resolver lts-5.18

You'll then need to add some extra-deps. See:

    https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md#extra-deps

You can also try falling back to a dependency solver with:

    stack init --solver

```
長ったらしいですが、「`uri`パッケージが見つからない」ということをスナップショットのバージョンごとに言っているだけです。

さらに後半部分では次に試すべきことが２つ書かれています。
1. `stack init --resolver xxx`の後、`extra-deps`を編集。
2. `stack init --solver`。

まずは1.の方法から見てみます。
# 1.の方法 - stack solver
今回はresolverとして`lts-6.10`を選びます。

```
$ stack init --resolver lts-6.10 --force
Writing default config file to: /home/.../dev/haskell/solver-test/stack.yaml
Basing on cabal files:
- /home/.../dev/haskell/solver-test/solver-test.cabal

Checking against build plan lts-6.10

* Build plan did not match your requirements:
    uri not found
    - solver-test requires -any


Selected resolver: lts-6.10
Wrote project config to: /home/.../dev/haskell/solver-test/stack.yaml

```

無事stack.yamlが生成されたので、次は`extra-deps`への記述を行います。記述内容は`stack solver`で計算させることが出来ます。時間とメモリを食う作業なので注意。

```
$ stack solver
Asking cabal to calculate a build plan, please wait
This command is not guaranteed to give you a perfect build plan
It's possible that even with the changes generated below, you will still need to do some manual tweaking
extra-deps:
- uri-0.1.6.4

To automatically modify your stack.yaml file, rerun with '--modify-stack-yaml'
```

今回`extra-deps`に記述するべき内容が出力されるので、これをstack.yamlにコピペしてやればOKです。
また、メッセージの最後にある通り、`--modify-stack-yaml`を付けてやればstack.yamlを自動で編集してくれます。但し、結果を覚えてくれる訳ではないので同じだけ時間がかかります。

# 2.の方法 - stack init --solver
次に2.の方法を試してみます。
`stack init --solver`を実行するとstack.yamlが次のような内容になります。

```yaml:stack.yaml（抜粋）
# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)
resolver: ghc-7.10.3

# Local packages, usually specified by relative directory name
packages:
- '.'

# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)
extra-deps:
- cairo-0.13.3.0
- gio-0.13.3.0
- glib-0.13.4.0
- gtk-0.14.5
- pango-0.13.3.0
- safe-0.3.9
- uri-0.1.6.4
- utf8-string-1.0.1.1
```
resolverが`ghc`になっていることに注意してください。この場合stackは、Stackageのスナップショットではなくコンパイラ付属のパッケージを使用します。実際`extra-deps`に追加された`cairo`、`gio`、`glib`、`gtk`、`pango`、`safe`、`utf8-string`はStackage（例えばlts-6.10）に含まれているものです。

そしてこれはビルドに失敗し、次のようなエラーを出力します。

```shell-session 
    /tmp/stack20983/cairo-0.13.3.0/Setup.hs:5:8:
        Could not find module ‘Gtk2HsSetup’
        Use -v to see a list of the files searched for.
```

`Gtk2HsSetup`は[`gtk2hs-buildtools`][pk-gtk-bt]というパッケージ（これも本来Stackageに含まれる）のバージョン0.13.1.0以降が公開しているモジュールです。今回はこのモジュールに対する依存関係を解決できなかった[^1]ということですね。

[^1]: ちなみに依存関係が解決できない理由はよく理解できていません。githubのissueの[これ][gi1]と[これ][gi2]が関係あるとは思うのですが。

[こちら][fpc-blog]に書いてありますが、`stack init --solver`では**コンパイルできるものが出てくるとは限りません。**

Stackageのスナップショットを使わないというのは非常にもったいないので、`stack init --solver`を使うのはなるべく控えたほうが良さそうです。

# まとめ
- `stack solver`の場合は出力をコピペしてやる必要がある。但し、`--modify-stack-yaml`を付けてやれば自動編集してくれる。
- `stack init --solver`ではビルドできるものが出てくるとは限らない。
- stackのメッセージはとても親切なのでよく読むべき。

[pk-uri]:https://hackage.haskell.org/package/uri
[pk-gtk]:https://hackage.haskell.org/package/gtk
[pk-gtk-bt]:https://hackage.haskell.org/package/gtk2hs-buildtools
[fpc-blog]:https://www.fpcomplete.com/blog/2015/08/new-in-depth-guide-stack
[gi1]:https://github.com/gtk2hs/gtk2hs/issues/171
[gi2]:https://github.com/fpco/stackage/issues/1471