Androidアプリのマルチモジュールでlintチェック結果をまとめてDangerでコメントする
Androidアプリ開発でマルチモジュールによる開発をすすめていた場合、Lintを実行すると各モジュールのbuildフォルダ配下にlintチェック結果のファイルが生成されます。もの挙動自体は正しくまったく問題はないのですが、GitHubを利用していてプルリクにDanger等でlintチェックの結果をコメントしていた場合、アプリケーションモジュールの結果のみがコメントされたり、特定のモジュールの結果のみがコメントされてしまいます。多くの場合、マルチモジュール開発でのモジュールは同じリポジトリ内に配置されていると思うので、これはなんとかして解決したいです。
解決の選択肢
この問題を解決する選択肢はいくつかある。
選択肢1
モジュール毎にGitHubのリポジトリを作成することで、そもそもプルリクがモジュール毎に行われるため、問題が発生しないという考え方です。ただ、自分の開発している環境ではモジュールが20近くあるのでこれは現実的じゃないかなと。また、Androidアプリ開発のマルチモジュールでモジュール毎にリポジトリを切る運用はあまりみたことがない。なぜなら、各モジュールがコアなモジュールに依存していたりとモジュール間の依存が結構発生するのと、リファクタリング時に他のモジュールのリファクタリングが走らなくなるので、モジュール毎にちゃんとライブラリ的な管理が必要になるからとかいろいろある。とりあえずこの選択肢はなし。
選択肢2
モジュールの数だけDangerを実行する場合は、それぞれDangerfileを作成して結果のXMLファイルそれぞれ指定してあげる必要があります。これはモジュールが増えるたび、Dangerfileの作成とCIの設定が必要になるのでやりたくない。また、ひとつのプルリクに対して複数のDangerを実行するとコメントが最後の1回で上書きされるか、ちゃんと設定したとしても実行した回数分コメントがされるので、見た目的にもちょっと嫌だ。GitHubとSlackを連携していたりすると大量に通知されることになったりもする。この選択もなし。
選択肢3
linkチェックで生成される結果のファイルを1つのファイルとしてまとめ、1度だけDangerを実行する。これは、マルチモジュールじゃなかった場合のDangerの利用と変わらない結果になるため理想。やりたいことがやれる感じがするので、この選択肢の実現を目指す。
対応
lintの設定
lintチェック結果のXMLファイルが、指定した場所に、指定した名前、で生成されるよう build.gradle
で設定しておきます。この設定は、モジュール単位で設定してあげる必要がある ので、androidブロックを共通化させているファイル等で設定してあげると良さそうです。
android { lintOptions { xmlReport true xmlOutput rootProject.file("./build-reports/lint-results-${project.getDisplayName()}.xml") } }
書き出されるファイル名がモジュール毎に変わるようにすることで、結果のファイルが上書きされるのを防いでいます。今回の例でモジュールが :modules:feature:home
という階層にあった場合、lint-results-project ':modules:feature:home'.xml
という名前でファイルが書き出されます。
この設定で ./gradlew lint
等でlintのGradleタスクを実行してあげると結果のファイルが ./build-reports
配下に生成されます。
lintチェック結果のXMLファイルをマージする
android_lintで結果をプルリクにコメントするために、結果のXMLファイルをマージして1つのXMLファイルを作ります。結果のXMLファイルは以下のような中身になっています。
<?xml version="1.0" encoding="UTF-8"?> <issues format="5" by="lint 3.4.2"> <issue id="…" severity="Warning" message="…" category="Lint" priority="…" summary="…" explanation="…"> … </issue> </issues>
なので全ての <issue>
を <issues>
にまとめてあげれば良さそう。ということで以下のようなRubyスクリプトを作成した。
require 'nokogiri' Dir.chdir('build-reports') new_doc = nil Dir::glob('lint-results-*.xml') do |item| file = File.new(item) if new_doc.nil? new_doc = Nokogiri.XML(file) else doc = Nokogiri.XML(file) issues = doc.search('issue') new_doc.at('issues').add_child(issues) end end File.open('lint-results.xml', 'w') do |file| unless new_doc.nil? file.puts(new_doc.to_xml(indent: 4)) end end
1つ目に読み込んだファイルをベースに、他のファイルの <issue>
を <issues>
にまとめて、lint-results.xml
というファイルで出力します。実行にはnoogiriが必要なので予め gem install nokogiri
しておきます。これをプロジェクトのrootディレクトリで実行すると結果が1つにマージされたXMLファイルがbuild-reportsに作成されています。
Dangerでファイルを指定する
あとは Dangerfile
で生成されたXMLファイルを指定してあげるだけ。
android_lint.skip_gradle_task = true android_lint.filtering = false android_lint.report_file = "build-reports/lint-results.xml" android_lint.lint(inline_mode: true)
ここまでの一連の流れをCIで実行してあげる。
Bitriseのワークフローを設定する
Bitriseでは以下の流れのワークフローを作成します。
ちなみに、Rubyスクリプトを実行するためには bitrise-steplib/steps-ruby-script を選択しています。理由はGemfileのサポートがあり、Bitriseで直接Rubyをかけるからです。Bitriseの設定を変えてビルドを実行するために毎回コミットをするのがつらくて一旦こういう形にしています。安定したらスクリプトファイルをGitの管理化に配置する予定です。Rubyスクリプトの設定は以下のようになっています。
また、忘れずに DANGER_GITHUB_API_TOKEN
をSecretsに設定しておきます。
あとはTriggersのPULL REQUESTに作成したワークフローを設定しておきます。
danger-android_lintの実行結果
プルリクエスト時にBitrise上でDangerが実行されると以下のようにlintチェック結果をコメントしてくれます。