なにこれ
C++をついこの間使い始めました. 外部ライブラリを使おうと思って, Homebrewからぶち込んでGCCでコンパイルして使おうと思ったら使えなかった. そういうお話.
環境
こういう困ったことの解決策を共有するときは計算環境を提示しておいたほうがいいよね. 足りなかったら言ってくだいね.
OS : macOS Mojave ver10.14
c++ compiler : gcc version 8.2.0 (Homebrew GCC 8.2.0)
homebrew : Homebrew 1.8.6
Homebrewってなんぞや
そんなん知ってるわっていう人が大半かもしれないけど一応. 〇〇をインストールしますって言って,
brew install 〇〇
しか書いてないみたいなのがとても嫌なので.
Homebrewとは, macOS用のパッケージ管理システムの1つで, ソフトウェアを簡単に入れるためのものだそうです. ふむふむ. 手動で入れようと思うとmakeが通らねえーとかパスが違うーとかままありますよね. そういっためんどくさいもんを勝手に解決してコマンド1つでソフトウェアがぶっ込める. そんな素敵なものがHomebrewなんだそうだ.
Homebrew自体のインストールはこちらからどうぞー Homebrew
とりあえず最初にコケるまで
外部ライブラリとしてICU - International Components for Unicodeを使おうと思って, とりあえずHomebrewで検索.
brew search icu
そうすっとicu4cなるものがヒット. 今考えるとこれicu for Cの意味だったのねとわかるんだけど, 始めはなにこの4cってずっと思ってた. まあそんなことはいいとして, わーい見つかったということでインストール.
brew install icu4c
はい, 完全勝利. あとは使うだけ!そう思っていた頃が僕にもありました. てなわけでコンパイル
g++ icu_test.cpp
icu_test.cpp:1:10: fatal error: 'unicode/translit.h' file not found
#include <unicode/translit.h>
^~~~~~~~~~~~~~~~~~~~
1 error generated.
あっるえー??ヘッダファイルが見つかっていないらしい. 一生懸命調べるとCコンパイラでは-I
オプションでヘッダファイルのインクルードパス, -L
でその実装が書かれたライブラリパスを指定するとのこと.
いやそのパスってどこにあんねんということで, find
コマンドで探した.
opt
の方でいいんじゃねって思って, 中身を見る.
xxxxxx$ find /usr/local/ -name icu4c
/usr/local//opt/icu4c
/usr/local//Cellar/icu4c
xxxxxx$ ls /usr/local//opt/icu4c
INSTALL_RECEIPT.json include readme.html
LICENSE lib sbin
bin license.html share
なんかいかにもなものがある. include
とlib
ってのがある. これじゃね?って思ってもう一回コンパイル.
g++ icu_test.cpp -I/usr/local/opt/icu4c/include -L/usr/local/opt/icu4c/lib
Undefined symbols for architecture x86_64:
"icu_63::UnicodeString::UnicodeString(char const*)", referenced from:
katakana2hiragana(icu_63::UnicodeString) in ccz5k34Q.o
"icu_63::UnicodeString::~UnicodeString()", referenced from:
katakana2hiragana(icu_63::UnicodeString) in ccz5k34Q.o
"icu_63::Transliterator::createInstance(icu_63::UnicodeString const&, UTransDirection, UErrorCode&)", referenced from:
katakana2hiragana(icu_63::UnicodeString) in ccz5k34Q.o
"icu_63::UnicodeString::extract(int, int, char*, unsigned int, char const*) const", referenced from:
icu_63::UnicodeString::extract(int, int, char*, char const*) const in ccz5k34Q.o
"operator new[](unsigned long)", referenced from:
katakana2hiragana(icu_63::UnicodeString) in ccz5k34Q.o
"___gxx_personality_v0", referenced from:
Dwarf Exception Unwind Info (__eh_frame) in ccz5k34Q.o
ld: symbol(s) not found for architecture x86_64
collect2: error: ld returned 1 exit status
は???ってなった. いやいやインクルードパスとライブラリパスは書いてあるやんけ!!
さらに頑張って調べてみると, これはリンカってもののエラーらしいことがわかった. ライブラリパスを-Lオプションで記載するだけではなく, その中のどのライブラリをくっつけて実行可能バイナリを生成するかってことを-l
オプションで指定する必要があるらしい.
xxxxxx$ ls /usr/local/opt/icu4c/lib
icu libicuio.63.1.dylib libicutu.63.dylib
libicudata.63.1.dylib libicuio.63.dylib libicutu.a
libicudata.63.dylib libicuio.a libicutu.dylib
libicudata.a libicuio.dylib libicuuc.63.1.dylib
libicudata.dylib libicutest.63.1.dylib libicuuc.63.dylib
libicui18n.63.1.dylib libicutest.63.dylib libicuuc.a
libicui18n.63.dylib libicutest.a libicuuc.dylib
libicui18n.a libicutest.dylib pkgconfig
libicui18n.dylib libicutu.63.1.dylib
なんかたくさんある. lib〇〇
っていうのがそれぞれライブラリらしい. どれを使えばいいかわからないので, とりあえず全部のせしてみよう. コンパイルが通ったら不要な物を減らして再コンパイルすればいいしね.
g++ icu_test.cpp -I/usr/local/opt/icu4c/include -L/usr/local/opt/icu4c/lib -licudata -licui18n -licuio -licutest -licutu -licuuc
Undefined symbols for architecture x86_64:
"operator new[](unsigned long)", referenced from:
katakana2hiragana(icu_63::UnicodeString) in cc1qyWVv.o
"___gxx_personality_v0", referenced from:
Dwarf Exception Unwind Info (__eh_frame) in cc1qyWVv.o
ld: symbol(s) not found for architecture x86_64
collect2: error: ld returned 1 exit status
これもうわかんねえな. 一旦お手上げもうだめ.
ライブラリインストール後のコンパイルをGCCにやらせてみる
さらに頑張って調べてみると, どうやらHomebrewで入るライブラリは, デフォルトではmacに最初からインストールされているclangでコンパイルされるらしい. てことは, clangでコンパイルしたライブラリをgccでコンパイルしたコードとgccでリンクしようとして上手く行ってないのではないかと推測.
stackoverflow - Using Homebrew with alternate GCC
このあたりを参考にビルド時のコンパイラをgccにしてHomebrewから再インストール
brew uninstall icu4c
HOMEBREW_CXX=g++ brew install icu4c
これでもう1回コンパイル. だけどダメ. もはやエラーメッセージをとっておくことも忘れた(´•ω•`)
ソースコードのコンパイルをclangにやらせてみる
ライブラリをgccでコンパイルしてダメなら, 今度はその逆, 自分のコードをclangでコンパイルすればいいのではないか. やってみる.
brew uninstall icu4c
HOMEBREW_CXX=c++ brew install icu4c
これでもう一回コンパイル.
c++ icu_test.cpp -I/usr/local/opt/icu4c/include -L/usr/local/opt/icu4c/lib -licudata -licui18n -licuio -licutest -licutu -licuuc
ようやく通った!!やった!もう外部ライブラリなんて怖くない(大嘘)
Homebrewのデフォルトのコンパイラがclangだから, Homebrewからインストールできるソフトウェアはそれで動くようにできているんだろうなぁ.
まとめ
C++で外部ライブラリを使うときには
-
-I
オプションでヘッダファイルがあるディレクトリのパスを指定する必要がある -
-L
オプションでライブラリの実装があるファイルへのお明日を指定する必要がある -
-l
オプションで使用したいライブラリ名を指定する必要がある - Homebrewからインストールしたライブラリを使用する場合にはgccではなくclangでコンパイルする必要がある
何はともあれ, 解決してよかった.