2013/10/21

Mac OS X環境でclangを使ってC++11をboostとともに使う環境を構築するの巻

前の会社にいたころはバリバリと音を立ててWindowsでC++を書いていたのでBoostも当然自前でビルドしてたりしていました。ただ年に1回ビルドするかどうかとかなので、あのビルドの長いオプション全然覚えられないんですよねー。ぼくはツイートしてそれをfavしてとりあえずメモっておくみたいなことをやっています。

これはmsvc-9.0だからたぶんVisual Studio 2008(たぶんSP1)でコンパイルしたときのメモだな…。まぁこんなかんじで当時はbjamでコンパイルしていました。今でもbjamは健在ですけど、Boost.Build v2っていうのが出てて公式サイトのビルド方法もb2コマンドを使ってビルドする方法に書き換わってるのでそっちでやってみることに(ちなみになんかbjamも一緒にビルドされるけど同じバイナリなのでもう完全にb2が置き換える存在になったのかな)。まぁ大した違いはないだろうということで公式サイトの記述に従いつつもclangでC++11を有効にビルドしてみると出るわ出るわエラーの山ー。

ということでこの記事はその戦いの記録(?)をメモって将来の自分がまたビルドしたくなったときに困らないようにするためのメモです。いやはや、Windowsでやってたころはほぼハマりどころなくサクッと(時間だけスゴイかかったけど)ビルドできた気がするのですが、Macでやったらすごいハマりました。まぁMacが悪いというよりXcodeとかclangでいろいろ地雷が埋まっているところが問題なのですけどね…。とりあえずC++やるの久々なのでだいぶ勘が鈍ってる感じですがゆっくりやっていきましょう。

Xcodeに最初から入ってるlibstdc++が古い(4.2.1)ため、C++11に対応してない

さて、C++11を有効にするとどんな問題が起こるかをみていきましょう…。clangでコンパイルするときのオプションで -std=c++11 を指定してC++11を有効にします。で、コンパイルしてみるとたくさんコンパイルエラーがでて、標準ライブラリがダメだということを思い知らされます。

はぁつらい。たぶんlibstdc++のバージョンはGCCのバージョンとリンクしてるだろうからGCC 4.2.1のリリース日を調べればこのStandard C++ Libraryの出た時期が分かるということで調べてみると、2007年7月ぐらいのようです。そりゃC++11に対応してるわけないわ。

具体的に何がダメかというとC++11から標準になったstd::moveが無いわけです。Boost.MoveがあるのでC++03だったらこれが使われるようにBoostは(たぶん)作られているはずですが、今回はclangをC++11で動かしてるので標準で存在することを期待してダメになっているんでしょうねぇ。Boost本家のTicketにstdlibc++ shipped by Apple does not provide std::moveっていうのがありますが、won't fixでCloseされてますので、clang + std=c++11 + Xcodeについてるlibstdc++ 4.2.1の組み合わせで動かすのはあきらめた方がよいです。

ちなみになぜAppleさんはlibstdc++ 4.2.1とかいうやたら古い標準Cライブラリをバンドルしてるの?っていうと、理由は簡単、libstdc++はこれの次のバージョンからGPL3になったからなんですね。えっ、そんなこといったってlibstdc++はGPL+例外条項があってプロプライエタリなプログラムにリンクしてもいいことになってるよね? と思った方、鋭いです。

ぼくもそう思ったんですが、その条項をちゃん読んでみるとめんどくさい話になっていてGCC RUNTIME LIBRARY EXCEPTIONには「GCCでコンパイルするときにはGCC RUNTIME LIBRARYを非GPLなプログラムと併せて使っていいよ!」って書いてあるんです。つまりclangはgccじゃないから非GPLなプログラムをclangを使ってコンパイルしてlibstdc++ 4.3以降と併せて使ったときには非GPL部分もGPLの適用対象とする必要があるんですね。まぁgccの標準C++ライブラリなんだしgccにロックインされてるのはしかたないか。GPLやGCCに対する恨み辛みを書くと「おっと誰か来たようだ…」となりそうなのでやめておきましょう(ぼくはあんまりそういうの無いですけど)。

あ、ちなみに「じゃあGPL2時代はGCC縛りはなかったの?」と気になる人もいるかと思います。結論から言うとありませんでした。そもそもGPL2時代はヘッダーファイルにちょろ〜んとruntime exception条項が書いてあっただけで、そこには「GCCで」っていうのは書かれていなかったようですね。web.archive.orgに2008年当時のGPL2時代のRuntime GPLに関する資料があったのでそちらも併せてご覧ください。

というわけで、GPL3になってからはclang勢はlibstdc++を流用できなくなってしまったのですが、まぁそこはそれ。llvm.orgがBSDライセンスなlibc++というのを作っており、clang勢が非GPLなC++プログラムを書きたいときはこちらを使えばOKということになっております。。実はXcodeにはちゃんとlibc++も入ってるので、-stdlib=libc++ を指定すればいいんですねー。

libc++のメソッドがx86_64で色々足りないっぽい

とおもってリンクする標準ライブラリを変えてコンパイルしてしばらくゴロゴロして終わるのを待つと大量のリンクエラーで半分くらい.dylibの生成に失敗しています。ちなみに.aはちゃんと作れているので、C++11としてのライブラリのコンパイル自体は全部うまくいっているようですね。

という感じなのでとりあえず静的ファイルでリンクするならいいんじゃね? って感じですが、一応気になるのでなんとかしたい…。さてどうするか? わりとどうしようもありません。

しかたないので上記のlibc++のサイトから最新のlibc++のソースを取ってきてコンパイルします。で、clangへ渡すオプションで -nostdlib して標準ライブラリを読み込まないようにし、手動でincludeパスとlibraryパスを指定して-lc++ などとやってリンクすればすべてがうまくいきます。めでたしめでたし。

そして私は気付くのでした…。「あ、これx86_64しかビルドしてない。fat binaryになってないぞ…」と。どうやらuser-config.jamでclangのオプションを指定して-archでi386とx86_64を指定する必要がありそうです。

最終的にこうなった

こうして編み出したビルド手順がコレだッ!

  1. とりあえずlibc++の最新版を持って来てビルドする
  2. boostのソースを展開してtools/build/v2/user-config.jamの一番最後に以下を追記
    using clang : osx : clang -arch i386 -arch x86_64 ;
    
  3. tools/build/v2 ディレクトリに移動してb2コマンドをビルド
    $ ./bootstrap.sh --with-toolset=clang-osx
  4. そのディレクトリでb2コマンドを直接実行してパスの通ったところへb2コマンドをインストール
    $ ./b2 install toolset=clang-osx 
    (別のディレクトリがいいときは--prefixでディレクトリを指定)
  5. boostのルートディレクトリに戻って以下のコマンドを実行
    $ b2 -j{concurrent} toolset=clang-osx \
      cxxflags="-std=c++11 -I {dir_to_libcxx}/include -nostdinc++ -ftemplate-depth=256" \
      linkflags="-L {dir_to_libcxx}/lib -lc++"  threading=multi --build-dir=/tmp stage
    

これでi386/x86_64が両方入ったfat binaryのできあがり。しかし、fat binaryを作ろうとするとBoost.Contextのビルドでunsupportedなプラットフォームとして判断されてこれだけうまくいきません。本家のTicketにcontext library don't compile for Mac OSX universal binaryっていうのが8ヶ月前にオープンされて「これはBoostというよりJamfileの問題だねー。現在のリリースだとUniversal Binaryを作るための指定をうまく捌けないわ」という話になっているので、直るのを待つしかないようです。ただ、Boost.Contextなんて自分でライブラリを書かない限り使わないと思うのでただのユーザにはあんまり影響ありません。

あとその他にもぼくの手元だとBoost.Pythonでpythonのi386がないということでエラーになりましたが、確認してみたところむかーしpythonbrewでビルドしたlibpythonのdylibがfat binaryになってなくてi386のリンクが出来なかったみたいなエラーでした。これはシステム側のdylibにリンクすれば大丈夫なのでそこら辺を正せば多分大丈夫です。もし同じように引っかかった方は自前のPythonを使ってないかご確認ください。

というわけで、今回はだいぶ誰得記事な感じがしましたが、Macでclangを使ってC++11が有効になったBoostをビルドする方法をメモりました。

うされもん @acidlemonについて

|'-')/ acidlemonです。鎌倉市在住で、鎌倉で働く普通のITエンジニアです。

30年弱住んだ北海道を離れ、鎌倉でまったりぽわぽわしています。

外部サイト情報

  • twitter
  • github
  • facebook
  • instagram
  • work on kayac