たごもりすメモ

コードとかその他の話とか。

AppStore提出アプリでAppleが直接は許可しない外部ライブラリを使用する方法

要するに「自分でビルドしてスタティックリンクしろ」だけなんですけどね……。以下の件の続き(解決編)です。
ソースコードビューア GlassDolphin for iPad/iPhone 本当に完成!(しかしreject) - tagomorisのメモ置き場

iOS SDKに入ってるのに使うと意味不明なrejectメールで怒られる libarchive ですが、結局どうしたかというと、以下のようにしました。

  1. libarchive および(それが依存する) bzlib をダウンロードして展開
  2. libarchive は configure も実行
  3. Xcodeで新規プロジェクトを作成し、そこに libarchive/bzlib を取り込んでビルド
    • i386(Simulator)およびarmv7(Device)の両方でビルド
  4. コマンドで結合してユニバーサルバイナリ
    • これやんないと、アプリ側のシミュレータ/実機の両方で動くバイナリにならない
  5. 使用するプロジェクトの側で取り込む

こんな感じ。手順は後で詳述。上記エントリのコメント欄で「移植とかしなくてもそのままビルドすればいけるかも?」というコメントをいただきましたし、Twitterでも英語で「シンボル名だけ変えてコンパイルし直せばいけるんじゃない?」みたいなことが書いてあるリンク先を紹介していただいたりしました。
正直、真相がどうなのかはよくわかりませんが、今回は元のライブラリのコードには一切手を入れず、呼び出し側も変えることなく、ビルドし直してリンクの仕方を変えるだけでAppStoreの機械式チェックは通るようになりました。
可能性としては以下のどれかみたいな感じかなーと思います。

  • Appleは使われたときにチェックできるよう、SDKには特殊な目印のついたシンボルのダイナミックリンクライブラリを添付していた
  • ダイナミックリンクライブラリとしてビルドしてたら自前でもダメだった
  • SDKのダイナミックリンクライブラリにリンク」しているのを検出する他の方法がなにかある

ちなみに最初はSDKに含まれる libarchive2.6.2.dylib をリンクしてましたが、いちおう他のもの(バージョン番号が省略されたもの)も試しました。ダメでした。

iOSアプリ用にlibarchiveをビルドする手順

他のライブラリでもほぼ同じように行けるんじゃないかなと思う。ちょっとヘッダまわりの癖にあわせて手間がいるかな? 以下のエントリを激しく参考にさせていただきました。多謝!
PostgreSQL Client Library (libpq) for iPhone/iPad - jakago

なお手順では、やれるところはXcodeでやる、という感じ。ではれっつごー。

ライブラリのビルド用プロジェクトを作る

ライブラリのビルド用プロジェクトをアプリとは別に新規作成。今回は "archivelib" という名前で作った。なんでわざわざlibarchiveという名前を避けたのかは、お察しください
なお作成時にはXcodeで "iPhone OS" → "Library" → "Cocoa Touch Static Library" を選んでおく。これ重要。

ダウンロード、展開、準備

ネットから libarchive-2.6.2.tar.gz および bzip2-1.0.5.tar.gz を適当な場所にダウンロード。それから以下のコマンドを叩いて展開と configure。

~$ cd path/to/archivelib
archivelib$ tar xzf ~/Download/libarchive-2.6.2.tar.gz
archivelib$ tar xzf ~/Download/bzip2-1.0.5.tar.gz
archivelib$ cd libarchive-2.6.2
libarchive-2.6.2$ ./configure --with-gnu-ld             # やらないと config.h が生成されない!
libarchive-2.6.2$ mkdir c; cp libarchive/*.c c/         # Xcodeでプロジェクトに加えるときに楽ができるように
libarchive-2.6.2$ cd ../bzip2-1.0.5
bzip2-1.0.5$ mkdir c; cp libarchive/*.c c/
ライブラリのビルド準備

Xcodeのプロジェクト archivelib に戻る。"Other Sources" に適当に各ライブラリ用のグループを作り(bzip2/libarchive)、それぞれの中に各ライブラリディレクトリの c/ の中のものを全部放り込む。選べば選べるんだろうけど(Windows用のとかあるし)、まあ面倒なのでまとめて。別にエラーになったりしないし。
あとはプロジェクト設定のビルド設定のうち「ユーザヘッダ検索パス」にプロジェクト用ディレクトリのパスを加え、「再帰的」にチェックを入れる。設定変更を Device/Release どっちかだけ忘れたりしないようにね! もう面倒だし「すべての構成」でやるといいと思うよ!

ここからは多分ライブラリの都合にあわせての部分。
まず libarchive はビルド時に HAVE_CONFIG_H というフラグを必要とするので、これを PROJECTNAME_Prefix.pch に加える。

#define HAVE_CONFIG_H 1

また libarchive のソースコードには bzlib がシステムのincludeパスにあると思っているように書かれているが、今回はいっしょに落としてきてプロジェクトフォルダの中にあるので、以下のように修正する。これは archive_read_support_compression_bzip2.c と archive_write_set_compression_bzip2.c の2ファイルの先頭付近にある。

  #ifdef HAVE_BZLIB_H
- #include <bzlib.h>
+ #include "bzlib.h"
  #endif

これで準備完了! (自分が忘れてなければ)*1

ライブラリのビルドとユニバーサルバイナリ

これはもう参考にしたページの通り。Xcodeのターゲット指定で "Simulator" および "Device" の両方で「ビルド」を実行。警告はもりもりと出力されるが、無視。エラーは無視できないので出たら頑張る。多分ヘッダまわりでフラグが何か足りてないとかだと思う。必要なら Prefix.pch に加えるとか、あとは configure するところまで戻って、なにかオプションを変えてやりなおすとかね。*2
ビルドが終了したらターミナルを開き、ユニバーサルバイナリを作成するために以下のコマンドを実行する。

 $ cd archivelib
 archivelib$ lipo -create build/Release-iphone*/libarchivelib.a -output ./libarchivelib.a

これでお望みのものができました!

アプリのプロジェクトで取り込み、使用する

できあがった *.a ファイルはアプリのプロジェクト上で "Frameworks" から "追加" → "既存のファイル" で追加すると、ビルド時のリンク対象にしてくれる。もちろん使用する側のソースコードには参照するときの定義が必要なので、必要な各ライブラリの *.h ファイルはプロジェクトに取り込んで適当な場所に放り込んでおくといい。
シミュレータで動作し、実機でも動作すれば、見事にユニバーサルバイナリのビルドの完成が確認できます!

ぶっちゃけ

初めてやると面倒なんだけど、一度作っちゃえばあとはあまり気にしなくていいので、そこを乗り越えられるかどうかがキモだと思います。一度やっちゃうと、これでどんなライブラリでも使えるかなーという気分になるので、精神的にラクにはなりました。バイナリのアップロード後の2時間はいまだにドキドキものですが。
また zip だけを扱うなら、多分SDKのzlibを直接リンクしても弾かれない*3ので、そっちを使う方がラクだとは思います。libarchive って実は gz/bzip2 以外にもあれこれ対応してるらしいんだよね。あんまり使う予定もないんだけどさ。

以上、もしここまで実際にやった人がいたら、お疲れさまでした!

*1:すいません酒飲みながら書いてます……。

*2:自分の場合も "bzlib.h" への変更にしばらく気付かなくて、最初は bzip2 を外すために configure --without-bzip2 して、bzip2関連ファイルも外してビルドしたりしてた。それでも構わないんなら、そういう方法もあり。

*3:と思われる。弾かれるなら誰か教えて!