Dangerでプルリクエストのチェックを自動化する
こんにちは。
リサーチ・アンド・イノベーションでAndroidエンジニアをしている jageishi です。
今回は、弊社で利用している Danger の簡単な説明と導入事例を紹介します。
Dangerとは
Dangerはプルリクエストの形式化や機械的なチェックを自動化するのに使える便利なツールです。
以下のような特徴があります。
- Dangerfileにルールを記述する
- ルールはプラグインとして公開されているものがある
- 様々なコードホスティングサービスをサポートしている
- Ruby版の他にJavaScript、Kotlin版等が存在する
Dangerの導入
今回はRuby版を使ってGitHub ActionsのワークフローでDangerを実行して、プルリクエストにコメントする方法を例に説明します。
Gemfile
公式からもBundlerの利用が推奨されているため、Gemfileを用意します。
# frozen_string_literal: true source "https://rubygems.org" gem "danger"
Dangerfile
次に、Dangerfileをプロジェクト直下のディレクトリに作成します。
message("メッセージ")
今回はメッセージをコメントするだけですが、他にも様々な機能が用意されているため細かいチェックを行うことが可能です。
GitHub Actions
pull_request
イベントをトリガーとして実行されるワークフローを作成します。
name: Danger on: pull_request: jobs: run: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: '3.0' bundler-cache: true - run: bundle exec danger env: DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Rubyを使用するために ruby/setup-ruby を利用しています。 こちらのアクションはデフォルトでBundlerもインストールされるため、別途gem install bundler
を実行する必要はありません。
また、bundler-cache: true
を設定することで併せて bundle install
が実行され、gemがキャッシュされるようになります。
最後に環境変数 DANGER_GITHUB_API_TOKEN
にアクセストークンを設定して bundle exec danger
を実行すればOKです。
プルリクエスト
以上の設定を行うことで、プルリクエストの作成時に以下のようにコメントされるようになります。
このように導入のハードルが低く、手軽に試すことができます。
弊社の導入事例
ここからは弊社での導入事例を紹介します。
CODEのAndroidアプリ開発で利用しているDangerfileの構成は以下の通りです。
danger.import_plugin("danger/plugins/*.rb") # Android Lint android_lint.gradle_task = "app:lintFlavorDebug" android_lint.report_file = "app/build/reports/lint-results-flavorDebug.xml" android_lint.filtering = true android_lint.lint # 画面密度毎に画像リソースが存在するか確認する image_resource_checker.check # Roomマイグレーションの有無を確認する schema_changes_checker.check
かなりシンプルな内容ですが、以下のような流れになっています。
1. プラグインの読み込み
danger.import_plugin("danger/plugins/*.rb")
Dangerはプラグインとしてルールを配布できますが、私達はDangerfileの肥大化を防ぐ目的でローカルにプラグインを作成してファイルを分離しています。
こちらはそれらのプラグインを読み込んでDangerfile内で参照できるようにするものです。
2. Android Lintの実行
android_lint.gradle_task = "app:lintFlavorDebug" android_lint.report_file = "app/build/reports/lint-results-flavorDebug.xml" android_lint.filtering = true android_lint.lint
Android Lintの実行には loadsmart/danger-android_lint を利用しています。Androidアプリ開発者にはおなじみかもしれません。
Android版CODEはマルチモジュール構成になっているため、checkDependencies true
を設定することで全てのモジュールに対してチェックが行われるようにしています。
android { lintOptions { checkDependencies true } }
前もって各モジュールに対してLintを実行しておいて、出力された複数のレポートを処理する方法もよく見かけますね。
3. 画面密度毎の画像リソースのチェック
image_resource_checker.check
module Danger class ImageResourceChecker < Plugin attr_accessor :target_densities, :target_extensions def target_densities return @target_densities || ["mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"] end def target_extensions return @target_extensions || ["png", "jpg"] end def check renamed_files = git.renamed_files target_files = git.added_files \ + git.modified_files \ + renamed_files.map { |f| f[:after] } \ - git.deleted_files \ - renamed_files.map { |f| f[:before] } regex = /drawable-(#{target_densities.join("|") })\/.*\.(#{target_extensions.join("|")})$/ target_image_files = target_files.filter { |f| regex.match?(f) } checked_files = [] missing_density_files = [] target_image_files.each do |f| next if checked_files.include?(f) target_densities.each do |d| image_file = f.gsub(/drawable-(#{target_densities.join("|")})/, "drawable-#{d}") missing_density_files << image_file unless File.exists?(image_file) checked_files << image_file end end unless missing_density_files.empty? header = "### 画像ファイルを追加してください:pray:\n" header << "| ファイル |\n" header << "| --- |\n" message = missing_density_files.map { |f| "| `#{f}` |\n" }.join markdown(header + message) end end end end
こちらでは画像リソースに変更が加えられた際に、画面密度毎に用意されているかをチェックしています。
Android LintにIconDensitiesというissue idがあり、まさに同じようなチェックを行ってくれるのですが、以下の理由から自作することにしました。
- ディレクトリ毎に検出されるためdanger-android_lintで
android_lint.filtering = true
を指定して対象を追加、および変更されたファイルに絞っている場合はプルリクエストにコメントされない - xxxhdpiがチェック対象外になっている
4. Roomマイグレーション有無のチェック
schema_changes_checker.check
module Danger class SchemaChangesChecker < Plugin def check schema_json_path = "<Your schema location>" has_changed_schema = (git.added_files + git.modified_files).filter { |f| f.start_with?(schema_json_path) }.any? if has_changed_schema markdown_text = "### マイグレーションが必要です\n" markdown_text << "- [ ] マイグレーション処理を実装済み\n" markdown_text << "- [ ] Google Play公開バージョンからのマイグレーションをテスト済み\n" markdown(markdown_text) end end end end
弊社ではRoomを利用しており、スキーマファイルを出力してリポジトリに含める運用になっています。
こちらはスキーマファイルに変更が加えられた際に、マイグレーションが必要である旨をコメントして確認を促すようなものです。
スキーマが変更されたもののマイグレーション処理が抜けた状態でメインのブランチにマージされてしまい、アプリをアップデートした際にエラーが発生してしまう状態になっているのをリリース前のテストまで気づけなかったことがありました。 そういった経緯もあり、対応漏れを未然に防ぐためにチェックを行っています。
出力内容
出力は以下のようになります。
ちなみに、同じプルリクエストでDangerが再度実行された場合は、すでにDangerによって投稿されたコメントが更新されるような挙動になっており、プルリクエストがコメントで溢れかえってしまうようなことはありません。
こういった部分にも配慮されているため、Dangerはとても使い勝手の良いツールだと思います。
まとめ
Dangerと弊社での導入事例について紹介しました。
チェックが自動化されることで、問題の早期発展に繋がったり、コードレビューの負担を減らせていると感じています。
導入済みの方も多いと思いますが、まだ導入していないという方は是非お試しください。その際にこの記事が少しでも参考になれば幸いです!
便利な機能は他にもたくさんありますので、詳しくは 公式ホームページ のガイドやリファレンスをご覧ください。
リサーチ・アンド・イノベーションではエンジニアを募集中です。
興味を持っていただけた方はお気軽にご連絡ください!