ブログを書くのはすごく久しぶり。ここのところ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に移行しなければいけないのだけど、ここでひとつめの地雷を踏んだ。
移行の方法はドキュメントに書いてあるので、その通りにやればいいだけなんだが、ここでうっかりミスすると
1 2 3 |
Error:A problem occurred configuring project ':app'. > The following model rules could not be applied due to unbound inputs and/or subjects: ... |
みたいなエラーが出てくる。
はっきり言ってエラーの内容に意味なんかないです。これが出てきたら、build.gradleの書き方がおかしいだけ。
僕がおかした間違いはこのStackOverflowの投稿と同じ。
このときはまだちゃんとした回答がなかったのでGradleとは何かというところから調べて自力で解決したんだけど、結論は
1 2 3 4 5 6 7 8 |
model { android { : } dependencies { : } } |
のように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を入れてます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
apply plugin: 'com.android.model.library' model { android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { minSdkVersion.apiLevel 10 targetSdkVersion.apiLevel 22 versionCode 1 versionName "1.0" } ndk { moduleName = "staticlibs" // このへんの設定はAndroid.mkを使うので関係ないハズ. ldLibs.add('log') cppFlags.add( "-std=c++11") cppFlags.add("-fexceptions") stl = 'c++_static' abiFilters.addAll(["armeabi, armeabi-v7a-hard"])//, "x86", "arm64-v8a", "x86_64", "mips", "mips64"]) } sources{ main { jni { source { srcDirs.removeAll() srcDirs.add('src/main/none') } } jniLibs { source { srcDirs.add('libs') } } } } } } import org.apache.tools.ant.taskdefs.condition.Os def ndkBuild = 'ndk-build' if (Os.isFamily(Os.FAMILY_WINDOWS)) { ndkBuild += '.cmd' } // call regular ndk-build(.cmd) script from app directory task ndkDebugBuild(type: Exec) { commandLine ndkBuild, '-C', file('Debug/jni').absolutePath, "NDK_DEBUG=1" doFirst { println commandLine.toString() } } task ndkReleaseBuild(type: Exec) { commandLine ndkBuild, '-C', file('Release/jni').absolutePath, "NDK_DEBUG=0" doFirst { println commandLine.toString() } } task ndkDebugClean(type: Exec) { commandLine ndkBuild, '-C', file('Debug/jni').absolutePath, "clean" doFirst { println commandLine.toString() } } task ndkReleaseClean(type: Exec) { commandLine ndkBuild, '-C', file('Release/jni').absolutePath, "clean" doFirst { println commandLine.toString() } } tasks.withType(JavaCompile) { compileTask -> if (compileTask.name.contains('Debug')) { compileTask.dependsOn ndkDebugBuild } else { compileTask.dependsOn ndkReleaseBuild } } clean.dependsOn ndkDebugClean, ndkReleaseClean |
それで、メインのアプリのモジュールのbuild.gradleはこんな感じに。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
import org.apache.tools.ant.taskdefs.condition.Os apply plugin: 'com.android.model.application' model { repositories { libs(PrebuiltLibraries) { cocos2d_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libcocos2d.a") } } cocos2d_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libcocos2d.a") } } cocos2dInternal_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libcocos2dxinternal.a") } } cocos2dInternal_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libcocos2dxinternal.a") } } cocos2dUI_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libui.a") } } cocos2dUI_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libui.a") } } cocos2dAudio_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libcocosdenshion.a") } } cocos2dAudio_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libcocosdenshion.a") } } cocos2dAudioEngine_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libaudioengine.a") } } cocos2dAudioEngine_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libaudioengine.a") } } cocos2dNetwork_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libnetwork.a") } } cocos2dNetwork_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libnetwork.a") } } cocos2dAndroid_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libcocos2dandroid.a") } } cocos2dAndroid_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libcocos2dandroid.a") } } crypto { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/curl/prebuilt/android/${targetPlatform.getName()}/libcrypto.a") } } freetype { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/freetype2/prebuilt/android/${targetPlatform.getName()}/libfreetype.a") } } jpeglib { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/jpeg/prebuilt/android/${targetPlatform.getName()}/libjpeg.a") } } pnglib { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/png/prebuilt/android/${targetPlatform.getName()}/libpng.a") } } nyahoon_debug { headers.srcDir "../../nyahoonlib/include" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libnyahoonlib.a") } } nyahoon_release { headers.srcDir "../../nyahoonlib/include" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libnyahoonlib.a") } } } } android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { applicationId "com.nyahoon.chocolatesweeper" minSdkVersion.apiLevel 10 targetSdkVersion.apiLevel 22 versionCode 1 versionName "1.0" } } android.ndk { moduleName = "ChocolateSweeper-JNI" platformVersion = 10 //same as minSdkVersion.apiLevel for better compatibility stl = "c++_static" cppFlags.add("-std=c++11") cppFlags.add("-fsigned-char") cppFlags.add("-fvisibility=hidden") cppFlags.add("-fexceptions") cppFlags.add("-I" + file("../../nyahoonlib/include").absolutePath) cppFlags.add("-I" + file("../../cocos2d/cocos").absolutePath) cppFlags.add("-Wno-conversion-null") ldLibs.addAll(['log', 'z', 'android', 'EGL', 'GLESv2', 'atomic', 'OpenSLES']) abiFilters.addAll(["armeabi"])//, "armeabi-v7a-hard", "x86", "arm64-v8a", "x86_64", "mips", "mips64"]) //filtering ABIs on the ones Google Play Games library is compatible with. } android.sources { main { java { source { srcDir "src" } } res { source { srcDir "res" } } jni { source { srcDir "jni" srcDir "../../Classes" exclude "**/*IOS.cpp" // iOS用のソースコードを除く. exclude "**/*Win.cpp" // Windows用のソースコードを除く. } dependencies { library "crypto" linkage "static" library "freetype" linkage "static" library "pnglib" linkage "static" library "jpeglib" linkage "static" } } jniLibs { source { srcDir "libs" } } manifest { source { srcDir "." } } assets { source { srcDir "../../Resources" } } } debug { jni { dependencies { library "cocos2dAudio_debug" linkage "static" library "cocos2dAudioEngine_debug" linkage "static" library "cocos2dUI_debug" linkage "static" library "cocos2dNetwork_debug" linkage "static" library "cocos2d_debug" linkage "static" library "cocos2dInternal_debug" linkage "static" library "cocos2dAndroid_debug" linkage "static" library "nyahoon_debug" linkage "static" } } } release { jni { dependencies { library "cocos2dAudio_release" linkage "static" library "cocos2dAudioEngine_release" linkage "static" library "cocos2dUI_release" linkage "static" library "cocos2dNetwork_release" linkage "static" library "cocos2d_release" linkage "static" library "cocos2dInternal_release" linkage "static" library "cocos2dAndroid_release" linkage "static" library "nyahoon_release" linkage "static" } } } } android.buildTypes { release { minifyEnabled false proguardFiles.add(file("proguard-rules.pro")); if (project.hasProperty("RELEASE_STORE_FILE")) { signingConfig = $("android.signingConfigs.release") } } } android.signingConfigs { create("release") { if (project.hasProperty("RELEASE_STORE_FILE")) { storeFile file(RELEASE_STORE_FILE) storePassword RELEASE_STORE_PASSWORD keyAlias RELEASE_KEY_ALIAS keyPassword RELEASE_KEY_PASSWORD } } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':libcocos2dx') compile project(':staticlibs') } |
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
に
1 2 3 4 5 |
zlib { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/zlib/prebuilt/android/${targetPlatform.getName()}/libz.a") } } |
を追加して、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 に移すことにした。その中でならリンクの順番は制御可能。
出力先のディレクトリがみつからない!?
まだまだ問題はなくなりません。こんなエラーが出ました。
1 2 |
Error:A problem was found with the configuration of task ':ChocolateSweeper:stripSymbolsArmeabiDebugSharedLibrary'. > Directory '/Users/Takao/Projects/ChocolateSweeper/proj.android-studio/app/build/intermediates/binaries/debug/obj/armeabi' specified for property 'inputFolder' does not exist. |
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を指定しているところを書き換えます。
1 2 3 4 5 6 7 8 9 10 11 |
import org.apache.tools.ant.taskdefs.condition.Os Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) def ndkDir = properties.getProperty('ndk.dir') def ndkBuild = ndkDir + File.separator if (Os.isFamily(Os.FAMILY_WINDOWS)) { ndkBuild += 'ndk-build.cmd' } else { ndkBuild += 'ndk-build' } |
他の方法もいくつかみつかったけど、今のバージョンで使えたのはこの方法のみ。
さて、これでようやくビルドが通って実行できるようになったけど…
.soファイルがみつからない!
これは僕の凡ミス。Android Studioに移行する際、JNIのモジュールの名前を変えて、AndroidManifest.xmlのandroid.app.lib_name
値も変えたけれど、そのときに.soのファイル名にあわせて”libChocolateSweeper-JNI”と、頭に”lib”をつけていたのが原因。”lib”を外して”ChocolateSweeper-JNI”としたら無事に.soファイルはみつかるようになった。
それでも.soのロードに失敗する!
.soファイルはみつかるようになったものの、まだロードは失敗する。こんなエラー。
1 |
dlopen failed: unknown reloc type 160 @ 0xXXXXXXXX |
原因はSTLにc++_staticを使っていたから。Cocos2d-xはgnustlしかサポートしていないので、stl = gnustl_static
に変える。Application.mkの方も。
追記: takataさんのコメントによればclangを使えばc++_staticでもいいそうです!
JNI_OnLoadがみつからない
まだ起動できない! ログをみると、JNI_OnLoadがみつからないと出ている。
これには心当たりがある。
Android.mkでビルドするときは、
1 |
LOCAL_WHOLE_STATIC_LIBRARIES := cocos2dxandroid_static |
として、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のドキュメントに書いてあった。
1 2 3 4 5 6 7 8 |
components.android { binaries.afterEach { binary -> if (binary.name.contains('Debug')||binary.name.contains('debug')) { binary.mergedNdkConfig.cppFlags.add("-DDEBUG") binary.mergedNdkConfig.cppFlags.add("-DCOCOS2D_DEBUG=1") } } } |
以上を踏まえて、最終的にできあがったアプリのbuild.gradleはこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
import org.apache.tools.ant.taskdefs.condition.Os apply plugin: 'com.android.model.application' model { repositories { libs(PrebuiltLibraries) { cocos2d_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libcocos2d.a") } } cocos2d_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libcocos2d.a") } } cocos2dInternal_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libcocos2dxinternal.a") } } cocos2dInternal_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libcocos2dxinternal.a") } } cocos2dUI_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libui.a") } } cocos2dUI_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libui.a") } } cocos2dAudio_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libcocosdenshion.a") } } cocos2dAudio_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libcocosdenshion.a") } } cocos2dAudioEngine_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libaudioengine.a") } } cocos2dAudioEngine_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libaudioengine.a") } } cocos2dNetwork_debug { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libnetwork.a") } } cocos2dNetwork_release { headers.srcDir "../../cocos2d/cocos" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libnetwork.a") } } crypto { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/curl/prebuilt/android/${targetPlatform.getName()}/libcrypto.a") } } freetype { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/freetype2/prebuilt/android/${targetPlatform.getName()}/libfreetype.a") } } jpeglib { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/jpeg/prebuilt/android/${targetPlatform.getName()}/libjpeg.a") } } pnglib { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/png/prebuilt/android/${targetPlatform.getName()}/libpng.a") } } zlib { binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../../cocos2d/external/zlib/prebuilt/android/${targetPlatform.getName()}/libz.a") } } nyahoon_debug { headers.srcDir "../../nyahoonlib/include" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Debug/obj/local/${targetPlatform.getName()}/libnyahoonlib.a") } } nyahoon_release { headers.srcDir "../../nyahoonlib/include" binaries.withType(StaticLibraryBinary) { staticLibraryFile = file("../staticlibs/Release/obj/local/${targetPlatform.getName()}/libnyahoonlib.a") } } } } android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { applicationId "com.nyahoon.chocolatesweeper" minSdkVersion.apiLevel 10 targetSdkVersion.apiLevel 22 versionCode 1 versionName "1.0" } } android.ndk { moduleName = "ChocolateSweeper-JNI" platformVersion = 10 //same as minSdkVersion.apiLevel for better compatibility stl = "gnustl_static" cppFlags.add("-std=c++11") cppFlags.add("-fsigned-char") cppFlags.add("-fvisibility=hidden") cppFlags.add("-frtti") cppFlags.add("-fexceptions") cppFlags.add("-I" + file("../../Classes").absolutePath) cppFlags.add("-I" + file("../../nyahoonlib/include").absolutePath) cppFlags.add("-I" + file("../../cocos2d/cocos").absolutePath) cppFlags.add("-I" + file("../../cocos2d/cocos/platform").absolutePath) cppFlags.add("-I" + file("../../cocos2d/cocos/platform/android").absolutePath) cppFlags.add("-Wno-conversion-null") ldLibs.addAll(['log', 'android', 'EGL', 'GLESv2', 'atomic', 'OpenSLES']) abiFilters.addAll(["armeabi"])//, "armeabi-v7a-hard", "x86", "arm64-v8a", "x86_64", "mips", "mips64"]) } android.sources { main { java { source { srcDir "src" } } res { source { srcDir "res" } } jni { source { srcDir "jni" srcDir "../../Classes" srcDir "../../cocos2d/cocos/platform/android" exclude "**/*IOS.cpp" exclude "**/*Win.cpp" } } jniLibs { source { srcDir "libs" } } manifest { source { srcDir "." } } assets { source { srcDir "../../Resources" } } } debug { jni { dependencies { library "cocos2dAudio_debug" linkage "static" library "cocos2dAudioEngine_debug" linkage "static" library "cocos2dUI_debug" linkage "static" library "cocos2dNetwork_debug" linkage "static" library "cocos2d_debug" linkage "static" library "cocos2dInternal_debug" linkage "static" library "nyahoon_debug" linkage "static" library "crypto" linkage "static" library "freetype" linkage "static" library "pnglib" linkage "static" library "jpeglib" linkage "static" library "zlib" linkage "static" } } } release { jni { dependencies { library "cocos2dAudio_release" linkage "static" library "cocos2dAudioEngine_release" linkage "static" library "cocos2dUI_release" linkage "static" library "cocos2dNetwork_release" linkage "static" library "cocos2d_release" linkage "static" library "cocos2dInternal_release" linkage "static" library "nyahoon_release" linkage "static" library "crypto" linkage "static" library "freetype" linkage "static" library "pnglib" linkage "static" library "jpeglib" linkage "static" library "zlib" linkage "static" } } } } android.buildTypes { release { minifyEnabled false proguardFiles.add(file("proguard-rules.pro")); if (project.hasProperty("RELEASE_STORE_FILE")) { signingConfig = $("android.signingConfigs.release") } } } android.signingConfigs { create("release") { if (project.hasProperty("RELEASE_STORE_FILE")) { storeFile file(RELEASE_STORE_FILE) storePassword RELEASE_STORE_PASSWORD keyAlias RELEASE_KEY_ALIAS keyPassword RELEASE_KEY_PASSWORD } } } components.android { binaries.afterEach { binary -> if (binary.name.contains('Debug')||binary.name.contains('debug')) { binary.mergedNdkConfig.cppFlags.add("-DDEBUG") binary.mergedNdkConfig.cppFlags.add("-DCOCOS2D_DEBUG=1") } } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':libcocos2dx') compile project(':staticlibs') } |
いやあ、長かった。ここまでくるのに2週間弱費した。最後に言ってやりたい。「Google 死ね!」
さらに追記
clangならc++_staticが使えるとのことなので試してみました。が、みごとにまた地雷を踏んだ。
まずメインのプロジェクトでclangを使うには
1 2 3 |
android.ndk { toolchain = "clang" } |
を追加すればいいはずなんだけど、syncが通らない。sdk/ndk-bundle/toolchainsの下にはllvmフォルダがバージョン番号なしであるだけなのでtoolchainVersionを指定してもダメ。でも試しにllvmフォルダをllvm-3.8にリネームして、toolchainVersion = “3.8”を追加したらsyncが通るではないですか!
でも今度はAndroid.mkのビルドが通らない。Application.mkに
1 |
NDK_TOOLCHAIN_VERSION := clang3.8 |
を指定してもどうも実際にはないフォルダを参照しようとしてエラーになる。
しょうがないのでシンボリックリンクでllvmフォルダを復活させて
1 |
NDK_TOOLCHAIN_VERSION := clang |
としたらビルドが通りました。
それにしても、NDKの開発チームとAndroid StudioのExperimental Pluginを開発しているチームは仲が悪いのだろうか? 全然連携できてないじゃん! Googleマジで死ね! というかExperimental Pluginの開発チームがダメダメな感じ。
コンパイラに clang を使用することで、c++_static を使用することは可能です(v3.8.1で確認済み
コンパイラに clang を使用することで、stlにc++static を使用することは可能です( v3.8.1 で確認
コメントありがとうございます!
ちょうど他のバグ(https://code.google.com/p/android/issues/detail?id=58476)に悩まされていたので、私もclang使うことにします。clangでもこのバグ(というよりはARMの仕様?)は直らなかったんですけどね。