iPhone・iPad用に手書き文字認識ライブラリZinniaをビルドする
iPhone・iPad向けに手書き文字認識ライブラリ-Zinniaをビルドしてみて、結構苦労したので、そのときのメモです。Zinniaと書いてますが、Fat binary全般についての僕の理解も込みで説明してます。以下iPhoneと書いてますが、iPad用でも同様の内容があてはまります。
Fat binaryについて
まず基本的な話で、iPhone実機、Mac OSが稼働するPC、そしてiPhoneシミュレータの三者は、動作するプログラムのアーキテクチャが違う、ということを頭に置いておく必要があります。iPhoneのCPUはarm、Mac OSが稼働するCPUは(OS10.6では)x86_64、そしてiPhoneシミュレータはi386のCPUをシミュレートするようになっています。Mac OS上で普通にコンパイルするとx86_64向けのバイナリが出来てしまうので、これはiPhone実機やシミュレータでは動かないわけです。
作成されたバイナリがどのアーキテクチャ向けにビルドされているかは、otoolコマンドなどで参照することが出来て、たとえば普通にconfigureしてmakeしたZinniaのバイナリは以下のようになっています。
$ otool -vh .libs/libzinnia.a Archive : .libs/libzinnia.a .libs/libzinnia.a(param.o): Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC_64 X86_64 ALL 0x00 OBJECT 3 1136 SUBSECTIONS_VIA_SYMBOLS ...
こんな感じでこのライブラリにリンクされているバイナリがx86_64をターゲットにコンパイルされていることが分かります。
で、こいつをiPhoneで動かしたい、となるとシミュレータのことも考慮してarmとi386の両方で動くようにビルドしてあげる必要があるわけです。こんな風に複数CPUで動くように作られたバイナリをFat binaryとかUniversal binaryとか呼んだりします。
さて、Mac OS上でFat binaryを作るにはここに書いてあるようにいくつか方法がありますが、一番シンプルなのが、それぞれのアーキテクチャ向けに個々にコンパイルしたものを、lipoコマンドで結合してあげるやり方です。
基本的にはg++に-arch [アーキテクチャ名]のオプションが渡るようにしてあげれば、そのアーキテクチャ向けにコンパイルしてくれるはずなのですが、これがなかなか一筋縄ではいきません
armバイナリのビルド
armでビルドするときは、arm向けのライブラリやヘッダファイルなどが、Mac OSのデフォルトパス上のものとは異なるので、それらのライブラリパスやインクルードパスも全部指定し直す必要があります。arm用のファイルは、iOS SDKをインストールしたフォルダ /Developer/Platforms/iPhoneOS.platform/Developer に格納されているので、configure時にこれらを参照するように〜FLAGSなどの環境変数を設定してあげます。
export CPPFLAGS="-I$SDKROOT/usr/lib/gcc/arm-apple-darwin10/4.2.1/include/ -I$SDKROOT/usr/include/ -I$SDKROOT/usr/include/c++/4.2.1 -I$SDKROOT/usr/include/c++/4. 2.1/armv6-apple-darwin10/ -miphoneos-version-min=3.0" export CFLAGS="$CPPFLAGS -pipe -no-cpp-precomp -isysroot $SDKROOT" export CPP="$DEVROOT/usr/bin/cpp $CPPFLAGS" export CXXFLAGS="$CFLAGS" # Dynamic library location generated by the Unix package LIBPATH=$LIBFILE.dylib LIBNAME=`basename $LIBPATH` export LDFLAGS="-L$SDKROOT/usr/lib -L$SDKROOT/usr/lib/gcc/arm-apple-darwin10/4.2.1/ -Wl,-dylib_install_name,@executable_path/$LIBNAME" ./configure CXX=$DEVROOT/usr/bin/arm-apple-darwin10-g++-4.2.1 CC=$DEVROOT/usr/bin/arm-apple-darwin10-gcc-4.2.1 LD=$DEVROOT/usr/bin/ld --host=arm-apple-darwin make
これでarm用のバイナリが作られるはずです。
i386バイナリのビルド
Mac OSのライブラリ類はi386互換で作られているはずなので、armのようにインクルードパスやライブラリパスをいちいち指定し直さなくても、g++に-arch i386オプションを指定してあげれば、i386用のバイナリが作られます。
ただ一つ問題なのがZinniaのMakefileです。./configure時にCFLAGS="-arch i386"と指定してもMakefileが作られるときには、なぜかこの指定が消えてしまっているのです。恐らくはZinniaがビルドに使用しているlibtoolの問題ではないかと思うのですが(推測です)、仕方がないので無理矢理Makefileを書き換えてあげます。
./configure perl -pi -e 's@^CXXFLAGS = @CXXFLAGS = -arch i386 @g' Makefile perl -pi -e 's@^CFLAGS = @CFLAGS = -arch i386 @g' Makefile make
これでi386のバイナリもビルドできました。
まとめ
以上をまとめたZinniaのFat binaryビルドファイルです。
Mac OS 10.6.4、iOS SDK 4.0.2、Zinnia 0.0.6で動作確認しています。
build_fat.sh
#!/bin/sh LIBFILE=.libs/libzinnia export DEVROOT=/Developer/Platforms/iPhoneOS.platform/Developer export SDKROOT=$DEVROOT/SDKs/iPhoneOS4.0.sdk # build arm binary export CPPFLAGS="-I$SDKROOT/usr/lib/gcc/arm-apple-darwin10/4.2.1/include/ -I$SDKROOT/usr/include/ -I$SDKROOT/usr/include/c++/4.2.1 -I$SDKROOT/usr/include/c++/4.2.1/armv6-apple-darwin10/ -miphoneos-version-min=3.0" export CFLAGS="$CPPFLAGS -pipe -no-cpp-precomp -isysroot $SDKROOT" export CPP="$DEVROOT/usr/bin/cpp $CPPFLAGS" export CXXFLAGS="$CFLAGS" LIBPATH=$LIBFILE.dylib LIBNAME=`basename $LIBPATH` export LDFLAGS="-L$SDKROOT/usr/lib -L$SDKROOT/usr/lib/gcc/arm-apple-darwin10/4.2.1/ -Wl,-dylib_install_name,@executable_path/$LIBNAME" LIBPATH_static=$LIBFILE.a LIBNAME_static=`basename $LIBPATH_static` ./configure CXX=$DEVROOT/usr/bin/arm-apple-darwin10-g++-4.2.1 CC=$DEVROOT/usr/bin/arm-apple-darwin10-gcc-4.2.1 LD=$DEVROOT/usr/bin/ld --host=arm-apple-darwin make mkdir -p lnsout cp $LIBPATH_static lnsout/$LIBNAME_static.arm make distclean unset CPPFLAGS CFLAGS CPP LDFLAGS CXXFLAGS DEVROOT SDKROOT # build i386 binary ./configure perl -pi -e 's@^CXXFLAGS = @CXXFLAGS = -arch i386 @g' Makefile perl -pi -e 's@^CFLAGS = @CFLAGS = -arch i386 @g' Makefile make cp $LIBPATH_static lnsout/$LIBNAME_static.i386 /usr/bin/lipo -arch arm lnsout/$LIBNAME_static.arm -arch i386 lnsout/$LIBNAME_static.i386 -create -output lnsout/$LIBNAME_static unset CPPFLAGS CFLAGS CPP LDFLAGS CPP CXXFLAGS DEVROOT SDKROOT
結果
fileコマンドで本当にfat binaryができているか確認してみます。
$ file lnsout/libzinnia.a lnsout/libzinnia.a: Mach-O universal binary with 2 architectures lnsout/libzinnia.a (for architecture arm): current ar archive random library lnsout/libzinnia.a (for architecture i386): current ar archive random library