ブログを書くのはすごく久しぶり。ここのところChocolate Sweeperをリリースするのに忙しかったから。
このゲームはマインスイーパーをモチーフにしていて、プログラマのみなさんにはだいぶ好評をいただいているので、もしよかったらiOS版をリリースしたので遊んでみてください。

ブログのタイトル通り、Android版は絶賛開発中です!

それにしても、Androidの開発環境はヒドイ。環境構築に手間がかかるのはいいとして、十分なドキュメントもないし、エラーが出てもそれが何を意味するのかわからない。ちょっとカスタマイズしようと思っても、ビルドスクリプトで何ができるのかリファレンスもない(僕がみつけられないだけかもしれないけど)。

Cocos2d-xも一応Android Studioに対応しているので、Android Studioからビルドして実行するのは割と簡単だったけど、C++のコードは全部Android.mkを使ってビルドしているのでデバッガが使えない。

とりあえず実行してみたところ、いきなりエラーで落ちちゃったので、やはりデバッガを使えるようにしよう! ということで、Android StudioでC++のコードもビルドするようにしたのだけど、そのときにありとあらゆる地雷を踏んだのでありました。マインスイーパーのゲームを作っているだけにね (ドヤッ

そこで、恥かしながら、ここにその全てを紹介します。ドキュメントが少ないので同じようなエラーで困っている人の助けになれば幸いです。

まず、すごく参考になるブログがあったので、それを紹介します。
The new NDK support in Android Studio
僕もこれを読んでからスタートしました。

それでまずGradleのAndroid用プラグインをExperimental Pluginに移行しなければいけないのだけど、ここでひとつめの地雷を踏んだ。
移行の方法はドキュメントに書いてあるので、その通りにやればいいだけなんだが、ここでうっかりミスすると

みたいなエラーが出てくる。
はっきり言ってエラーの内容に意味なんかないです。これが出てきたら、build.gradleの書き方がおかしいだけ。
僕がおかした間違いはこのStackOverflowの投稿と同じ。
このときはまだちゃんとした回答がなかったのでGradleとは何かというところから調べて自力で解決したんだけど、結論は

のようにdependenciesをmodelの中に入れちゃったのが原因。
dependenciesはGradleの基本機能なのでExperimental Pluginとは関係なくmodel{}の外に書かなければいけなかった。
他に android.xxx {}と書かなければいけないところを android { xxx {} } と書いちゃうとか、あるいはその逆とかの場合も同じようなエラーが出るので、他の人のブログとかExperimental Pluginのドキュメントを良く読んで、その通りに書くようにした方が無難です。ただし、”Experimental” なので仕様がコロコロかわるみたいで、古いブログを鵜呑みにするのも危険。

さて、これでExperimental Pluginへの移行ができたので、Cocos2d-xをスタティックライブラリにビルドしてそれをリンクするようにbuild.gradleを書き換える。でもスタティックライブラリをどうやってAndroid Studioでビルドするのかよくわからなかったので(たぶんldFlagsを指定すればいいんだろうけど)、最初に紹介したブログを元にスタティックライブラリはAndroid.mkを使ってビルドすることにした。
staticlibsというモジュールをプロジェクトに追加して、build.gradeはこんな感じ。DebugとReleaseでフォルダを分けて、それぞれにjni/Android.mkとjni/Application.mkを入れてます。

それで、メインのアプリのモジュールのbuild.gradleはこんな感じに。

Debug用とRelease用にライブラリのリポジトリをわけてるのでやけに長い。これを一緒にできればもうちょっとスッキリするんだけど。
でも、これはまだダメなやつです。これを参考にすると以下の地雷を踏むことになります。

STLのリンクが通らない!
STLのリンクがなかなか通らないという問題に悩まされました。これはexceptionやRTTIのコンパイルオプションがスタティックライブラリ(Application.mkで指定)とアプリのJNI(build.gradleで指定)で一致しないと起きるみたいです。
Exceptionの方はどちらにも-fexceptionsをつけていたので問題なかたけれど、RTTIの方はApplication.mkの場合デフォルトでRTTIあり、Android Studioの場合デフォルトでRTTIなしになっていたのでした!
なので、アプリのメインのbuild.gradelのcppFlagsに-frttiを追加する必要があります。(Cocos2d-xはRTTIがないとコンパイルが通らない)

zlibのリンクが通らない!
まだまだリンクの問題が続きます。Cocos2d-xで使っているlibpngが参照している一部のzlib関数のシンボルがみつかりません。これは僕が使っているNDKのプラットフォームレベルが10だからだと思うのだけど、libpngはもっと新しいlibzを必要としているみたいなので、libzをCocos2d-xに付属しているやつに切り替えます。
ldLibsから'z'を取り除き、repositories

を追加して、main.jniのdependenciesにzlibを追加します。

Release版のリンクが通らない!
これでDebug版のビルドが通ったと思ったら、Release版でまだエラーが出る!
アプリ用のbuild.gradleのあるフォルダの下に “build/tmp/linkXxxxxArmabiReleaseSharedLibrary/” みたいなフォルダが作られて、そこに”options.txt”があるので、どいういうコマンドでリンカが実行されたのかをそこで確認できる。
それで気付いたのは、どうもリンカに指定するライブラリの順番が debug, main, releaseの順番になっているらしい。まあABC順ですね。
なので、Debug版のときは main で指定していたライブラリが debug で指定していたライブラリ の後にリンクされていたので問題なかったのだけど、Release版では release で指定していたライブラリが参照していた他のライブラリのシンボルが解決できずにいたのでした。
ライブラリのシンボルがリンクに追加されるタイミングでしか解決されず、それ移行に追加したライブラリが前に追加したライブラリのシンボルを参照していたらシンボルがみつからずにエラーになるというGCC系のリンカの仕様はなんとかならないものかなあと思いつつ、しょうがないので main の dependencies で指定していたライブラリを全て debug と release に移すことにした。その中でならリンクの順番は制御可能。

出力先のディレクトリがみつからない!?
まだまだ問題はなくなりません。こんなエラーが出ました。

StackOverflowによればExperimental Pluginのバグで、beta-4で直っているということなので(というかbeta-4がいつ出たんだ!)、beta-4に更新して解決。

Terminalからはビルドできるけど、メニューからはできない!
実は今までTerminalから./gradlew --info build(Mac)と打ってビルドしてました。この方がエラーが起きたときに色々情報を出してくれるので。ところがメニューから”Make Project”するとビルドができないではないですか!
どうやら、ndk-buildをフルパスで指定しなければいけないようです。でも、どうやってndkのパスを取得するか。Android StudioのProject StructureでSDK Locationを指定しているので、そのパスを取得したいところなんだけど・・・と思って探したら、またStackOverflowにありました。
こんな具合にstaticlibsのbuild.gradleでndk-buildを指定しているところを書き換えます。

他の方法もいくつかみつかったけど、今のバージョンで使えたのはこの方法のみ。

さて、これでようやくビルドが通って実行できるようになったけど…

.soファイルがみつからない!
これは僕の凡ミス。Android Studioに移行する際、JNIのモジュールの名前を変えて、AndroidManifest.xmlのandroid.app.lib_name値も変えたけれど、そのときに.soのファイル名にあわせて”libChocolateSweeper-JNI”と、頭に”lib”をつけていたのが原因。”lib”を外して”ChocolateSweeper-JNI”としたら無事に.soファイルはみつかるようになった。

それでも.soのロードに失敗する!
.soファイルはみつかるようになったものの、まだロードは失敗する。こんなエラー。

原因はSTLにc++_staticを使っていたから。Cocos2d-xはgnustlしかサポートしていないので、stl = gnustl_staticに変える。Application.mkの方も。
追記: takataさんのコメントによればclangを使えばc++_staticでもいいそうです!

JNI_OnLoadがみつからない
まだ起動できない! ログをみると、JNI_OnLoadがみつからないと出ている。
これには心当たりがある。
Android.mkでビルドするときは、

として、cocos2d-xのライブラリの一部は-whole-archiveオプションをつけてリンクされていて、JNI_OnLoadはまさにこのライブラリの中にある。
ところがbuild.gradleでは普通にリンクしているだけなのである。
このライブラリだけ-whole-archiveをつけてリンクするには、-Wl,-whole-archibe libcocos2dandroid.a -Wl,-no-whole-archiveをldFlagsに追加して、”../staticlibs/[Debug|Release]/obj/local/[armabi|armabi-v7a-hard|x86]”などをライブラリのディレクトリに指定しないといけない。
ldFlagsの設定はいいとして、ライブラリのディレクトリに[Debug|Release]とか[armabi|armabi-v7a-hard|x86]とかビルドのタイプに依存したものを指定する方法がよくわからん。
もうここまでで疲れ果ててるので、-whole-archiveでリンクするのはあきらめて、このライブラリのソースをアプリの.soのソースファイルに追加することにした。

デバッグビルドにDEBUGが定義されていない
これでとりあえず起動はできるようになったのだが、Debug版のビルドなのにDEBUGが定義されていなくて、#ifdef DEBUGなどでくくられたコードが含まれていないことが発覚!
せっかくデバッグ用に色々エラーチェックを仕込んでおいても全部素通り。
普通、デフォルトで入れてくれるんじゃないの? Debugなんだから。
しょうがないので、DebugのときだけcppFlagsに-DDEBUGを追加。やり方はExperimental Pluginのドキュメントに書いてあった。

以上を踏まえて、最終的にできあがったアプリのbuild.gradleはこんな感じ。

いやあ、長かった。ここまでくるのに2週間弱費した。最後に言ってやりたい。「Google 死ね!」

さらに追記
clangならc++_staticが使えるとのことなので試してみました。が、みごとにまた地雷を踏んだ。
まずメインのプロジェクトでclangを使うには

を追加すればいいはずなんだけど、syncが通らない。sdk/ndk-bundle/toolchainsの下にはllvmフォルダがバージョン番号なしであるだけなのでtoolchainVersionを指定してもダメ。でも試しにllvmフォルダをllvm-3.8にリネームして、toolchainVersion = “3.8”を追加したらsyncが通るではないですか!
でも今度はAndroid.mkのビルドが通らない。Application.mkに

を指定してもどうも実際にはないフォルダを参照しようとしてエラーになる。
しょうがないのでシンボリックリンクでllvmフォルダを復活させて

としたらビルドが通りました。

それにしても、NDKの開発チームとAndroid StudioのExperimental Pluginを開発しているチームは仲が悪いのだろうか? 全然連携できてないじゃん! Googleマジで死ね! というかExperimental Pluginの開発チームがダメダメな感じ。

3 thoughts on “Cocos2d-xを使ったアプリをAndroid Studioでビルドするときにハマった罠

  • 2016/03/30 at 11:52 PM
    Permalink

    コンパイラに clang を使用することで、c++_static を使用することは可能です(v3.8.1で確認済み

    Reply
  • 2016/03/30 at 11:53 PM
    Permalink

    コンパイラに clang を使用することで、stlにc++static を使用することは可能です( v3.8.1 で確認

    Reply
    • 2016/04/01 at 10:51 AM
      Permalink

      コメントありがとうございます!
      ちょうど他のバグ(https://code.google.com/p/android/issues/detail?id=58476)に悩まされていたので、私もclang使うことにします。clangでもこのバグ(というよりはARMの仕様?)は直らなかったんですけどね。

      Reply

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Anti Spam Code *