cabal sandbox環境のpackage DBを参照する(改)

前回の記事は調査不十分だった.

まず,sandbox環境では,

cabal install --only-dependencies --enable-tests
cabal configure --enable-tests
cabal build
cabal test

を実行したときと,

cabal install --enable-tests

を実行したときで,config dist prefixが異なる.前者では,これまで同様にdistで,後者ではdist/dist-sandbox-HASHとなる.HASHの決まり方は前回の記事参照.

このHASH付きのprefixが使われるのはちゃんとした理由がある.cabal sandboxにはadd-sourceというローカルパッケージディレクトリに依存させる機能がある.どういうことかというとパッケージBがパッケージAに依存していて共にローカルにソースツリーがある場合.

cd /path/to/B
cabal sandbox init
cabal sandbox add-source /path/to/A
cabal install --only-dependencies

とすることで,ローカルに置いてあるパッケージAを依存パッケージとして使うことができるというものだ.このとき,BのためにビルドされるAは./path/to/A/dist/dist-sandbox-[Bの.cabal-sandboxのフルパスを基準にしたHASH]以下で作業される.もし,パッケージAをパッケージBとパッケージCからそれぞれcabal sandbox add-sourceするような場合に,Bからcabal installする場合のAの依存関係はBの依存関係に引きずられ,Cからcabal installする場合のAの依存関係はCの依存関係に引きずられるため,それぞれ違うパッケージバージョンセットに依存してビルドされたAが必要になっているかもしれない.同じdistディレクトリを使っていたままでは,BとCを並列にビルドするようなときに衝突してしまうため,Bを基準に決まるHASH,Cを基準に決まるHASHの付いたディレクトリにそれぞれ分けられてA以下でビルドされる.

このことがテストなどビルドが必要な操作に影響するのは前回書いた通りであるが,前回はcabal install --enable-testsのときの話しかしておらず,sandboxかどうかだけで分ければいいと思っていた.明らかに間違いである.正しい認識では「sandbox環境ではどのcabal subcommandからテストが実行されるかによってconfig dist prefixを変えなければならない」ということになる.sandboxかどうかだけでは判断できない.

これに影響を受けるもので最も身近なのは恐らくdoctestだと思う.CPP拡張やPath_*.hsを使っているモジュールに対してdoctestを実行するような場合などは必ずautogen以下を使う必要がある.たとえば,test-suite doctestsのコードは,

-- doctests.hs
import Test.DocTest

main :: IO ()
main = doctest [ "-isrc"
               , "-idist/build/autogen/"
               , "-optP-include"
               , "-optPdist/build/autogen/cabal_macros.h"
               , "Test.Target.Module"
               ]

といった具合になるわけだが,このままではこの記事の冒頭に示した2通りのテスト実行コマンド列をカバーできておらず,実際cabal install --enable-testsのほうであたりまえのようにコケる.cabal_macros.hがみつからないか,みつかったとしても期待してるのと別のファイルを見てるハズだ.なので,2通りのどちらで実行されたかを知らねばならないのだが,残念なことにあまり良い方法が無い.Setup.hsに手を入れて上手いこと判断できるような気はするのだが,Setup.hsを弄るのは面倒が増えるし地獄っぽいのであまりやりたくない.というかCabalパッケージをあまり見たくない.目がー!ってなる

結局,テスト実行バイナリが自身のファイルの位置を基準にする方法がリーズナブルではないだろうか.バイナリファイルの位置は,前者のケースではdist/build/doctests/doctests,後者のケースではdist/dist-sandbox-HASH/doctests/doctestsになる.たとえば,linux環境下では/proc/self/exeがプロセス自身のファイルへのシンボリックリンクになるので,test-suite doctestとしては,

-- doctests.hs
import Test.DocTest

import System.FilePath
import System.Posix.Files

getConfDistDir :: IO FilePath
getConfDistDir = fmap (dirname . dirname . dirname) getModuleFile where
    dirname = takeDirectory
    getModuleFile = readSymbolicLink "/proc/self/exe" -- ghc 7.6.1 以降なら getExecutablePath

main :: IO ()
main = do
  confDistDir <- getConfDistDir
  doctest [ "-isrc"
          , "-i" ++ confDistDir ++ "/build/autogen/"
          , "-optP-include"
          , "-optP" ++ confDistDir ++ "/build/autogen/cabal_macros.h"
          , "Test.Target.Module"
          ]

のようになる.これなら冒頭に示したどちらのケースで実行されても期待する通りにテストが走る.Windowsは知らないけどたぶん似たようなことできるだろう.ghc 7.6.1 以降なら getExecutablePath でよさげ.でも,現在のdebian wheezyはghc 7.4.1なので,そこへんまでカバーさせたいライブラリでテストも対応させたいなら別の方法で.