• 作成:

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.1ghci: The library supporting GHC's interactive interpreterを依存関係に持っています。しかしHackageに登録されているghci8.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-parserLanguage.Haskell.THの曖昧性

ghc-lib-parserLanguage.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-parserghc-libを使うようにしましょう。