前提
cocos2dライブラリを使った結構古めのプロジェクトに、Product Flavor対応をしました。
が、一部C++のコードの中にもFlavorに依存して変えたい部分があり、対応方法を探しました。
条件としては以下があります。
- cocos2d (version3.17.2)
- かなり古いプロジェクトで上げることが出来ない
- NDKのビルドには
ndkBuild
を使う-
cmake
ではなく、ndkBuild
です
-
また、筆者はC/C++経験は長かったものの、既に離れて10年経過しており、そもそもmakeファイルはちゃんと書いたことがない(IDEに頼ってビルドしてきた)人間です。
その辺しっかり分かっている方には「今更何を」感があるかも知れませんが、ご容赦下さい。
現状
C++コード
#define SAMPE_ID=xxxxx
#if SAMMPLE_ID==xxxx
// ID=xxxxの時のコード
#elif SMAPLE_ID=yyyy
// ID=yyyyの時のコード
#elif SAMPLE_ID=zzzz
// ID=zzzzの時のコード
#endif
みたいな感じで、SAMPLE_ID
に応じてdefineマクロでコードを変えていました。
Androidプロジェクト
Androidプロジェクト側は、同じくSAMPLE_ID
に応じて、リソースファイルが差し替わるなどの対応があり、Product Flavorに対応させました。
flavorDimensions "demo"
productFlavors {
sampleXXXX {
dimension "demo"
applicationId "com.example.cocos2d.sampleX"
versionCode 51
manifestPlaceholders = [sampleScheme:"fooxxxx",
appName:"XXXX cocosSample"]
}
sampleZZZZ {
dimension "demo"
applicationId "com.example.cocos2d.sampleZ"
versionCode 52
manifestPlaceholders = [sampleScheme:"foozzzz",
appName:"ZZZZ cocosSample"]
}
}
以前はリソースファイルをわざわざ別のフォルダからコピペしてきたりして切り替えていたのですが、これだけでも多少切替が楽になりました。
が、xxxx
というFlavorでビルドしたとき、C++コードのSAMPLE_ID
のdefineをxxxx
とし、yyyy
でビルドしたいときには、yyyy
に書き変えるという手動コード書き換えが発生していました。
結果、Flavorを切り替えてビルドする際、Fioo.cpp
ファイルの書き換え漏れを個人的に多発させてしまい、「アプリがちゃんと動かない!」と勝手に焦っていることが多くありました。
そこで、
「Product Flavorに追随すべき内容なのだから、Product Flavorの設定で出来る方法は無いか?」
と探した結果がこの記事です。
結論
結論から言うと、コンパイル時フラグ(cppFlags)を使うのですが、ここで前提であったcocos2d/ndkBuildを使っている、ということがネックになりハマりました。
cmake
でしたら、cppFlag
にそのまま追加すれば良さそうなのですが、それをやるとcocos2dのビルドが失敗するようになりました。
flavorDimensions "demo"
productFlavors {
sampleXXXX {
dimension "demo"
applicationId "com.example.cocos2d.sampleX"
versionCode 51
manifestPlaceholders = [sampleScheme:"fooxxxx",
appName:"XXXX cocosSample"]
externalNativeBuild.ndkBuild {
cppFlags += '-DSAMPLE_APP_ID=xxxx'
}
}
sampleZZZZ {
dimension "demo"
applicationId "com.example.cocos2d.sampleZ"
versionCode 52
manifestPlaceholders = [sampleScheme:"foozzzz",
appName:"ZZZZ cocosSample"]
externalNativeBuild.ndkBuild {
cppFlags += '-DSAMPLE_APP_ID=zzzz'
}
}
}
そう、我々が使っているのはndkBuild
であり、Application.mk
でコンパイルフラグなどは設定しており、build.gradle
でcppFlags
を設定すると、どうやらその内容を上書きしてしまうようなのです。
cocos2dでデフォルトで作成されるApplication.mk
はこんな感じです。
APP_STL := c++_static
# Uncomment this line to compile to armeabi-v7a, your application will run faster but support less devices
#APP_ABI := armeabi-v7a
APP_CPPFLAGS := -frtti -DCC_ENABLE_CHIPMUNK_INTEGRATION=1 -std=c++11 -fsigned-char -Wno-extern-c-compat
APP_LDFLAGS := -latomic
APP_ABI := armeabi-v7a
APP_SHORT_COMMANDS := true
USE_ARM_MODE := 1
ifeq ($(NDK_DEBUG),1)
APP_CPPFLAGS += -DCOCOS2D_DEBUG=1
APP_OPTIM := debug
else
APP_CPPFLAGS += -DNDEBUG
APP_OPTIM := release
endif
これで言うところの、APP_CPPFLAGS
なんかをまるっと奪ってしまうようなんですね、build.gradle
でcppFlags
に追加すると。
そのためcocos2dのライブラリのビルドに失敗するということのようです。
解決策は、arguments
を使う、というものでした。
flavorDimensions "demo"
productFlavors {
sampleXXXX {
dimension "demo"
applicationId "com.example.cocos2d.sampleX"
versionCode 51
manifestPlaceholders = [sampleScheme:"fooxxxx",
appName:"XXXX cocosSample"]
externalNativeBuild.ndkBuild {
arguments 'SAMPLE_APP_ID=xxxx'
}
}
sampleZZZZ {
dimension "demo"
applicationId "com.example.cocos2d.sampleZ"
versionCode 52
manifestPlaceholders = [sampleScheme:"foozzzz",
appName:"ZZZZ cocosSample"]
externalNativeBuild.ndkBuild {
arguments 'SAMPLE_APP_ID=zzzz'
}
}
}
arguments
で渡したものは、Application.mk
で次のように参照してAPP_CPPFLAGS
に追加します。
ifneq ($(SAMPLE_ID),)
APP_CPPFLAGS += -DSAMPLE_ID=$(SAMPLE_ID)
endif
なんかちょっと2度手間になってはいる気はしますが、arguments
で受け取った$(SAMPLE_ID)
の値を、未設定でなければ、-D
オプションでSAMPLE_ID
のdefine値として渡しています。
で、Foo.cpp
の方からは、SAMPLE_ID
の定義部分は削除してしまいます。
// #define SAMPE_ID=xxxxx // 削除
#if SAMMPLE_ID==xxxx
...
これで、ProductFlavorにFoo.cpp
内のコードが一緒に連動して変わるようになりました。
恐らく最初から設計できるのであればもっとスマートな方法(targets
を使う方法とか)あるのでしょうが、現状のコードや仕組みを大きく変えること無く、コンパイルオプションで解決できたので、まずまずと思っています。
【おまけ】iOSアプリも同じことをしている場合
実は当該プロジェクトはFoo.cpp
ファイルはAndroidとiOSとで共有しているコードです。
なので、iOSでも同じようにやりたくなります。
NDKでコンパイルオプションで解決できたので、iOSというかXcodeでも同じようにコンパイルオプションを指定すれば良いのは分かりますね。
Build SettingsとUser-Defined settingを使ってこんな風に実現しました。
- コンパイルフラグ
Apple Clang - Custom Compiler Flags ※flagsで絞り込んだ方が探しやすいです
Other C++ Flagsに-DSAMPLE_ID=$(SAMPLE_ID)
を追加する
- ユーザー定義設定
User-Defined setting ※Build Settingsの一番下の方にあります
key=SAMPLE_ID, value=xxxxで作成する
あとはinfo.plist
も実はこのSAMPLE_ID
を使いたい部分があったのでこんな感じにしました。
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.example.cocos2d.sample</string>
<key>CFBundleURLSchemes</key>
<array>
<string>foo${SAMPLE_ID}</string>
</array>
</dict>
</array>
これで、User-Defined
のSAMPLE_ID
を書き変えるだけでFoo.cpp
の実装やinfo.plist
の値を切り替えることが出来ました。
が、これだといちいちSAMPLE_ID
の値を書き変えなければなりません。個人的に、この設定部分が使いづらくて良く意図しない場所を編集してしまうため、出来ればXcode上で触りたくありません。
Android StudioでBuild Variantsを切り替えるのと同じような、UIで切り替えられると便利です。
※本当はコマンドラインビルドのことも考えなければならないのですが、いったん担当者が違うので無視しています。
そこで気付いたのが"Target"です。
Targetごとに、User-Defindeの値だけを変えればいいんじゃない?
ということで、SAMPLE_ID
の数だけ、Targetを作りました。
- 10000の場合
- 20000の場合
まとめ
- AndroidのndkBuildでnative(C/C++)コードをProduct Flavorに追随させたい場合は、
arguments
を使う。 - cmakeならcppFlagsでやれるんじゃないかな?
- cocos2dがデフォルトで持ってるmakeファイルをよく見てね
- XcodeならTargetとBuild SettingsのOptional Custom Compiler Flags/User-Defined Settingを使うと同じような感じにできる
参考サイト
cppFlagとかの情報
https://developer.android.com/ndk/guides/android_mk?hl=ja
https://developer.android.com/ndk/guides/cpp-supportcppFlagでProductFlavor追随できるよと言う情報
https://stackoverflow.com/questions/47350245/ndk-support-with-different-product-flavour
https://stackoverflow.com/questions/9052792/how-to-pass-macro-definition-from-make-command-line-arguments-d-to-c-sourcendkBuildではargumentsを使うヒントになりました
https://qiita.com/kenmasu/items/598496840eef775db142
https://developer.android.com/studio/projects/gradle-external-native-builds?hl=ja
https://stackoverflow.com/questions/44169071/gradle-seems-to-overrwrite-ndk-build-arguments-while-adding-product-flavorsXcodeのTargetの使い方の参考になりました。
https://qiita.com/alt_yamamoto/items/c3b78b24d0ff5ddc93f9