SwiftLintをGithubActionsのmacOSではなくubuntuで実行する
この記事は、Classi developers Advent Calendar 2021 19日目の記事です。
はじめに
Classi プロダクト開発部 iOSエンジニアをしています。
先日、会社の開発者ブログで iOSDC2021 に関する記事を執筆させて頂きました。
iOSDC2021でアンケートを取らせて頂いたので結果を公開します! - Classi開発者ブログ
この記事の中では、Danger の導入を検討すると書かせて頂いてます。
実際に、Danger を導入した際に得られた知見を記事にしようと考えました。
この記事で書かせて頂いたのは、表題にもある通り
SwiftLint を Github Actions の macOS ではなく ubuntu で実行する
です。
Danger に関する記事というよりは、Github Actions の知見が主な内容になります。
やりたかったこと
プルリクエストを作成した時に SwiftLint を走らせ、変更されたファイルに対して、警告が出たら自動でコメントする。
これは、Danger で実現できます。
この Danger を Github Actions で実行するにあたり、コストがかかる macOS ではなく ubuntu で実行したい。
というのがやりたかったことになります。
DangerをGithubActionsのmacOSではなくubuntuで実行するということについて
これについて、少しづつ分解して、説明させてください。
DangerをGithubActionsで実行することについて
Danger を導入するにあたり、Github のプルリクエストへのアクセスが必要になります、この点で言うと、Github の Token が取得しやすい Github Actions が適しています。
そのため、Danger を Github Actions で実行しようと考えました。
Github ActionsのmacOSで実行することについて
Github Actions は、他のCIツールと比べて導入しやすくコストも低めです。
しかしながら、macOS は ubuntu の約10倍のコストがかかります。
GitHub Actionsの支払いについて - GitHub Docs
Danger は、プルリクエストの度に実行されるので、コスト面は無視できません。
なので、ubuntu で実行できるなら、その方がいいです。
Dangerをubuntuで実行することについて
実は、Danger には、Swift製のDanger-Swift という Danger を Swift で書けるというものがあります。
これを使えば、比較的簡単に、やろうとしていることは実行できるのですが、せっかくなのでこれを使わずに
オリジナルのRuby製のDangerを使って、GithubActions の Ubuntu の環境で SwiftLint を実行する
というニッチな導入方法を試してみました。
もし、このような情報を必要としている方がいて、役立つ情報となれば嬉しいです。
なぜ Danger-Swift を使わなかったのか
「なぜ、Danger-Swift を使わなかったのか」と、疑問に思う方もいるかと思います。
これを使わずに、わざわざ、構築したのは、運用上、以下のメリットがあると考えました。
- サーバーサイドは Ruby なので、Ruby なので、Danger の設定ファイルが共有できそう
- iOS/Android で、Danger の設定ファイルが共有できそう
ただ、これらについては、実際は、あまり大きなメリットはなさそうだというのは、構築してて感じました。
いくつかのルールは、共有しやすくても、結局、各プロジェクトの構造特有の記述が増えそうなので、上記のメリットを期待して、わざわざ、手間をかけて構築するのは、割りに合わないと思います。
手っ取り早く構築したいなら、「Danger-Swift」をご検討ください。
上記の点にメリットがないと感じるのであれば、何がモチベーションになっているかというと
Danger-Swift には、以下のように運用で問題になりそうな点がありそうだと、いろいろ調べて分かりました。
- Danger-Swift 固有のバグがいくつかある
- 少し機能がダウングレードする
Danger-Swift は、内部的には、Danger の Ruby 版ではなく、JS版が使われているそうです。
なので、Ruby版にはない不具合だったり、足りない機能があったりするようです。
詳細は、調べられていないので、この件については、割愛させてください。
Swift製CLIツールを ubuntu で実行するということについて
前述の通り、macOS で実行するとコストがかかるので、SwifltLint など ubuntu で動作させられるツールについては、コストを抑えた運用ができるでしょう。
この記事の方法であれば、SwifltLint だけでなく、SwiftFormat などのSwift製CLIツールをインストールでき、バージョンも任意のバージョンを指定できるので、比較的、柔軟に運用できるようになるかと思います。
Doker イメージを用意したり、Github Actions のプラグインを作成するなどの方法もあるかと思いますが、Github Actions のymlファイルを見れば、一目瞭然なので、こちらの方が運用しやすいのかなと思います。
失われるメリットについて
utunbu にすることによって、失われるメリットもあります。
例えば、以下の記事のように、Xcode のコンパイラやリンカの警告を Danger で拾いたい場合、xcodebuild は、macOS でしか使えないので、出来ないかもしれません。
Xcodeのコンパイラやリンカの警告をDangerで指摘するプラグインを作っていた - しおメモ
ただ、Github Actions には、異なる環境で実行できるような仕組みがあるようなので、これを活用すれば、うまく構築出来るかもしれません。
Building and testing Swift - GitHub Docs
この記事を読むとどうなるの?
以上のように、上記で触れられている通り、Danger については詳しく書いていません、この記事を読むことで何が得られるのかについて、まとめてみました。
この記事を読んで得られること
- GithubActions で SwiftLint を使う方法
- GithubActions で Danger を使う方法
- GithubActions で Swift Package Manager を使ってCLIツールのインストールおよびキャッシュをする方法
- GithubActions の ubuntu で Swift (Swift製CLIツール) を実行する方法
- GithubActions の macOS を ubuntu に変えてみたときに必要な情報
この記事を読んでも得られないこと
- SwfitLintの詳しい使い方
- Dangerの詳しい使い方
Danger 自体や SwiftLint の活用事例などは、今後、実際に運用をして、よい知見が得られたら、Qiita もしくは、会社のブログで、書かせて頂ければと思います。
Danger を Github Actions の macOS で導入してみる
前置きがかなり長くなってしまいましたが、さっそく、表題の内容に入っていきたいと思います。
まずは、ubuntu で導入する前に、Github Actions に Danger を導入する方法について紹介し、そのあと、どう変更すればいいかを比較できるようにしたいと思います。
まずは、通常通りに、macOS で実行する方法です。
つまずいたポイントを説明しつつ、話を進めます。
導入手順
1. Gemfile にインストールする gem を記述
gem 'danger'
gem 'danger-swiftlint'
2. Gem をインストール
% bundle install
3. Dangerfile の作成
% touch Dangerfile
Dangerfile の記述例
# Ignore inline messages which lay outside a diff's range
# github.dismiss_out_of_range_messages
# Check PR
warn("PRの説明が短すぎるよ!レビュアーが見て分かる説明を書いてね! :cry:") if github.pr_body.length < 100
# Warn when there is a big PR
warn("PRのサイズが大きすぎるよ!可能であれば分割してね! :cry:") if git.lines_of_code > 1000
# Check and comment on swiftlint only in the range corrected by PR
swiftlint.verbose = true
swiftlint.config_file = '.swiftlint.yml'
swiftlint.lint_files inline_mode: true
ローカルで確認したい場合、任意のプルリクエストに対して、以下のコマンドで確認できる
% DANGER_GITHUB_API_TOKEN={Githubの個人アクセストークン} bundle exec danger pr {チェックしたいプルリクエストのURL}
// 既にマージ済のブランチの場合
% DANGER_GITHUB_API_TOKEN={Githubの個人アクセストークン} bundle exec danger local --use-merged-pr=[プルリクエストのid]
上記のようなコマンドで、動作を確認できます。 このコマンドで実行しても、実際にはプルリクエストへは、反映されないのでご安心ください。
5. Github Action 用の yml を作成
% touch .github/workflows/danger.yml
danger.yml の記述
name: Danger Swift
on:
pull_request:
branches:
- main
permissions:
contents: read
pull-requests: write
issues: write
statuses: write
jobs:
danger:
runs-on: macos-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v2
- name: Cache Gems
uses: actions/cache@v2
env:
cache-name: gems
with:
path: vendor/bundle
key: v1-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('Gemfile.lock') }}
restore-keys: |
v1-${{ runner.os }}-${{ env.cache-name }}-
- name: Install Gems
env:
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
run: |
bundle config set path 'vendor/bundle' ; bundle config set clean 'true'
bundle check || bundle install
- name: Run Danger
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bundle exec danger
ハマったポイント
Danger を Github Actions で実行するさせる際に、ハマったポイントがあるので、ご紹介します。
Github Actions で Danger を実行するにはパーミッションが必要
Danger を Github Actions で動かすには、Github のリポジトリや、プルリクエストが参照できる権限がないといけません。
permissions:
contents: read
pull-requests: write
issues: write
statuses: write
既に Danger を Github Actions で動かす記事については、多くの人がブログ記事で書いてくれていますが、ほとんどの記事で、パーミッションの追加がされていませんでした。
このため、Danger コマンドが失敗したので、いろいろ調べていましたが、結局はパーミッションが問題でした。
ちゃんと、エラーを読んでいれば、分かることでもあったんですが、無駄にハマってしまいました。
エラーと公式ドキュメントは、ちゃんと読みましょう。。
こちらが参考になりました。
Github Actionsの使い方メモ - Qiita
Github Actions の公式ドキュメント
Automatic token authentication - GitHub Docs
Danger を Github Actions の ubuntu で動かせるようにしてみる
今度は、ubuntu で動かせるようにしてみましょう。
実際に変更したファイルは、こちらになります。
danger.ymlの記述
name: Danger Swift
on:
pull_request:
branches:
- main
permissions:
contents: read
pull-requests: write
issues: write
statuses: write
jobs:
danger:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Swift
uses: fwal/setup-swift@v1
- name: Get Swift Version
id: get-swift-version
run: |
echo "::set-output name=swift-version::$(swift --version | head -1 | sed 's/^.*Swift version \([0-9]\.[0-9]\.[0-9]\) (.*)$/\1/')"
shell: bash
- name: Get Swift Version
run: swift --version
- name: Setup Ruby
uses: ruby/setup-ruby@v1
- name: Cache Gems
uses: actions/cache@v2
env:
cache-name: gems
with:
path: vendor/bundle
key: v1-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('Gemfile.lock') }}
restore-keys: |
v1-${{ runner.os }}-${{ env.cache-name }}-
- name: Install Gems
env:
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
run: |
bundle config set path 'vendor/bundle' ; bundle config set clean 'true'
bundle check || bundle install
- name: Cache DangerTools
uses: actions/cache@v2
env:
cache-name: danger-tools
with:
path: DangerTools
key: v1-${{ runner.os }}-${{ env.cache-name }}-${{ steps.get-swift-version.outputs.swift-version }}-${{ hashFiles('DangerTools/Package.resolved') }}
restore-keys: |
v1-${{ runner.os }}-${{ env.cache-name }}-${{ steps.get-swift-version.outputs.swift-version }}-
- name: Build DangerTools
run: |
./DangerTools/.build/release/swiftlint --version || swift run -c release --package-path DangerTools --build-path DangerTools/.build swiftlint --version
sudo cp ./DangerTools/.build/release/swiftlint /usr/bin
- name: Run Danger
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bundle exec danger
解説
では、解説します
danger.yml について
まずは、danger.yml
から
Swiftをインストールする
- name: Setup Swift
uses: fwal/setup-swift@v1
ubuntu で swift を使うために、fwal/setup-swiftを利用しました。
Swift は、Ubuntu ( Linux )にもインストールすることができます。
直接ダウンロードしてきて、コンパイルするのもいいですが、fwal/setup-swift
を使った方が楽なので、これを使います。
fwal/setup-swift
は、公式のドキュメントで案内されているものです。
Building and testing Swift - GitHub Docs
Swifrを直接ダウンロードして、コンパイルするなら、こちらを参照すると良さそうです
Swiftプロジェクトを構築するためのCIとしてGitHubアクションを使用する-Arm1.ru
Rubyをインストールする
- name: Setup Ruby
uses: actions/setup-ruby@v1
ruby のバージョンを指定することも出来ますが、.ruby-version
ファイルが存在していれば、それを参照します。
ruby/setup-swift
は、公式のドキュメントで案内されているものです。
Building and testing Ruby - GitHub Docs
SwiftLint を Swift Package Manager でインストールする
SwiftLint をインストールするには、ダウンロードしてコンパイルしてもいいですが、今回は、Swift Package Manager でインストールします。
Swift Package Manager でインストールすることにより、SwiftLint だけではなく、SwiftFormat など、他のSwift製CLIツールも一緒に管理することが出来ます。
Swift Package Manager でインストールするには、以下のようなファイルを作成します。
% tree -L 5
.
├── DangerTools
│ ├── Package.resolved
│ ├── Package.swift
│ └── Sources
│ └── DangerTools
│ └── Empty.swift
Empty.swift
は、中身が空のファイルです。
Package.swift
は、こちら
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "DangerTools",
dependencies: [
.package(url: "https://github.com/realm/SwiftLint.git", from: "0.45.1"),
],
targets: [.target(name: "DangerTools", path: "")]
)
SwiftLint の Github のリンクと、バージョンを指定しています。
dependencies に、SwiftLint 以外のツールを複数追加することもできます。
Package.resolved
は、以下のコマンドを叩くと作成されます。
% swift run -c release --package-path DangerTools --build-path DangerTools/.build swiftlint --version
これを、Github Actions で実行します。
今回の記事での danger.yml
の該当箇所はこちらです。
- name: Cache DangerTools
uses: actions/cache@v2
env:
cache-name: danger-tools
with:
path: DangerTools
key: v1-${{ runner.os }}-${{ env.cache-name }}-${{ steps.get-swift-version.outputs.swift-version }}-${{ hashFiles('DangerTools/Package.resolved') }}
restore-keys: |
v1-${{ runner.os }}-${{ env.cache-name }}-${{ steps.get-swift-version.outputs.swift-version }}-
- name: Build DangerTools
run: |
./DangerTools/.build/release/swiftlint --version || swift run -c release --package-path DangerTools --build-path DangerTools/.build swiftlint --version
sudo cp ./DangerTools/.build/release/swiftlint /usr/bin
Package.resolved
をキーに含めてキャッシュさせています。
SwiftLint のバージョンを変えた場合は、先程のコマンドを実行し、Package.resolved
を更新してください。
Swift Package Manager でインストールする方法は、以下の記事を参考にさせて頂きました。
Swift製CLIツールをSwiftPMで管理するベストプラクティス - Qiita
上記で、Github Actions の ubuntu で、Danger を通して SwiftLint が実行されて、プルリクエスト作成時に Danger によって Dangerfile
に記述したルールが自動的にレビューされて、コメントされます。
Swiftバージョンをキャッシュのキーに含める
Swiftのバージョンが変わると古いキャッシュは使えなくなるので、Swiftのバージョンをキャッシュに含めます。
- name: Get Swift Version
id: get-swift-version
run: |
echo "::set-output name=swift-version::$(swift --version | head -1 | sed 's/^.*Swift version \([0-9]\.[0-9]\.[0-9]\) (.*)$/\1/')"
shell: bash
このコマンドで、Swiftの数字の部分を、切り出せます。
swift --version | head -1 | sed 's/^.*Swift version \([0-9]\.[0-9]\.[0-9]\) (.*)$/\1/'
これを、キャッシュのキーに含めます。
key: v1-${{ runner.os }}-${{ env.cache-name }}-${{ steps.get-swift-version.outputs.swift-version }}-${{ hashFiles('DangerTools/Package.resolved') }}
restore-keys: |
v1-${{ runner.os }}-${{ env.cache-name }}-${{ steps.get-swift-version.outputs.swift-version }}-
参考
actions/cache: Cache dependencies and build outputs in GitHub Actions
Dangerfile
Dangerfile
は、SwiftLint の部分だけ変更しています
# Check and comment on swiftlint only in the range corrected by PR
swiftlint.verbose = true
swiftlint.binary_path = '/usr/bin/swiftlint' if !env.ci_source.kind_of?(LocalGitRepo) ← この行を追加
swiftlint.config_file = '.swiftlint.yml'
swiftlint.lint_files inline_mode: true
Dangerfile の swiftlint.binary_path を指定
Dangerfile
の swiftlint.binary_path に、インストールした SwiftLint のパスを指定する必要がありました。
これを入れないと、danger-swiftlint プラグインの Swiftlint を使おうとするみたいだが、これを使おうとすると、エラーになるので、自分でコンパイルした Swiftlint を指定する
[プロジェクトへのパス]/vendor/bundle/ruby/2.7.0/gems/danger-swiftlint-0.29.4/ext/swiftlint/bin
ローカルで実行した際に、swiftlint.binary_path を指定したパスを使わないようにする
ローカルで danger pr
や danger local
コマンドを実行して、動作を確認する際に、SwiftLint のパスが違うとエラーになるので、CIだけで有効になるようにしました。
swiftlint.binary_path = '/usr/bin/swiftlint' if !env.ci_source.kind_of?(LocalGitRepo)
ci_source
が LocalGitRepo
かどうかを確認することにより、ローカルかどうかをチェックすることができました。
その他
DangerTools の Package.swift を Lint の対象外にする
そのままだと、SwiftLint を実行した際に、DangerTools の Package.swift
がチェックされてしまうので除外する
excluded:
- DangerTools
実行
danger.yml
で指定したブランチ向けにプルリクエストが作成されたタイミングと、そのプルリクエストにプッシュ実行したタイミングなどで、このようにコメントされます。
サンプルソースについて
今回の記事のソースは、このサンプルで試しているので、よければ覗いてみてください。
まとめ
今回の記事に関しては、どちらかというとニッチな内容だったかもしれませんが、Github Actions をテーマにした内容なので、Github Actions に興味のある誰かの役に立つ内容になっているのではないでしょうか。
弊社では、モバイルアプリのビルドやテストのCIツールに、CircleCI を採用していますが、今回、Danger の導入を通して、Github Actions の構築のしやすさを感じました。
特に、Github のリポジトリやプルリクエストに容易にアクセスできるメリットは、CircleCI には無い大きなメリットがありますよね。
Danger や SwiftLint については、導入して終わりというわけではなく、これからどう活用できるかが重要ですが、今回は、導入するところまでしか出来ていないので、チームで話し合いながら運用していければなと思います。
Danger を運用してみて、みなさんに有用そうな知見が溜まったら、会社の開発者ブログなどで、記事を書ければと考えています。
この記事が役に立ったと感じた方は、是非フォローやいいねをよろしくお願いします〜。
Discussion