📘

SwiftLintをGithubActionsのmacOSではなくubuntuで実行する

2021/12/19に公開

この記事は、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 を記述

Gemfile
gem 'danger'
gem 'danger-swiftlint'

2. Gem をインストール

% bundle install

3. Dangerfile の作成

% touch Dangerfile

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 の記述

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 のリポジトリや、プルリクエストが参照できる権限がないといけません。

danger.yml
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の記述

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をインストールする

danger.yml
      - 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をインストールする

danger.yml
      - 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 は、こちら

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 の該当箇所はこちらです。

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のバージョンをキャッシュに含めます。

danger.yml
      - 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/'

これを、キャッシュのキーに含めます。

danger.yml
          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 の部分だけ変更しています

Dangerfile
# 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 を指定する

danger-swiftlint プラグインの Swiftlint のパス
[プロジェクトへのパス]/vendor/bundle/ruby/2.7.0/gems/danger-swiftlint-0.29.4/ext/swiftlint/bin
ローカルで実行した際に、swiftlint.binary_path を指定したパスを使わないようにする

ローカルで danger prdanger local コマンドを実行して、動作を確認する際に、SwiftLint のパスが違うとエラーになるので、CIだけで有効になるようにしました。

Dangerfile
swiftlint.binary_path = '/usr/bin/swiftlint' if !env.ci_source.kind_of?(LocalGitRepo)

ci_sourceLocalGitRepo かどうかを確認することにより、ローカルかどうかをチェックすることができました。

その他

DangerTools の Package.swift を Lint の対象外にする

そのままだと、SwiftLint を実行した際に、DangerTools の Package.swift がチェックされてしまうので除外する

.swiftlint.yml
excluded:
  - DangerTools

実行

danger.yml で指定したブランチ向けにプルリクエストが作成されたタイミングと、そのプルリクエストにプッシュ実行したタイミングなどで、このようにコメントされます。


サンプルソースについて

今回の記事のソースは、このサンプルで試しているので、よければ覗いてみてください。

https://github.com/yoko-yan/RxSwiftIncrementalSearch

まとめ

今回の記事に関しては、どちらかというとニッチな内容だったかもしれませんが、Github Actions をテーマにした内容なので、Github Actions に興味のある誰かの役に立つ内容になっているのではないでしょうか。

弊社では、モバイルアプリのビルドやテストのCIツールに、CircleCI を採用していますが、今回、Danger の導入を通して、Github Actions の構築のしやすさを感じました。
特に、Github のリポジトリやプルリクエストに容易にアクセスできるメリットは、CircleCI には無い大きなメリットがありますよね。

Danger や SwiftLint については、導入して終わりというわけではなく、これからどう活用できるかが重要ですが、今回は、導入するところまでしか出来ていないので、チームで話し合いながら運用していければなと思います。
Danger を運用してみて、みなさんに有用そうな知見が溜まったら、会社の開発者ブログなどで、記事を書ければと考えています。

この記事が役に立ったと感じた方は、是非フォローやいいねをよろしくお願いします〜。

Discussion