いものやま。

雑多な知識の寄せ集め

GHCの使い方を調べてみた。(その9)

昨日はGHCi環境のカスタマイズについて説明した。

今日は、GHCを使ったビルドについて。

GHCでのビルド

以下のHaskellソースコードをビルドして、実行ファイルを作ることを考える。

-- Main.hs

import System.Environment
import Control.Monad
import Fibo

main = do
    args <- getArgs
    when (length args > 0) $ do
      let count = read $ head args
      print $ map fibo [1..count]
-- Fibo.hs

module Fibo (fibo) where

fibo :: Integer -> Integer
fibo n = fibo' n 1 1

fibo' :: Integer -> Integer -> Integer -> Integer
fibo' 1 a b = a
fibo' n a b = fibo' (n - 1) b (a + b)

これをビルドするには、次のように、ghcコマンドの引数にMain.hsを指定すればいい。

$ ghc Main.hs

上のコマンドを実行すると、以下のようになる。

$ ls
Fibo.hs     Main.hs
$ ghc Main.hs 
[1 of 2] Compiling Fibo         ( Fibo.hs, Fibo.o )
[2 of 2] Compiling Main         ( Main.hs, Main.o )
Linking Main ...
$ ls
Fibo.hi     Fibo.hs     Fibo.o      Main*       Main.hi     Main.hs     Main.o
$ ./Main 10
[1,1,2,3,5,8,13,21,34,55]
$ 

コンパイルされてオブジェクトファイル(拡張子.o)とインタフェースファイル(拡張子.hi)が作られ、そして、リンクが行われてMainという実行ファイルが作られていることが分かると思う。

依存するモジュールのコンパイル

ここで一つポイントになるのが、Fibo.hsは引数に指定していないということ。
GHCはimport宣言から依存関係を調べて、必要となるモジュールのコンパイルも行ってくれるようになっている。

これはさらに、Makeのようにファイルのタイムスタンプを確認して、必要ならコンパイルを行うということもやってくれる。

$ ls
Fibo.hi     Fibo.hs     Fibo.o      Main*       Main.hi     Main.hs     Main.o
$ ghc Main.hs  -- ファイルは最新なので、何も起こらない
$ touch Fibo.hs  -- Fibo.hsのタイムスタンプを更新
$ ghc Main.hs
[1 of 2] Compiling Fibo         ( Fibo.hs, Fibo.o )  -- Fibo.hsがコンパイルされる
Linking Main ...
$ 

見てのとおり、Fibo.hsのタイムスタンプを更新すると、Fibo.hsだけコンパイルを行って、そしてリンクしてくれているのが分かる。
これはかなり便利。

ソースファイルの置き場所

import宣言からモジュールの定義されているソースファイルを見つけられるようにするために、ソースファイルは規則に従った場所に置く必要がある。

といっても、細かい規則があるわけではなくて、ドット.で区切られて入れ子になっているモジュールのソースファイルは、検索パスに含まれるディレクトリを起点として、ドットをディレクトリの区切りに置き換えた場所に置きましょう、というだけ。

例えば、A.B.Cというモジュールなら、(検索パスに含まれるディレクトリ)/A/B/C.hsとする必要がある。

もしモジュールが入れ子になっていないのであれば、検索パスに含まれるディレクトリにソースファイルが置いてあれば問題ない。

検索パスの追加

検索パスを追加したい場合には、-iオプションを使う。
-iに続いて(半角スペースを入れずに)検索パスに追加したいディレクトリを指定すればいい。
複数指定したい場合、コロン:で区切る)

例えば、先程のMain.hsとFibo.hsをsrc/というディレクトリに置いてビルドするには、次のようにする:

$ mkdir src
$ mv Main.hs Fibo.hs src/
$ ls
src/
$ ls src/
Fibo.hs     Main.hs
$ ghc -isrc src/Main.hs 
[1 of 2] Compiling Fibo             ( src/Fibo.hs, src/Fibo.o )
[2 of 2] Compiling Main             ( src/Main.hs, src/Main.o )
Linking src/Main ...
$ ls
src/
$ ls src/
Fibo.hi     Fibo.hs     Fibo.o      Main*       Main.hi     Main.hs     Main.o
$ 

ここで、ちょっと気をつけたいことが2つ。

まず、ghcの引数には、Main.hsではなくsrc/Main.hsを指定しないといけないということ。
検索パスにsrc/が入っているので、Main.hsを指定すれば見つけてくれそうなものだけど、見つけようとするのはモジュール名に対するソースファイルなので、引数で指定されたソースファイル自体を見つけてくれるわけではない。

もう一つは、出力がsrc/の中にされているということ。
GHCは、デフォルトでは出力をソースファイルと同じ場所に行うようになっている。
なので、オブジェクトファイルやインタフェースファイル、実行ファイルが、ソースファイルの置かれているsrc/に出力されている。

出力先のコントロール

オブジェクトファイル、インタフェースファイルや、実行ファイルが生成される場所が、ソースファイルを置いてあるディレクトリと同じだと、ごちゃごちゃして分かりにくい。
なので、生成されるファイルの出力先をコントロールするためのオプションが用意されている。

最終的な出力ファイル名の指定

最終的な出力ファイル名を指定するには、-oオプションを使う。

$ ghc -isrc -o fibonacci src/Main.hs 
[1 of 2] Compiling Fibo         ( src/Fibo.hs, src/Fibo.o )
[2 of 2] Compiling Main         ( src/Main.hs, src/Main.o )
Linking fibonacci ...
$ ls
fibonacci*      src/
$ ls src/
Fibo.hi     Fibo.hs     Fibo.o      Main.hi     Main.hs     Main.o
$ 

最終的に作られた実行ファイルが指定された名前になっていることが分かると思う。

オブジェクトファイルなどの出力先の指定

オブジェクトファイルやインタフェースファイルを出力するディレクトリを指定するには、-outputdirオプションを使う。

$ ghc -isrc -outputdir bin -o fibonacci src/Main.hs 
[1 of 2] Compiling Fibo             ( src/Fibo.hs, bin/Fibo.o )
[2 of 2] Compiling Main             ( src/Main.hs, bin/Main.o )
Linking fibonacci ...
$ ls
bin/            fibonacci*      src/
$ ls bin/
Fibo.hi         Fibo.o          Main.hi         Main.o
$ ls src/
Fibo.hs         Main.hs
$ 

オブジェクトファイルやインタフェースファイルが指定されたディレクトリに出力されているのが分かると思う。
これでソースファイルの入ったディレクトリはキレイなまま。

なお、出力先に指定されたディレクトリが存在しなければ、勝手に作ってくれる。
便利!

今日はここまで!