このエントリはGoのカレンダー | Advent Calendar 2022 - Qiitaの13日目です。多種多様なGoに関する記事を見ることができて、とても刺激になってます。ありがとう、クリスマス。ありがとう、アドベントカレンダー。当方はというと、最近ふつうにハマった問題の共有をしたいなと思います。
問題
cgoを使ったパッケージを開発中に、プラットフォームに依存したCフラグなどを渡す必要があり、cgoの中で #ifdef __APPLE__
や #ifdef __FreeBSD__
を書くものの、正しく分岐されず間違ったプラットフォームに間違ったビルドフラグが渡されるような挙動を観測した。
先に結論
#cgo
ディレクティブはgoがCをコンパイルするよりも前に評価される。- したがって、Cのプリプロセッサ(
#if
や#ifdef
など)により#cgo
を分岐しようとしても、前もって評価されてしまうため、#cgoディレクティブに書かれたものが当該プラットフォームにとって正しくなければ掲題のようなエラーを吐く。 - これを避けるために、Goのファイル構成の段階で
//go:build
ないしxxxx_unix.go
などのファイル名suffixを使ってcgoへ渡す前に分岐しなければならない。 - 雑にChatGPTにエラーメッセージを投げるの、勢いづけにめちゃくちゃ良かった!
追記
`#cgo darwin LDFAGS:` とかで OS 指定した cgo ディレクティブ書けますね
— Hajime Hoshi (@hajimehoshi) December 14, 2022
以下、ログなので読まなくていいです。メリークリスマス!★
ログ
- FreeBSDで起きている問題の認識と解決の方向性
- 長いことメンテしている自作のGoライブラリがあるんですが、こちらで、1年以上前からFreeBSDのCIがコケていることが観測されていた。
- CIの失敗ログを見ると、
"ld-elf.so.1: /usr/local/lib/libtesseract.so.5: Undefined symbol "__kmpc_global_thread_num"
というエラーメッセージとともにコケていることが分かった。 __kmpc_global_thread_num
とは、どうやら、OpenMPという並列計算を可能にするAPIを提供するライブラリに定義される定数のもよう。libtesseract.so
がOpenMPによって定義される定数を見に行こうとして、リンクされておらず困っているのだと推察。- どうやら、
LDFLAGS
などに-fopenmp
を与えれば、解決しそうなので、Goのimport "C"
をしているファイルの冒頭にフラグを追加すればよさそう。
- openmp問題の解決により新たに生じた問題
- 前述の通り、Goの
import "C"
をしているファイルの冒頭に-fopenmp
のフラグを追加した。⇒Fix test for vagrant · otiai10/gosseract@9be0318 · GitHub - GitHub-hosted runners にpreinstallされているVirtualBox+Vagrantを利用したFreeBSDのテストは、通るようになった。
- 一方で、macOS上のテストは掲題のエラーとともにコケるようになった。
- Goの
import "C"
をしているclient.goでは、#if __FreeBSD__ >= 10
を用い#cgo
に与えるフラグを出し分けしているはずなのに、ナゼ...?
- 前述の通り、Goの
- Goファイルにおける#cgoディレクティブの振る舞いに関する気付き
#if
ディレクティブの書き方が良くない可能性に仮説を持ち、#ifdef __APPLE__
や#ifdef __FreeBSD__
に書き直してテストするが、同じ結果を得る。__APPLE__
や__FreeBSD__
などのマクロ定数が定義されていない可能性に仮説を持ち、gcc -E -dM - </dev/null
やprintf
デバグで確認するが、残念ながら__APPLE__
は存在し__FreeBSD__
は存在しない、正しい結果を得るものの、テストの結果は同じである。#define __APPLE__ 1
や#undef __FreeBSD__
を冒頭に挿入しても、#ifdef __FreeBSD__
のブロックへ突入するような挙動で、同じ結果を得る。- この時点で、C/C++のプリプロセッサが評価されるタイミングと、
#cgo
が評価されるタイミングが異なる可能性に気づく。Special Thanks id:moriyoshi
- Goファイルのレイヤでプラットフォーム依存のコードを分割し、解決を得る
- FreeBSDに与えたいLDFLAGSの定義は、
preprocess_freebsd.go
というファイルに定義し、それ以外に与えたいLDFLAGSはpreprocess_x.go
というファイルに定義することで、解決した。 - 本件における最終成果物 ⇒ Separate Go files to control platform-specific Cgo flags · otiai10/gosseract@060b832 · GitHub
- FreeBSDに与えたいLDFLAGSの定義は、
参考資料
リファレンス
- C? Go? Cgo! - The Go Programming Language
- runtime/cgocall.go
- cgo command - cmd/cgo - Go Packages
- build package - go/build - Go Packages
- go command - cmd/go - Go Packages
参考
- cgoについて
- proposal: cmd/go: build tag in filename suffix for matching of syso files · Issue #42477 · golang/go · GitHub
- go - How to properly use build tags? - Stack Overflow
- What's the difference between `//go:build` and `// +build` directives? - Stack Overflow
- What are conventions for filenames in Go? - Stack Overflow
- Building Go Applications for Different Operating Systems and Architectures | DigitalOcean
- go - All possible GOOS value? - Stack Overflow
- Go (Golang) GOOS and GOARCH · GitHub
- cmd/go: goinstall -installsuffix={..} {package} attempts to install to $GOROOT/pkg/goos_goarch_{..} · Issue #10998 · golang/go · GitHub
- openmpについて
- undefined symbol: __kmpc_global_thread_num - Intel Communities
- https://github.com/kaldi-asr/kaldi/issues/4347:titlte
- C++ (Cpp) __kmpc_global_thread_num Example - itcodet
- [NVPTX] Early call to __kmpc_global_thread_num · Issue #38519 · llvm/llvm-project · GitHub
- ⚙ D27975 fix for the __kmpc_global_num_threads function to return the value of the __kmp_all_nth global var
- undefined reference to `__kmpc_.... and such - Intel Communities
- https://www.rccm.co.jp/development/parallel/openmp.html
- OpenMP - Wikipedia
- c - What preprocessor define does -fopenmp provide? - Stack Overflow
- c++ - Difference between linking OpenMP with -fopenmp and -lgomp - Stack Overflow
- fopenmp-targets, Qopenmp-targets
雑感
当ブログとは関係無いんですが、GoからOpenAI ChatGPTのAPIを利用できるためのAPIクライアントを作ったので、スターやissueくれるとうれしいです!
DRYな備忘録として