GHCのAPIを使う時はなるべくghcではなくghc-lib-parserに依存した方が良い
前提
Haskellでちょっとしたハックを行う時、 GHCのAPIを使いたいことがあります。
その時にghc: The GHC APIを使ってしまうことがあるようです。
しかしなるべくghc-lib-parser: The GHC API, decoupled from GHC versionsを使った方が良いでしょう。それだけでは足りない場合もghc-lib: The GHC API, decoupled from GHC versionsを追加で使った方が良いです。
その理由を説明します。
問題
ghc
パッケージを利用するとシステムのghcとコンフリクトすることがあります。
具体的な事例
最近awakesecurity/proto3-suite: Haskell Protobuf Implementationでこの問題に直面しました。
私の環境でこのライブラリに依存すると、以下のエラーメッセージを得てしまいました。
[__1] next goal: ghc (dependency of proto3-suite)
[__1] rejecting: ghc-9.12.1 (conflict: proto3-suite => ghc>=9.0 && <9.11)
[__1] trying: ghc-9.10.1
[__2] next goal: ghci (dependency of ghc +/-static-libzstd +/-with-libzstd)
[__2] rejecting: ghci; 8.10.2, 8.10.1, ... (conflict: ghc +/-static-libzstd +/-with-libzstd => ghci==9.10.1)
[__2] fail (backjumping, conflict set: ghc, ghci)
このエラーメッセージの意味を私も完全に把握しているとは言い難いのですが、以下のような問題を報告していると理解しています。
まずproto3-suiteはパッケージghc
に依存しています。
proto3-suiteはghc-9.12.1
に対応していないのでまずこれは却下されます。これ自体は別に問題ありません。
そしてghc-9.10.1
を依存関係に使おうとします。システムのghcとフラグが合わないからなのか、私がhaskell.nixを使っていたからなのか、この理由は正直よく分かりませんが、
Hackageのghc
をビルドしようとします。
ghc-9.10.1
はghci: The library supporting GHC's interactive interpreterを依存関係に持っています。しかしHackageに登録されているghci
は8.0.1
から8.10.2
までしか存在しません。よって同じバージョンのghci-9.10.1
の依存関係は解決できません。
Hackageのghciが昔のバージョンしか登録されていないのはおそらく意図的なもので、システムのghcがシステムのghciを使うだけなので、アップロードする必要がないのでしょうね。
パッケージghc
はインターフェイス的な宣言をするだけで、実際にビルドされることは想定していないのでしょう。
解決
ghc-lib-parser
を使うことでおおよそ解決します。
これはGHCのパーサー部分だけを切り出したライブラリです。
これを使うとパッケージのghc
よりはシステムのghcに強く依存せずに済みます。
少なくともproto3-suite
の例では、
ghc
からghc-lib-parser
への切り替えにより、我々の環境でのビルドは通るようになりました。
実装方法
既存のghc
依存をどのようにghc-lib-parser
に切り替えるか、基本的な手順を紹介します。
依存関係
まずpackage.yaml
または.cabal
ファイルの依存関係を更新します。
- ghc
+ ghc-lib-parser
マクロでのパッケージ指定の名称
マクロでのバージョンチェックでのコード分岐を行っている場合は、これも名前変更をする必要があります。
-#if !MIN_VERSION_ghc(9,6,0)
+#if !MIN_VERSION_ghc_lib_parser(9,6,0)
パッケージghc
とシステムGHCの混同
ghc
とシステムのGHCのバージョンが同期していることを期待している場合は単純な名前置き換えが不適切な場合があります。
ghc
パッケージの内容ではなくGHCのコアシステムによって動作を変更する必要がある場合は、
__GLASGOW_HASKELL__
マクロを使いましょう。
-#if MIN_VERSION_ghc(9,4,0)
+#if defined(__GLASGOW_HASKELL__) && 904 <= __GLASGOW_HASKELL__
ghc-lib-parser
のLanguage.Haskell.TH
の曖昧性
ghc-lib-parser
はLanguage.Haskell.TH
をexportしているので既存のimport
との衝突が起きることがあります。その場合はPackageImports
を使って曖昧性を解消します。
-import Language.Haskell.TH qualified as TH
+import "template-haskell" Language.Haskell.TH qualified as TH
それでも切り捨てが今回必要でした
それでも動かなかったものがありました。 doctestで動くテストで古いGHCだけモジュール解決が出来ませんでした。
でも動かないGHCのバージョンは9.0.X系だけでした。それもMacではすでにこのプロジェクトでサポートを終了しているので、サポートを切る方向性で、ついでにサポートを切った場合CPPの条件分岐を簡素化出来ることを示して、了承をもらいました。
どうしてもやらないといけないならやりますが、 GHC 9.0.X系をサポートするというあまり誰も求めてなさそうなことをやる気力はありませんでした。
生成されたPR
こうしてこのPRは生成されました。
build: depend from ghc
to ghc-lib-parser
by ncaq · Pull Request #276 · awakesecurity/proto3-suite
皆さんはGHCのAPIを使う時はghc
パッケージではなく、なるべく最初からghc-lib-parser
とghc-lib
を使うようにしましょう。