DRYな備忘録

Don't Repeat Yourself.

cgoの中で#ifdefのようなプラットフォーム分岐をしてはいけない:clang: error: unsupported option '-fopenmp'

このエントリはGoのカレンダー | Advent Calendar 2022 - Qiitaの13日目です。多種多様なGoに関する記事を見ることができて、とても刺激になってます。ありがとう、クリスマス。ありがとう、アドベントカレンダー。当方はというと、最近ふつうにハマった問題の共有をしたいなと思います。

問題

cgoを使ったパッケージを開発中に、プラットフォームに依存したCフラグなどを渡す必要があり、cgoの中で #ifdef __APPLE__#ifdef __FreeBSD__ を書くものの、正しく分岐されず間違ったプラットフォームに間違ったビルドフラグが渡されるような挙動を観測した。

先に結論

  1. #cgoディレクティブはgoがCをコンパイルするよりも前に評価される。
  2. したがって、Cのプリプロセッサ#if#ifdefなど)により#cgoを分岐しようとしても、前もって評価されてしまうため、#cgoディレクティブに書かれたものが当該プラットフォームにとって正しくなければ掲題のようなエラーを吐く。
  3. これを避けるために、Goのファイル構成の段階で//go:buildないしxxxx_unix.goなどのファイル名suffixを使ってcgoへ渡す前に分岐しなければならない
  4. 雑にChatGPTにエラーメッセージを投げるの、勢いづけにめちゃくちゃ良かった!

追記





以下、ログなので読まなくていいです。メリークリスマス!★



ログ

  1. FreeBSDで起きている問題の認識と解決の方向性
    1. 長いことメンテしている自作のGoライブラリがあるんですが、こちらで、1年以上前からFreeBSDのCIがコケていることが観測されていた。
    2. CIの失敗ログを見ると、"ld-elf.so.1: /usr/local/lib/libtesseract.so.5: Undefined symbol "__kmpc_global_thread_num" というエラーメッセージとともにコケていることが分かった。
    3. __kmpc_global_thread_numとは、どうやら、OpenMPという並列計算を可能にするAPIを提供するライブラリに定義される定数のもよう。
    4. libtesseract.soOpenMPによって定義される定数を見に行こうとして、リンクされておらず困っているのだと推察。
    5. どうやら、LDFLAGSなどに-fopenmpを与えれば、解決しそうなので、Goのimport "C"をしているファイルの冒頭にフラグを追加すればよさそう。
  2. openmp問題の解決により新たに生じた問題
    1. 前述の通り、Goのimport "C"をしているファイルの冒頭に-fopenmpのフラグを追加した。⇒Fix test for vagrant · otiai10/gosseract@9be0318 · GitHub
    2. GitHub-hosted runners にpreinstallされているVirtualBox+Vagrantを利用したFreeBSDのテストは、通るようになった。
    3. 一方で、macOS上のテストは掲題のエラーとともにコケるようになった。
    4. Goのimport "C"をしているclient.goでは、#if __FreeBSD__ >= 10 を用い#cgoに与えるフラグを出し分けしているはずなのに、ナゼ...?
  3. Goファイルにおける#cgoディレクティブの振る舞いに関する気付き
    1. #ifディレクティブの書き方が良くない可能性に仮説を持ち、#ifdef __APPLE__#ifdef __FreeBSD__ に書き直してテストするが、同じ結果を得る。
    2. __APPLE____FreeBSD__などのマクロ定数が定義されていない可能性に仮説を持ち、gcc -E -dM - </dev/nullprintfデバグで確認するが、残念ながら__APPLE__は存在し__FreeBSD__は存在しない、正しい結果を得るものの、テストの結果は同じである。
    3. #define __APPLE__ 1#undef __FreeBSD__を冒頭に挿入しても、#ifdef __FreeBSD__のブロックへ突入するような挙動で、同じ結果を得る。
    4. この時点で、C/C++プリプロセッサが評価されるタイミングと、#cgoが評価されるタイミングが異なる可能性に気づく。Special Thanks id:moriyoshi
  4. Goファイルのレイヤでプラットフォーム依存のコードを分割し、解決を得る
    1. FreeBSDに与えたいLDFLAGSの定義は、preprocess_freebsd.goというファイルに定義し、それ以外に与えたいLDFLAGSはpreprocess_x.goというファイルに定義することで、解決した。
    2. 本件における最終成果物 ⇒ Separate Go files to control platform-specific Cgo flags · otiai10/gosseract@060b832 · GitHub

実際に上記のプロジェクトでコケていたCIのログ

上部は実際のコードだが、下部はデバグのために仕込んだコード。
①のセクションでは#ifdefで分岐がされていない?ように見える

参考資料

リファレンス

参考

雑感

自作Slack-botにChatGPTを組み込んだので話が早い...!

当ブログとは関係無いんですが、GoからOpenAI ChatGPTのAPIを利用できるためのAPIクライアントを作ったので、スターやissueくれるとうれしいです!

github.com

DRYな備忘録として