りんごがでている

何か役に立つことを書きます

Haskellのパッケージ管理について調べてみた

Haskellやって1年ちょっと経つわけですが、Haskellで使うpackageの管理についてよく知らなかったので色々調べてみました。

対象としては最近Haskellを始めた方やpackage管理についてよく知らないという方向けです、 packageを利用する側からの説明なので、作るにはどうしたらいいかは書いてません(`・ω・´)ゞ

そして、やたら長いのです。

ちなみにghc-7.0.3で、OSはLinux Mintです。

packageって?

packageはHaskellのライブラリを構成する一つのまとまりで、packageはいくつかのmoduleをまとめています。

moduleはだいたい.hs拡張子のHaskellプログラム1ファイルと対応していて、module Foo.Bar whereとファイルの上の方に書いてあるFoo.Barがモジュール名です。

Haskell Platformに含まれる標準的なpackageはココを見てみましょう。 え、Haskell Platformを入れてない? 入れましょう(゚Д゚)

Haskell Platform

f:id:bicycle1885:20121007222819p:plain

右側にある haskell98-2.0.0.1 とかってヤツですね(・・)

packageはそのパッケージから利用できるmoduleがあって、あるパッケージのモジュールを使いたくなったらプログラムからimportするわけです。 どんなモジュールが使えるかは、次のようにghc-pkgコマンドを使うことで調べられます。 textパッケージから利用できるモジュールを調べてみると、

% ghc-pkg field text exposed-modules
exposed-modules: Data.Text Data.Text.Array Data.Text.Encoding
                 Data.Text.Encoding.Error Data.Text.Foreign Data.Text.IO
                 Data.Text.Internal Data.Text.Lazy Data.Text.Lazy.Builder
                 Data.Text.Lazy.Builder.Int Data.Text.Lazy.Builder.RealFloat
                 Data.Text.Lazy.Encoding Data.Text.Lazy.IO Data.Text.Lazy.Internal
                 Data.Text.Lazy.Read Data.Text.Read

となりますね。同じパッケージでも複数のバージョンがあるとそれも出てきます。

ghc-pkgコマンドは他にもいろいろ機能がありますが、それも追って紹介します。

cabalって?

Haskellのパッケージのメタ情報や管理などをしている一つのエコシステムです。

主な役者は、

  • Cabalパッケージ
  • cabal-installパッケージ
  • .cabalファイル
  • cabalコマンド

などです。

各パッケージのメタ情報は<package名>.cabalなるファイルに収められていて、ココに依存関係やらバージョンやらビルド方法など様々な情報が収められています。 Hackageにあるパッケージ群にはこのファイルが付属していて、こいつを使って管理されているようです。

Haskellのパッケージ管理は主にcabalコマンドを通して行います。 cabalコマンドはcabal-installパッケージが提供するCabalパッケージへのインターフェースです。 え?よくわからん?私もですъ(゚Д゚)

ちなみにcabal-installパッケージの依存先を見てみると、

f:id:bicycle1885:20121008004703p:plain

HTTPとかunixとかnetworkとかのパッケージに依存しててイカにも外部と通信してパッケージを取ってインストールしますよ〜ってな感じですね! もちろんCabalパッケージにも依存しているようです。

Cabalパッケージの依存関係はシンプルです。

f:id:bicycle1885:20121008005107p:plain

まぁHaskell Platformを入れていればcabalコマンドは使えるはずなので、特に問題ないはずです。 cabalを使えばHackageからパッケージをインストールしたり、自分の作ったパッケージをビルドしたりHackageへアップロードするのが簡単になります。助かります。

使い方

Hackageから特定のパッケージをインストールしてくるには、cabal installコマンドを使います。

が、そのまえに、Hackageにある最新のパッケージの情報とローカルの情報を同期させるため、

% cabal update

をしましょう。

それから、例えばconduitパッケージをインストールしたいなら、

% cabal install conduit

と打てば、conduitの最新版がインストールされます。 インストールするバージョンの指定もできます。

% cabal install conduit-0.5.2.4

各パッケージの情報は、

% cabal info conduit

とすればパッケージの説明や、現在インストールされているバージョンなどが見られます。 利用可能なモジュール名もここで表示されますね:-)


自分でHaskellのプロジェクトを作るには、

% cabal init

を使えば、対話的に自分のパッケージ管理のための.cabalファイルが生成されます。

自分のパッケージの依存関係を解決してインストールするには、.cabalファイルのあるディレクトリで

% cabal install

とすればOKです。Hackageから自動的に依存しているパッケージを集めてビルドしてくれます。 依存している必要なパッケージが一度入ってしまえば、次からはcabal buildをするだけです。

しかしこのcabalコマンドはなぜだかインストールしたパッケージを削除する機能が無いので、その辺使い勝手が良いcabを使うと良いかもしれませんねъ(゚Д゚)

cab: A maintenance command of Haskell cabal packages

他にも使うパッケージをプロジェクトで共有したくないよ〜って時はcabal-devが便利です。 パッケージの依存関係が他と干渉してメタメタになることはたまによくある(?)ことなので、こういうのも便利ですね!

他にもcabal-installの代替や補佐役のパッケージは色々ありますよ。

packageはどこに?

インストールして利用できるようになったはずのパッケージはどこにあるんでしょう? これは、% ghc-pkg listで一覧で出てきます。

% ghc-pkg list
/var/lib/ghc-7.0.3/package.conf.d
   Cabal-1.10.1.0
   GLUT-2.1.2.1
   HTTP-4000.1.1
   HUnit-1.2.2.3
   OpenGL-2.2.3.0
   QuickCheck-2.4.1.1
   ....
/home/kenta/.ghc/x86_64-linux-7.0.3/package.conf.d
   Cabal-1.14.0
   HUnit-1.2.4.2
   ListLike-3.1.6
   MissingH-1.1.1.0
   MonadCatchIO-transformers-0.3.0.0
   PSQueue-1.1
   QuickCheck-2.4.2
   SHA-1.5.0.1
   SafeSemaphore-0.7.0
   attoparsec-conduit-0.4.0.1
   ....

適当に% cabal installしまくってるとズラズラ〜っとたくさん出てきます。こんな感じでインストールされたパッケージの名前とバージョンが表示されますね。

上に表示された/var/lib/ghc-7.0.3/package.conf.dは、システムにインストールされたパッケージの情報を持っているディレクトリです。Haskell Platformをインストール先を指定せずインストールするとこんな感じのところに入ると思います。

その下の/home/kenta/.ghc/x86_64-linux-7.0.3/package.conf.dは、ユーザーの領域にインストールされたパッケージですね。% cabal installでインストールしたパッケージは普通こっちの管理下に入ります。なので普通に% cabal installするにはsudoは要らないわけです:-)


それでは、package.conf.d/ディレクトリの中を見てみましょう。

% ls /var/lib/ghc-7.0.3/package.conf.d
Cabal-1.10.1.0-e951c182da4a22a7b82c0f2e4be13b7b.conf
GLUT-2.1.2.1.conf
HTTP-4000.1.1.conf
HUnit-1.2.2.3.conf
OpenGL-2.2.3.0.conf
QuickCheck-2.4.1.1.conf
X11-1.5.0.0.conf
X11-xft-0.3.conf
array-0.3.0.2-143060371bda4ff52c270d1067551fe8.conf
base-4.3.1.0-91c3839608ff4d3ec95f734c5ae4f31c.conf
bin-package-db-0.0.0.0-03f52950478226c0334a7d7e83d56e17.conf
....

おぉ、パッケージの設定ファイルみたいなのが、たくさんありますね! package.conf.dってのはpackage configuration directoryの略でしょうかね。

一つ設定ファイルの中身を見てみましょう。中身は人間が読めるようになっています。 (長いので、一部省略)

% cat zlib-0.5.3.1.conf
name: zlib
version: 0.5.3.1
id: zlib-0.5.3.1-7e19941cbd00147a79723e25160ffc8b
license: BSD3
copyright: (c) 2006-2008 Duncan Coutts
(....)
exposed-modules: Codec.Compression.GZip Codec.Compression.Zlib
                 Codec.Compression.Zlib.Raw Codec.Compression.Zlib.Internal
hidden-modules: Codec.Compression.Zlib.Stream
import-dirs: /usr/lib/haskell-packages/ghc/lib/zlib-0.5.3.1/ghc-7.0.3
library-dirs: /usr/lib/haskell-packages/ghc/lib/zlib-0.5.3.1/ghc-7.0.3
hs-libraries: HSzlib-0.5.3.1
extra-libraries: z
extra-ghci-libraries:
include-dirs:
includes: zlib.h
depends: base-4.3.1.0-91c3839608ff4d3ec95f734c5ae4f31c
         bytestring-0.9.1.10-6aa1efbfa95d1689fc03d61e7c4b27c4
(....)

なるほど、「フィールド名: 内容」の形式で、バージョンやどんなモジュールが利用可能か、依存関係なども書いてありますね。 先ほど紹介した% ghc-pkg field <package名> exposed-modulesというヤツは、実はこのファイルのフィールドを指定して、それを表示させているだけでした。 つまり、% ghc-pkg field <package名> <フィールド名>となってます。

ですので、% ghc-pkg field zlib dependsとやれば、

% ghc-pkg field zlib depends
depends: base-4.3.1.0-91c3839608ff4d3ec95f734c5ae4f31c
         bytestring-0.9.1.10-6aa1efbfa95d1689fc03d61e7c4b27c4

と、どのフィールドでもフィールド名を指定してやれば見られるわけです。

実際には、.confファイルをキャッシュしているpackage.cacheファイルを見に行っています。straceがあれば、

% strace ghc-pkg field zlib depends 2>&1 1>/dev/null | grep open

などでわかりますね。

@さんに実際のコードの位置を教えて頂きました。ありがとうございます。( ;∀;)

Packages.lhs#L235

readPackageConfig :: DynFlags -> FilePath -> IO [PackageConfig]
readPackageConfig dflags conf_file = do
  isdir <- doesDirectoryExist conf_file

  proto_pkg_configs <- 
    if isdir
       then do let filename = conf_file </> "package.cache"
               debugTraceMsg dflags 2 (text "Using binary package database:" <+> text filename)
               conf <- readBinPackageDB filename
           return (map installedPackageInfoToPackageConfig conf)

  # (省略)

確かにpackage.cacheを読みに行ってますね!


そして、パッケージの実体がどこにあるのかというと、import-dirslibrary-dirsフィールドに書いてあるようです。 そこをfindで覗いてみると。。。

% find /usr/lib/haskell-packages/ghc/lib/zlib-0.5.3.1/ghc-7.0.3/ -type f -printf '%P\n'
HSzlib-0.5.3.1.o
Codec/Compression/Zlib/Raw.hi
Codec/Compression/Zlib/Stream.hi
Codec/Compression/Zlib/Internal.hi
Codec/Compression/Zlib.hi
Codec/Compression/GZip.hi
libHSzlib-0.5.3.1.a

おぉ、オブジェクトファイル(.o)やらインターフェースファイル(.hi)もありますね!

実際ghciで試してみると、

% ghci
GHCi, version 7.0.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :m Codec.Compression.GZip Data.ByteString.Lazy.Char8 
Prelude Codec.Compression.GZip Data.ByteString.Lazy.Char8> unpack . decompress . compress . pack $ "Compress me!"
Loading package bytestring-0.9.1.10 ... linking ... done.
Loading package zlib-0.5.3.1 ... linking ... done.
"Compress me!"

最後から2番目の行にある通り、ちゃんとzlibパッケージをロードして、リンクしてるらしいことがわかりますね。 *1


ココまではシステム(global)に入ったパッケージについて述べて来ましたが、ユーザー領域でも大体一緒のようです。 インストールされたパッケージは、先程調べたように/home/kenta/.ghc/x86_64-linux-7.0.3/package.conf.dにありました。 内容も一緒ですが、ここにはcabalで入れたものの情報が入ります。

コンパイル済みの実体は、~/.cabal/lib以下に同じように収まってますね。 ~/.cabal/binには、cabal installなどで入れたプログラムの実行ファイルがあります。cabal installでインストールしたプログラムを使うなら、ココへのパスを通しておきましょう:-)

先ほど紹介したcabal-devcabなどの実行ファイルも~/.cabal/binにありました。

それで、今までhackage.haskell.orgのレポジトリしか使ってませんでしたが、実はミラーがあります。 本家hackage.haskell.orgはちょくちょく落ちるので、ミラーを使ってみたりするのも良いかもしれません。

  • hdiff.luite.com
  • hackage.haskell.biz

はすぐ見つけられたのですが、他はよくわかりません(`・ω・´)ゞ

cabalコマンドでこれらのミラーを使うには、~/.cabal/configファイルのremote-remoを編集すれば、リポジトリを切り替えられます。 最初は本家の

remote-repo: hackage.haskell.org:http://hackage.haskell.org/packages/archive

になっているので、この行をコメントアウト(--)して、

-- remote-repo: hackage.haskell.org:http://hackage.haskell.org/packages/archive
remote-repo: hackage.haskell.biz:http://hackage.haskell.biz

とすればOKです。 試しに% cabal updateをしてみると、

% cabal update
Downloading the latest package list from hackage.haskell.biz

ちゃんとミラーに取りに行ってますね!

ちなみに、取ってきたパッケージの情報は、~/.cabal/packages/<リポジトリのホスト名>/00-index.tarに収められています。 本家レポジトリなら、~/.cabal/packages/hackage.haskell.ord/00-index.tarですね。

このtarファイルの中身は、% tar -tf 00-index.tarをしてみると、

% tar -tf 00-index.tar | head 
4Blocks/0.1/4Blocks.cabal
4Blocks/0.2/4Blocks.cabal
AC-Angle/1.0/AC-Angle.cabal
AC-Boolean/1.0.0/AC-Boolean.cabal
AC-Boolean/1.1.0/AC-Boolean.cabal
AC-BuildPlatform/1.0.0/AC-BuildPlatform.cabal
AC-BuildPlatform/1.1.0/AC-BuildPlatform.cabal
AC-Colour/1.1.1/AC-Colour.cabal
AC-Colour/1.1.2/AC-Colour.cabal
AC-Colour/1.1.3/AC-Colour.cabal

.cabalファイルがただひたすら入ってます。凄く...たくさんです...///

長くなってきたので一旦ここまで

なんだかやたらと長くなってきたので一旦ここまでにします。

そのうちある程度まとまったらまた書きます(^m^;)