Droidkaigiのリポジトリを参考に自分でGradle Pluginを作成してみる
Droidkaigiのリポジトリのように、マルチプロジェクトで設定ファイルを共有する仕組みを知りたいと思って幾星霜。
今まで重い腰が上がらなかったのだが、Wear OSのプロジェクトが設定をベタ書きにしており非常につらい思いをしていたので、必要に迫られてやることにした。誰かの参考になれば幸いである。
Droidkaigiのリポジトリのように、マルチプロジェクトで設定ファイルを共有する仕組みを知りたいと思って幾星霜。
今まで重い腰が上がらなかったのだが、Wear OSのプロジェクトが設定をベタ書きにしており非常につらい思いをしていたので、必要に迫られてやることにした。誰かの参考になれば幸いである。
Gradleの機能として提供されているversionCatalogsを使って依存関係を定義してみた。
Gradleでライブラリの管理を便利にできそうなプラグインを見かけた。refreshVersionsというプラグインだ。
buildSrcを使って一括管理する方法は知っていたが、プロジェクトごとに用意するのも面倒くさい。なにか楽な方法はないかと思っていたが、これはその解の1つとなりそう。
gradle経由でテストタスクを実行すると、テスト結果のレポートがbuild/reportsディレクトリに出力される。このレポートファイルを確認するのに、毎回ProjectウィンドウをProject表示に切り替えて、ディレクトリを掘ってファイルをブラウザで開くようにするのは手間である。そこでテスト実行後に自動的に開くようにした。
もっとスマートにやる方法があるのではないかと思うのだが、Gradleがよくわからなくて私には無理だった。以下のタスクをapp/build.gradleに追加した。
task openReportJvmTest(type: Exec) {
workingDir "build/reports/tests/testProdDebugUnitTest"
commandLine 'open'
args "index.html"
}
task openReportInstrumentedTest(type: Exec) {
workingDir "build/reports/androidTests/connected/flavors/MOCK"
commandLine 'open'
args "index.html"
}
openReportJvmTest.onlyIf { !ciBuild && !travisBuild }
openReportInstrumentedTest.onlyIf { !ciBuild && !travisBuild }
tasks.withType(AbstractTestTask) { task ->
if (task.name == "testProdDebugUnitTest") {
task.finalizedBy openReportJvmTest
}
}
tasks.withType(com.android.build.gradle.internal.tasks.AndroidTestTask) { task ->
if (task.name == "connectedMockDebugAndroidTest") {
task.finalizedBy openReportInstrumentedTest
}
}
openReportXXXというのがテストレポートをブラウザで開くタスク。開くディレクトリを直書きしているが、がんばればタスクによって開くディレクトリを変えることはできるだろう。openコマンドを使っているが、Windowsだときっと動かない。
既存のテストタスクのFinalizerタスクとして実行するよう指定しているが、CI上ではopenコマンドが認識できないので失敗してしまう。そこでCI上では実行しないようにonlyIfで指定している。
pluginで定義されているテストタスクの後に実行させたいのだが、tasks.withTypeを使うことで各タスクの参照を得ている。直接タスクを指定すると「そんなプロパティはない」と言われたり、nullだったりしてうまくいかなくてこういう形に行き着いた。
取得したテストタスクそれぞれでfinalizedByを使ってレポートを開くタスクを指定している。dependsOnを使うとテストが全てパスしていればレポートが開くが、1つでもテストが失敗しているとopenReportタスクが実行されないので、finalizedByを使っている。
http://gradle.monochromeroad.com/docs/userguide/more_about_tasks.html
そもそもRun configurationでテストを実行できるようにすればいいのではないかと思うかもしれない。というか私も最初はその方法をとった。
しかしAndroid Instrumented Testに関してはできたが、Android JUnitではできなかった。
個別のテストをクラスを指定して実行する分には問題ないが(テストコードを表示して実行するやり方)、プロジェクトに存在するテストコードをまとめて実行する方法が取れなかった。Javaで書かれたテストコードは実行されても、Kotlinで書かれたテストが漏れてしまうのである(このプロジェクトはJavaで書かれたテストコードとKotlinで書かれたテストコードが混在している)。
しょうがないのでCIでも実行するgradleのテストタスクを走らせる方法をとることにしたのだ。
ストアに公開しているアプリは2018年8月までにtargetSdkVersionを26以上にする必要がある。その関係で以前にリリースしたアプリを更新しているのだが、その際に最初のハードルとなるのが古いAndroid Studioで作ったプロジェクトを現在のバージョンに更新する作業である。
最近何回も同じことをやっているので、備忘録的に書いておく。
基本的には全部Migrate to Android Plugin for Gradle 3.0.0に書いてあるので、そのとおりにやるだけである。
書いている途中でAndroid Studio 3.1が正式版になった。が、3.0から3.1への更新はそんなに大きく変更しなくても済むので、基本は3.0へのマイグレーションガイドに書いてあることに対応すればよいはず。
まず始めにGradle(Gradle wrapper)のバージョン更新を行う。Android Studio3.0は最低でもGradle4.1以上が必要。(3.1はGradle4.4以上)
gradle wrapperの更新はgradle-wrapper.propertiesファイルを書き換えてgradle syncを行うだけ。
私は./gradlew wrapper --gradle-version=<Gradleのバージョン>コマンドで更新している。これで更新したらシェルスクリプトの更新もかかる場合があるので、こっちのほうがいいのかなという雰囲気で選んでいるだけだったりする。
コマンドで更新した場合、distributionUrlで指定されるgradle wrapperが4.x-bin.zipといった感じでバイナリのみのものになる。Android Studioはallにしとけと変更を促してきて、結局手書きでdistributionUrlを書き換えることになるので、普通にマイグレーションドキュメントにあるようにgradle-wrapper.propertiesを書き換えるだけでいいと思う。
ちなみに利用するgradleのバージョンだが、私の場合Twitterで見かけたGradleのリリースツイートを見て覚えてるバージョンを利用している。今だと4.6。
使えるバージョンはhttps://gradle.org/releases/で確認できる。基本的には最新使っとけばいいんじゃないだろうか。
プロジェクトで利用しているgradle pluginのバージョンが古い場合、原因がよくわからないエラーが多発する。例えばNo signature of method: com.android.build.gradle.internal.scope.VariantScopeImpl.getGenerateRClassTask() is applicable for argument types: () values: []とか。こういうのは利用しているgradle pluginを最新のバージョンに更新すると解消されることが多かった。
基本的にAndroid Studioが利用しているプラグインの新しいバージョンがあれば教えてくれるはず(網掛けになって新しいバージョンがあることを示唆してくれる)ので、それに従ってgradle pluginのバージョンを上げてみると解決する場合が多いだろう。
エラーメッセージで検索してもこれといった解決策が見つからないという場合には、利用しているプラグインのバージョンを上げることを試してみよう。
昔はライブラリプロジェクトを利用している場合、publishNonDefault trueを設定して、アプリケーションモジュールをdebugビルドするときは、ライブラリプロジェクトもdebugビルドにするなんて指定をしている人もいただろう。
この設定をしている場合、Android Studio3.xにアップデートするにあたってちょっとした手直しが必要になる。
Android Studio3.0からはライブラリモジュールのビルド設定は、利用するアプリケーションモジュールのものと同じものが利用されるようになった。appモジュールでdebugビルドを選んだら、自動的に依存しているライブラリモジュールもdebugビルドでビルドされる。
そのため以下の手直しが必要。
Android Wearアプリ(WatchFaceも)をGoogle Playで公開するときにbuild.gradleの共通化をやっておいた方がいいと思います。
Android Wearアプリプロジェクトを作成すると、標準ではmobileモジュールとwearモジュールが作成され、それぞれのモジュールにbuild.gradleが作成されます。
Google Playにアプリを公開する場合、build.gradleで指定するversionCodeとversionNameはmobile,wearモジュールで共通にしなければなりません。
初回アップロード時は両方同じ値なので問題ありませんが、アプリをバージョンアップする際に2つのファイルをいじらないといけないのは面倒くさいと思います。(というか絶対に忘れる)
そのためversionCodeなどは、一箇所直せばmobileとwearのどちらにも適用されるようにしてやるといいと思います。
私はmobile,wearのbuild.gradleで共通して利用する部分を、別ファイルにして読み込ませるようにしてみました。
QiitaのAndroidの署名情報(signingConfigs)を外出しようを参考にさせていただきました。
/mobile/buildConfig.gradle
defaultConfig {
applicationId "jp.gcreate.product.customphotowatch"
minSdkVersion 18
targetSdkVersion 21
versionCode 3
versionName "1.0.2"
}
/mobile/build.gradle
apply plugin: "com.android.application"
android {
apply from: "configBuild.gradle", to: android
compileSdkVersion 21
buildToolsVersion "21.1.2"
}
〜dependenciesは省略
/wear/build.gradle
apply plugin: "com.android.application"
android {
apply from: "../mobile/configBuild.gradle", to: android
compileSdkVersion 21
buildToolsVersion "21.1.2"
defaultConfig{
minSdkVersion 20
}
}
〜dependenciesは省略
上記では省略しましたが、buildTypeも外部ファイルに出して両者で同じ設定が適用されるようにしてます。
やってて未だに不安なのが、ちゃんと正しく設定できているのか、確認の仕方がいまいち分からず不安だということでしょうか・・・。
先日のDroidKaigiで発表のあった、つかえるGradleプロジェクトの作り方のやり方も参考になります。
こちらのスライドでの方法は、versionCode等の値を/build.gradleで定義し、各々のプロジェクトその値を参照することで共通化するやり方です。
こちらのやり方のほうが分かりやすいなぁって発表聞いてて思いました。
ちなみにAndroid Studioではルートのことをプロジェクト、mobileとかwearのことをモジュールと呼びますが、Gradleの世界ではどれもプロジェクトと呼ぶそうです。勉強になりました。
Android Studioは、Gradleが便利だとよく言われています。
私がAndroid Studioを使い始めた頃は「Gradle便利っていうけど、どう便利なんだろう」とさっぱり分かりませんでした。むしろGradleが何をやっているか、何者なのかさっぱり分からず、逆によく分からない存在でした。(でしたというか、現在進行形でよく分かっていませんが・・・)
実際にGradleが便利というのが実感できたのは、外部ライブラリを簡単に取り込めることが分かってからです。
Androidではアプリ開発に便利なライブラリが多数公開されています。
自分で1から作るより、すでにある便利なライブラリのお世話になった方が、アプリ開発スピードも早くなりますしクオリティも高くなります。
Android Studioではそういったライブラリを、build.gradleに1行記述するという簡単な方法で自分のプロジェクトに取り込むことができます。
デフォルトではbuild.gradleは2つあるのですが、いじるのはプロジェクト直下にあるものではなく、appディレクトリにあるbuild.gradleです。

例えばcroutonというToastをカスタマイズして使える便利ライブラリを取り込む場合は、app/build.gradleのdependanciesにcompile 'de.keyboardsurfer.android.widget:crouton:1.8.4'と記述をするだけで取り込めます。
app/build.gradle
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:19.+'
compile 'de.keyboardsurfer.android.widget:crouton:1.8.4'
}
Sync Project with Gradle Filesを実行すると、External Librariesに目的のライブラリが取り込まれます。

あらゆるライブラリがこの方法で使えるとは限りませんが、非常に便利です。
アプリ開発の始めの頃は外部ライブラリを利用するなんて発想がなかったものですから、Gradleが便利だぞと言われてもなんのこっちゃとさっぱり理解できませんでした。しかし、実際にこうやってライブラリが簡単に取り込めるのを確認すると、「なるほど、こりゃ便利だわ」とAndroid Studioを使うのが楽しくなってきました。