GHCの最新のソースコードをビルドし、すこしいじる
はじめに
GHCのソースコードを読んでみたい。あるいは、GHCに自分の考えた機能を追加してみたい。誰もが一度は思うことだ。そのとき、意外とハードルが高いのが「ダウンロードとビルドの手間」だ。難しいことはほとんどないけれど、道標がないと腰が重くなってしまう。この作業を記事にしよう。
難しいことはほとんどない。ビルドしたあと、すこしだけいじってみて、自分オリジナルのGHCをビルドして悦に入ってみようかと思う。
以下の記事を参考にした。@takenobu-hsさん、良記事をありがとうございます。
Haskell GHCをソースからビルドする手順 @takenobu-hs
上記の記事のGentoo版のような位置づけになるかと。ただし、できるだけGentooの機能を使わずにstackを使うようにしたので、ほかの環境でも応用がききやすいかとは思う。
前提とする環境
いろいろとインストールずみのシステム上での作業だと、「何が必要なのか」が不明確になるため、最低限のところから作業をすすめていくことにした。この記事をテストする環境は以下の記事で作成される最低限のGentooシステムとする。
ほかの環境でも、Haskellのパッケージ以外について、それぞれの環境でのやりかたに変えれば応用できるはずだ。
この記事の書きかた
GHCのソースコードを取得して、それをコンパイルするなかで、エラーメッセージが出る。そのエラーメッセージをひとつひとつ解決していくというやりかたとする。
コードの取得
まずはコードを取得する。つぎのようなコマンドとなるだろう。
% git clone --recursive https://gitlab.haskell.org/ghc/ghc.git
しばらく待つ。するとカレントディレクトリ下にディレクトリghcができているので、そこに移動する。
% cd ghc
./boot
mk/build.mkを作成する。mk/build.mk.sampleをコピーして、編集する。
% cp mk/build.mk.sample mk/build.mk
% vi mk/build.mk
BuildFlavour = quick
BuildFlavourはquickがRECOMMENDEDになっていたので、そのようにした。no profilingと書かれているので、profileを無効にすることでビルドがはやくなるのだろう(多分)。
./bootを実行する。
% ./boot
./configure
./configureをする。ここでは、いろいろなエラーが出る。それを解決していく。
% ./configure
...
checking for ghc... no
configure: error: GHC is required.
ここでは「GHCがない」と言われている。場合によっては、「GHCのバージョンが古い」と言われるかもしれない。どちらにしても、対処はおなじようにする。
stackとGHCの導入
stackを導入する。つぎのどちらかを実行する。curlとwgetのうち、システムにあるほうを使う。どちらもない場合は、どちらかをインストールしよう。
curlでは
% curl -sSL https://get.haskellstack.org/ | sh
wgetでは
% wget -qO- https://get.haskellstack.org/ | sh
パスワード入力が要求されたら、使っているアカウントのパスワードを入力する。また、今後stackを使っていくうえで、PATHに/home/[アカウント名]/.local/binを追加しておくと良い。
zshでは
% vi .zshrc
path=($HOME/.local/bin(N-/) $path)
bashでは
% vi .bashrc
PATH=$HOME/.local/bin:$PATH
GHCを導入する。つぎのようにコマンド入力する。
% stack setup
するとGHCがホームディレクトリ下の以下のようなパスにインストールされる。
.stack/programs/x86_64-linux/ghc-ncurses6-nopie-8.2.2/bin/ghc
システムによって、多少パスは変わるが、自分のシステムに置き換えて読み進めてほしい。この新しくインストールされたghcをコンパイルに使うことを、configureで設定する。
% ./configure GHC=/home/[使用しているアカウント名]/.stack/programs/x86-64-linux/ghc-ncurses6-nopie-8.2.2/bin/ghc
...
configure: error: Happy version 1.19.4 or later is required to compile GHC.
Happyの導入
こんどはHappyがないと言っている。つぎのようなコマンドでHappyをインストールする。HappyはHaskellのパーサジェネレータだ。
% stack install happy
% echo $PATH
/usr/local/bin:...
コマンドhappyはホームディレクトリ下の、つぎのパスにインストールされる。
.local/bin/happy
なので~/.local/bin/にパスが通っている必要がある。ZshまたはBashへの設定はしてあるので、一度exitして再度シェルに入り直す。
% echo $PATH
/home/tatsuya/.local/bin:/usr/local/bin:...
./configureをもう一度やってみる。
% cd ghc
% ./configure --with-ghc=/home/[アカウント名]/.stack/programs/x86_64-linux/ghc-ncurses6-nopie-8.2.2/bin/ghc
...
configure: error: Alex version 3.1.0 or later is required to compile GHC.
Alexの導入
今度はAlexが必要とのこと。AlexはHaskellの字句解析器ジェネレータ。おなじようにインストールする。
% stack install alex
再度./configureをする。
% ./configure --with-ghc=...
...
Confiture completed successfully.
...
...
...
うまく./configureできたようだ。
make
これでmakeできる。
% make
初回のmakeは当然、時間がかかる。僕の環境だと1時間とか2時間とか、そのくらいだ。Core-i3のうえの仮想環境なので、まあ「極非力なマシンで」ということだ。
まずはstage1コンパイラをビルドして、そのstage1コンパイラを使ってstage2コンパイラをビルドするというかたちだ。理由はよく知らない。新しいコンパイラで採用されたコードの最適化の恩恵によって、コンパイラ自体を速くするため、とかか?
makeが成功すると、つぎのパスにコンパイラが作成される。
ghc/inplace/bin/ghc-stage2
build.mkの編集
ここまでできたら、何はなくとも、まずやっておくことがある。mk/build.mkにstage=2を設定する。
% vi mk/build.mk
stage=2
これをしておかないと、ソースコードの些細な変更ごとにstage1コンパイラが更新され、そのためにstage2コンパイラを1から作り直すはめになる。
バージョン表示の変更
いろいろといじりたいが、まずは簡単なところから。バージョン表示をすこし編集してみよう。
% ./inplace/bin/ghc-stage2 --version
The Glorious Glasgow Haskell Compilation System, version 8.5.20180403
このようにバージョンが表示されるが、これにYoshikuni Editionを追加してみよう。
% vi ghc/Main.hs
showVersion :: IO ()
showVersion = putStrLn (cProjectName ++ ", version " ++ cProjectVersion ++ " Yoshikuni Edition")
これでmakeしてみよう。
% make
試してみる。
% ./inplace/bin/ghc-stage2 --version
The Glorious Glasgow Haskell Compilation System, version 8.5.20180403 Yoshikuni Edition
言語拡張LambdaCaseに別名をつける
バージョン表示をいじっただけでは、あまりにも簡単すぎるので、言語拡張に別名をつけてみよう。まずは、LambdaCase拡張を使うコードを書く。
% mkdir tmp
% vi tmp/lc.hs
{-# LANGUAGE LambdaCase #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}
main :: IO ()
main = putStrLn $ hello "Yoshio"
hello :: String -> String
hello = \case
"Yoshio" -> "Hello, darling!"
n -> "Hi, " ++ n ++ "."
これをコンパイル、実行する。
% ./inplace/bin/ghc-stage2 tmp/lc.hs
% ./tmp/lc
Hello, darling!
さて、このLambdaCase拡張にLCという別名をつけてみよう。まずはソースコードの海をgrepする。
% grep '"LambdaCase"' */*/* 2>/dev/null
compiler/main/DynFlags.hs: flagSpec "LambdaCase" LangExt.LambdaCase,
compiler/main/DynFlags.hsを編集すれば良さそうだ。
% vi compiler/main/DynFlags.hs
flagSpec "LambdaCase" LangExt.LambdaCase,
flagSpec "LC" LangExt.LambdaCase,
"LC"の行を追加した。makeする。
% make
これでLambdaCase拡張の別名としてLC拡張が使えるはずだ。テスト用のコードを編集する。
% vi tmp/lc.hs
{-# LANGUAGE LC #-}
LambdaCaseのところをLCに修正した。
% ./inplace/bin/ghc-stage2 tmp/lc.hs
% ./tmp/lc
Hello, darling!
言語拡張の一覧も表示してみよう。
% ./inplace/bin/ghc-stage2 --supported-extensions
...
LambdaCase
NoLambdaCase
LC
NoLC
...
ちゃんと言語拡張LCが表示されている。
まとめ
GHCの最新ソースコードの取得とコンパイルのしかたを、GHCさえインストールされてない状態から順に説明した。いろいろな環境で応用がきくように、できるだけ、ディストリビューション独自の機能は使わないようにして、stackを使うようにした。これで、オレオレGHCを作って遊ぶことができるようになった。
この先はコードリーディングするなり、新機能を実装するなり、各自楽しんでいただけたら幸いだ。