PHONE APPLI Engineer blog

エンジニアブログ

SwiftLint・Danger・Bitriseを使ってGitHub上でPRチェックの結果を表示させてみた

おはようございます☀️ PHONE APPLIでiOSエンジニアとしてインターンを行っている西田と言います。チーム内ではにっしーというあだ名で呼ばれています。以前はアプリ開発がメインだったのですが最近はCI/CDなどを使った環境構築を担当させていただく場面が増えました。そこでこの記事では私の行った環境構築の1つをご紹介します。

さて、この記事ではGitHub上にPRのチェックを自動で表示させる機能を実装するまでに行ったことを紹介します。似たような趣旨の記事は多少ありますが、「SwiftLint・Danger・Bitrise」を使ってPRチェックを行う日本語の記事は見受けられなかったので、今後このようなことがしたいという方のためにこの記事を作成します。今回のプロジェクトではBitriseを用いてDangerを走らせ、そのDanger上でSwiftLintをを実行させるという手法を取ります。

目次

SwiftLint

SwiftLintとは

SwiftLintとはSwift用の静的解析ツールです。静的解析とはコードの検証をビルド前から行うことです。コードの検証ではソースコードを解析して特定のコーディングパターン(コーディングルール)に違反している箇所を指摘します。Xcodeにも強力な静的解析機能が付いていますが、SwiftLintを活用することでより多くの静的解析を行うことが可能なり、拡張性も高いため今回採用しました。

SwiftLint Readme

SwiftLint ルール一覧

SwiftLint導入・設定方法

SwiftLintの導入・設定方法は色々な方が詳しい方法を記事になさっているので、今回は割愛させていただきます。

Danger

Dangerとは

DangerはCI/CD上で実行されるコードレビューに関わる自動化ツールです。Dangerを使うことで今までは人の手で行っていたコードレビューの負担を軽減することができ、開発効率向上につながります。Dangerは2021年7月現在、JavaScript/Swift/Ruby版が配布されており、それぞれの言語で記述することが可能です。Kotlin/Python版は開発中だそうです。また、Dangerのコードレビュー結果をGitHub上にコメントすることができます。

Danger Readme

Danger ドキュメント

Danger導入・設定方法

DangerはJavaScriptやSwift版が配布されていますが、今回はRuby版を採用しました。理由としては以下の2つです。

  1. Ruby版がDangerのオリジナルであり、多くの記事がオンライン上に存在しているため。
  2. Ruby版はAndroid上でも動作し、今回のプロジェクトはAndroidiOSでほぼ同一の機能を実装するため。
Dangerの導入

bundleを用いてDangerをインストールします。 まずターミナルでプロジェクトファイル内に移動し以下のコードを実行します。これによってプロジェクトファイル内にGemfileを生成します。

rbenv exec bundler init

次にGemfileを開き以下の文を記述します。

gem 'danger'
gem 'danger-swiftlint'

ターミナルで以下の文を走らせてDangerをインストールします。

bundle install

最後にターミナルで以下の文を実行し、Dangerfileをプロジェクトファイル内に生成します。

danger init
Dangerfileの設定

DangerfileはDangerがどのような項目をレビューするのかを記述するファイルです。今回導入したDangerはRuby版ですので、Ruby言語でDangerfileを記述していくことになります。以下は今回のプロジェクトで実際に実装したコードになります。(一部変更)

Danger上でFastlaneを動かしGitHubにコメントするコード
swiftlint.config_file = '.swiftlint.yml'
swiftlint.binary_path = 'Pods/SwiftLint/swiftlint'
swiftlint.lint_all_files = false
swiftlint.lint_files
GitHubのPRが形式通りに記述されているかをレビューするコード
is_to_master = github.branch_for_base == 'master'
is_to_develop = github.branch_for_base == 'develop'
is_written_what_is_this_pr_do_title = github.pr_body.match(/^(?=.*What's this PR do \?)/)
is_written_what_is_this_pr_do_example = github.pr_body.match(/概要は必ず記入してください。/)
is_written_how_to_check_title = github.pr_body.match(/^(?=.*How to check)/)
is_written_how_to_check_example = github.pr_body.match(/チェック項目は必ず記入してください。/)
is_written_related_ticket_title = github.pr_body.match(/^(?=.*Related Tickets)/)
has_danger_changed = git.diff_for_file("Dangerfile")
has_podfile_changed = git.diff_for_file("Podfile")
has_podfile_lock_changed = git.diff_for_file("Podfile.lock")
is_set_ticket_url = github.pr_body.match(/hogehoge.backlog.jp\/view\/(Hogehoge_IPHONE_APP|Hoge_TEAM|Hoge_PRDCT)-[0-9]{1,}/)
is_set_ticket_title = !!github.pr_title.match(/(Hogehoge_IPHONE_APP|Hoge_TEAM|Hoge_PRDCT)-[0-9]{1,}/)
is_from_release_main = !!github.branch_for_head.match(/release\/([A-Z]{,3}_)*v[0-9]+\.[0-9][0-9]+\.[0-9]\/main/)
is_from_hotfix_main = !!github.branch_for_head.match(/hotfix\/main/)
is_from_project_main = !!github.branch_for_head.match(/feature\/.*\/main/)
is_from_feature = !!github.branch_for_head.match(/feature\/(Hogehoge_IPHONE_APP|Hoge_TEAM|Hoge_PRDCT)-[0-9]{1,}/)
is_from_master = !!github.branch_for_head.match(/master/)


# マージ先ブランチが正しくなかった時にエラー
if is_to_master && (!is_from_release_main || !is_from_hotfix_main)
    fail("以下のブランチ以外からはmasterへマージできません。
            - `release/xxxx/main`
            - `hotfix/main`")
elsif is_to_develop && (!is_from_release_main || !is_from_project_main || !is_from_feature || !is_from_master)
    fail("以下のブランチ以外からはdevelopへマージできません。
            - `release/xxxx/main`
            - `feature/xxx/main`
            - `feature/チケット番号`
            - `master`")
end

# コードの変更量が多かった時に警告
warn("PRの変更量が多すぎます。
    可能であればPRを分割してください。") if git.lines_of_code > 500

# ラベルが設定されていなかった時に警告
warn("このPRにはラベルが設定されていません。
    ラベルを1つ以上付けてください。") if github.pr_labels.empty?

# Dangerfileが変更された時に警告
if has_danger_changed
    warn("`Dangerfile`が変更されました。
    変更内容を確認してください。")
end

# Podfileが変更された時に警告
if has_podfile_changed || has_podfile_lock_changed
    warn("`Podfile`もしくは`Podfile.lock`が変更されました。
        変更内容を確認してください。") 
end

# PRボディにチケットが登録されていなければ警告
warn "担当したチケットURLをPRに記載してください。" unless is_set_ticket_url

# Related Ticketが記載されていなければ警告
warn("`Related Tickets`が未記入です。
    チケット情報を記載してください。") unless is_written_related_ticket_title

# How to checkがデフォルトのまま or そもそもなかったら警告
if !is_written_how_to_check_title || is_written_how_to_check_example
    warn("`How to check`が未記入もしくは`Check List`に例文が記入されているままです。
    動作確認手順をPRに記載してください。")
end

# what is this pr doがデフォルトのまま or そもそもなかったらエラー
if !is_written_what_is_this_pr_do_title || is_written_what_is_this_pr_do_example
    fail("`What's this PR do ?`が未記入もしくは例文が記入されているままです。
        何を行ったかをPRに記載してください。")
end

# PRタイトルをチェックし、何もチケットが含まれていなかったら警告
warn "PRタイトルにはチケットタイトルを含めてください。" unless is_set_ticket_title

Bitrise

Bitriseとは

Bitriseはモバイルアプリ開発におけるCI/CDサービスです。CI/CDとは、ソフトウェア開発などの業務をより正確に、より効率良く進めるための手法です。CI/CDを使うことでソフトウェアの状態や変更を常時自動でテストを行い、本番環境にリリース可能な状態にすることができます。また、バグを素早く発見したり、ソフトウェアに加えた変更を自動でリリースしたりできるようになります。

Bitriseの導入や初期設定方法などは他の記事、もしくは公式ドキュメントをご覧ください。

DangerをBitrise上で走らせるための設定

ワークフローの設定

ワークフローの最後の方にスクリプトブロックを設置し、以下のコードを記述します。

set -ex
bundle install

gem install danger
bundle exec danger

環境変数の設定

次にワークフローエディター(Workflow Editor)内のSecrets内にてGitHubのPersonal access tokensを設定します。Personal access tokensはこのGitHubのドキュメントを参考に発行してください。

発行したトークンをSecrets内にキーをDANGER_GITHUB_API_TOKENとして以下のように追加します。

トリガー(Triggers)の設定

最後にワークフローエディター(Workflow Editor)内のトリガー(Triggers)のプルリクエスト(Pull Request)にて先ほど設定したワークフローをどのタイミングで走らせるかを設定します。

GitHub上の表示

正しくBitrise・Danger・SwiftLintの設定ができた場合、GitHubのPR上に以下のようにDangerにて生成されたメッセージが投稿されます。

まとめ

今回はBitrise・Danger・SwiftLintを使い、PRチェックを自動化する方法を紹介しました!PRチェックを自動化することで時短になり、チェック項目の見落としもなくなります。開発環境の自動化はメリットが大きいので今後も様々な工程において自動化を進めていきたいと思います!!


PHONE APPLIについて

phoneappli.net
phoneappli.net