今更ghc-modの設定でつまずいたのでメモ。
はじめに
競プロ、流行ってますね。私も最近(でもないですが)、AtCoderで競プロをはじめました。
AtCoderではHaskellのコードも提出できるのですが、提供されているGHCは7.10.3と、少し(?)古めです。
イマドキのhaskellの開発環境はHaskell IDE Engineを使用するようですが、
残念ながらGHC7系はサポートされていないようです。
そのため、ghc-mod
をemacsのバックエンドとして使用して開発環境を構築することにします。
環境は以下の通り。
version | |
---|---|
stack | 1.9.3 |
resolver | lts-6.35 (ghc 7.10.3) |
ghc-mod | 5.5.0.0 |
ビルドしたパッケージがghc-modから認識されない
私が競プロに取り組むとき、新しいプロジェクトを作成せずglobal projectでコードを書いています。
必要なパッケージも、ビルドしています(例えば、Data.Vector
を使用するためにvector
パッケージをstack build
します)。
さて、追加でビルドしたパッケージ中のモジュールを使ってコードを作成したとき、
stack ghci
などで作成したコードをそのまま試す分には問題ないです。
ただし、emacs等の中でghc-mod
による構文チェックが実行されているとき、以下のようになって追加導入モジュールが認識されません。
import qualified Data.Vector as V
...
Main.hs:1:18:Could not find module `Data.Vector'Perhaps you meant Data.Functor (from base-4.8.2.0)Use -v to see a list of the files searched for.
すなわち、ghc-mod
からはData.Vector
が見えていません。
これは、ghc-mod check Main.hs
で直接構文チェックをした時も同様です。
解決
issue
ghc-mod Failed to load interface for ‘Data.List.Split’ #878
を参考にて色々試してみた結果、GHC_PACKAGE_PATH
を環境変数として以下のように設定するのが良さそうです。
export GHC_PACKAGE_PATH=$(stack path --local-pkg-db):$(stack path --snapshot-pkg-db):$(stack path --global-pkg-db)
補足
注意: 以下の内容は私が検証した結果であり、上記issueで@DanielGさんが指摘している内容とは一部異なる可能性があります。
ghc-mod
がパッケージの存在を確認する場所は以下のディレクトリです。
- システム領域(stackでghcをインストールする限り、通常はここは使用しない)
- ユーザごとの領域(
stack path --global-pkg-db
で表示されるパス、各ユーザの領域にインストールしたGHCに対応するパッケージDBのディレクトリ)
ghc-mod
はデフォルトでは上記のみ確認します。
Stackを使用してパッケージ管理する場合は、上記に加えてさらに2つのパッケージDB用ディレクトリが存在します。
- スナップショットごとの領域(
stack path --snapshot-pkg-db
で表示されるパス) - プロジェクトごとの領域(
stack pathlocal-pkg-db
で表示されるパス、global projectもここに含まれるっぽい)
今回ビルドしたパッケージは、stack build vector
コマンドで実行しており、プロジェクト固有の(extra-deps
に記載される)パッケージではないため、「スナップショットごとの領域」に登録されます。
そのため、ghc-mod
からは見えず、従ってemacs上でパッケージが認識されません。
一方、以下のようにした場合は事情が異なります。
# stack exec -- ghc-mod check Main.hs
stack exec
コマンドは、プロジェクト内でビルドしたアプリを実行するために使うことが多いです(私はその認識でした)が、実際には、「Stackが作った環境情報を追加した環境で、stack exec
以下の任意のコマンドを実行する」ためのコマンドとして理解する方が正しいようです。
stack exec ls
みたいなコマンドも実行できますし、以下のように環境変数を比較すると、
# diff <(env | sort) <(stack exec env | sort)
0a1,4
> GHC_PACKAGE_PATH=/root/.stack/global-project/.stack-work/install/x86_64-linux/lts-6.35/7.10.3/pkgdb:/root/.stack/snapshots/x86_64-linux/lts-6.35/7.10.3/pkgdb:/root/.stack/programs/x86_64-linux/ghc-7.10.3/lib/ghc-7.10.3/package.conf.d
> HASKELL_DIST_DIR=.stack-work/dist/x86_64-linux/Cabal-1.22.5.0
> HASKELL_PACKAGE_SANDBOX=/root/.stack/snapshots/x86_64-linux/lts-6.35/7.10.3/pkgdb
> HASKELL_PACKAGE_SANDBOXES=/root/.stack/global-project/.stack-work/install/x86_64-linux/lts-6.35/7.10.3/pkgdb:/root/.stack/snapshots/x86_64-linux/lts-6.35/7.10.3/pkgdb:
7c11
< PATH=/root/bin:/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
---
> PATH=/root/.stack/global-project/.stack-work/install/x86_64-linux/lts-6.35/7.10.3/bin:/root/.stack/snapshots/x86_64-linux/lts-6.35/7.10.3/bin:/root/.stack/compiler-tools/x86_64-linux/ghc-7.10.3/bin:/root/.stack/programs/x86_64-linux/ghc-7.10.3/bin:/root/bin:/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
9a14
> STACK_EXE=/usr/local/bin/stack
11c16
< _=/usr/bin/env
---
> _=/usr/local/bin/stack
stack exec
の有無で環境変数に差が出ていることがわかります(PATHも変更され、アプリのバイナリが存在するディレクトリにPATHが通っています)。
ここで注目するのが、stack exec
で追加されたGHC_PACKAGE_PATH
環境変数です。
この変数に格納されたパスを、ghc-mod
はパッケージ検索の際に使用します(この中に、スナップショットごとの領域も含まれます)。
この値は、それぞれglobal projectで実行されたstack path
の内容となっています(解決策で設定したのと同じ内容です)。
終わりに
一応、パッケージ検索用の環境変数を設定することで問題は解決しました。が、issueでは、@lierdakilさんが以下のように指摘しています。
あなたがbaseからのパッケージだけを使用する10行のスクリプトを持っているならば、すべては今のようにうまくいくでしょう。
そうでない場合は、global projectを汚染するのではなく、stack new
を使用してプロジェクトを作成することをお勧めします。
これはその通りで、そもそも複数のsnapshotが並存している場合には、上に書いた解決策ではうまくいかない気がします。
私の場合は、競プロでのコーディング用にDockerでresolverを固定したイメージを作って使用しているため、上記の方法でうまくいっています。
なお、今回の設定内容はglobal projectでしか正しく動きません。
...とここまで書いといてなんですが、
stack exec -- emacs Main.hs
とかでいいような気がしてきました。
これなら何も汚さないし。
以上です。