bjam戦記 Boost.Log

Boost.Logのサンプルコードを打ち込んでいたら、一部のコードが動作しませんでした。
ログファイルのローテーションを行うサンプルですが、ビルドすると、以下のようなリンクエラーが出ます。

Undefined symbols for architecture x86_64: "basic_formatter<char> parse_formatter<char>(char const*, char const*)", referenced from:

今回は、このリンクエラーを解消するための奮闘記です。


環境
Mac OS XGCC 4.7。


結論から
まず結論から書きます。
原因は、bjamの設定が間違っていて、古いGCC(Boost.Logのサポート外環境)でコンパイルされていたからでした。以下のように設定することで、解決しました。

  • bjamの設定ファイルsite-config.jam

このファイルは、HOME下、もしくはBOOST_ROOT下に作成します。

using gcc : 4.7 : g++-mp-4.7 ;

MacPortsで入れたGCC 4.7を、gcc-4.7のツールセットとして認識させます。

  • bjamコマンドでのBoostライブラリのビルド
./bjam toolset=gcc-4.7 --with-log link=static runtime-link=static

GCC 4.7でBoostをビルドします。
with-logで、Boost.Logに関連するライブラリだけがビルドされます。このオプションを付けなければ、Boostの全ライブラリがビルドされます。
デフォルトでなぜか動的ライブラリ(.dylib)を作られるようになっていたので、静的ライブラリ(.a)を作るようstaticを指定します。

  • ビルドされたライブラリファイル

「boost_1_54_0」がBoostのルートディレクトリだった場合は、boost_1_54_0/stage/libディレクトリ以下に、ビルドされたライブラリファイル(.a)が置かれます。

  • サンプルコードのビルド

Boost.Logのサンプルコードを動かす際のコマンド。

g++-mp-4.7 -o main main.cpp -L/Users/faith_and_brave/repository/Library/Boost/boost_1_54_0/stage/lib -lboost_thread -lboost_system -lboost_log -lboost_log_setup -lboost_filesystem -std=c++11 -Wall -Wextra -pedantic -pthread -I ~/repository/Library/Boost/boost_1_54_0

Boost.Logは、libboost_log.aとlibboost_log_setup.aの2つを少なくてもリンクする必要があり、そのほかに使用する関連ライブラリ(Boost.Thread、Boost.System、Boost.Filesystem等)をリンクします。

これでBoost.Logのファイルローテーションを行うサンプルが動きます。


奮闘記
では、ここからbjamの奮闘記です。

まず、サンプルコードがリンクエラーになる原因は、bjamでのビルド時に、libs/log/src/formatter_parser.cppがコンパイルエラーになり、formatter_parser.oが作成されていなかったためです。
コンパイルエラーの内容を見たら、どうやらresult_ofメタ関数の呼び出し部分でエラーになっているようです。調べたら、以下のチケットが見つかりました(このエントリを書いている時点では未解決)。


#8639 Boost.Log (trunk: r84587) fails to build with g++ 4.1


このチケットは、タイトルそのままですが、GCC 4.1でBoost.Logのビルドに失敗するというもので、Boost.Log作者のコメントで、

Sorry, this compiler is not supported. It fails to handle TR1 result_of protocol correctly, which is necessary for the part of Boost.Log that uses Boost.Phoenix.

If you have a patch and it's not overhauling the whole library, you can reopen this ticket. Closing it for now.

と書いてあり、つまりTR1のresult_ofを使ってる(Boost.Phoenixを使ってる)から、古いコンパイラでは動かないよ、ということです。

なるほど、bjamで使われているGCCのバージョンが古そうです。調べてみたら、g++コマンドでビルドされていたので、おそらく最初から入っていたGCCです。バージョンは4.2.1。result_ofとかないですね。
bjamで使用するGCCのバージョンを切り替えてみました。

// site-config.jam
using gcc : 4.7 : g++-mp-4.7 ;
./bjam toolset=gcc-4.7

すると、今度は全てのBoostライブラリでリンクエラーが出るようになりました。

ld: unknown option: -R

ldコマンドには、-Rオプションなんてない、というエラーです。


Boostのメーリングリストを調べてみると、「darwinGCCを使わないとだめだよ」とのこと。つまりbjamデフォルトのコンパイラ
そんなわけないだろ、と思ってkikairoyaさんに手伝ってもらいながら調べて、tools/build/v2/tools/gcc.jamをいじればいけるんじゃないか、ということに。


(中略します。 https://twitter.com/cpp_akira/status/362275774625099777 )


結果的に、ldコマンドが必要になるのは、なぜか意図せずlibboost_log.dylibとかの動的ライブラリが作られているからなので、静的ライブラリを作ればいい、ということになりました。
最終的に、bjamのコマンドは以下のようになりました。

./bjam toolset=gcc-4.7 --with-log link=static runtime-link=static

静的ライブラリも作られてこれで万事解決かな、と思ったら、まだ一番最初のリンクエラーがでます(parse_formatter)。
libboost_log.a内にparse_formatter関数が含まれているのか怪しくなってきたので、.aファイル内の関数一覧を出力してみました。
.aファイルの解析にはnmコマンド、出力された関数名をデマングルするにはc++filtコマンドを使いました。

nm libboost_log.a | c++filt

これで、以下のように、.oファイルごとの関数名一覧が出力されます。

...
libboost_log.a(unhandled_exception_count.o):
0000000000000018 short EH_frame1
0000000000000000 T boost::log::v2s_mt_posix::aux::unhandled_exception_count()
                 U ___cxa_get_globals
...

しかし、libboost_log.aファイルには、formatter_parser.oが含まれていませんでした。調べたら、libboost_log_setup.aの方にありました。これら2つのライブラリをリンクしたら、リンクエラーが解消しました!


このリンクエラーは、夜22:00くらいから調べ始めて、解決したのは朝5時前くらいでした。
解決まで付き合ってくれたkikairoyaさんに感謝します!ありがとうございます!