この記事は BASE Advent Calendar 2023 の18日目の記事です。
Pay ID Appグループの北川です。ショッピングアプリ「Pay ID」の開発チームでエンジニアリングマネージャーを担当しています。
iOSアプリ開発で依存管理に使っている Mint のバージョンアップデートを GitHub Actions を使って自動化した話をします。
Mint とは
Mint は Swift 製のライブラリのパッケージマネージャです。
https://github.com/yonaskolb/Mint
私たちのiOSアプリのプロジェクトでは、以下のようなビルドツール系のライブラリの依存管理に1年ほど利用しています(アプリ本体の依存管理には Swift Package Manager を使っています)。
- SwiftLint
- Mockolo
- IBLinter
- SwiftFormat
- XcodeGen
- periphery
- LicensePlist
- SwiftGen
もともと、プロジェクトリポジトリの中に BuildTools というディレクトリをつくり、そのなかにアプリケーション本体では利用しないツール群を Package.swift で定義しビルドしていました。
しかし、複数のライブラリを利用している場合、それぞれで利用している依存関係のバージョンが衝突することがたびたびおこり、よりシンプルな方法を検討した結果 Mint を採用することにしました。
サードパーティ製のライブラリではありますが、シンプルな定義ファイルでバージョンを固定することができ、かつキャッシュも扱いやすいことがメリットと捉えています。
候補としてビルド不要で利用できる Homebrew も上がりましたが、チーム開発をする上で各メンバのマシンや実行環境で利用するバージョンを固定したいため、私たちは選択しませんでした。
重視したポイント | バージョン固定 | 依存管理難易度 | CIでのキャッシュ管理 |
---|---|---|---|
SPM | ⭕️ | ❌ | ⭕️ |
Homebrew | ❌ | ⭕️ | ❌ |
Mint | ⭕️ | ⭕️ | ⭕️ |
ちなみに Mint 自体をどうやってインストールするかという課題もあるのですが、それは Homebrew を使ってインストールすることで妥協しています。
バージョンの更新を自動化したい
さて、Mint では Mintfile というファイルでパッケージのバージョンを固定することができます。
yonaskolb/xcodegen@2.18.0 yonaskolb/genesis@0.4.0
これらのパッケージは定期的にアップデートをしたいとは思いつつ、 Linter などが管理したいパッケージであったのでアップデートしなくともある程度動くため後回しになりがちという課題がありました。
Renovate を使ってアップデートするPRを作れそうだとわかりましたが、弊社では現時点で導入しておらず、 別の方法を検討しました。
Mintfile 自体はシンプルなフォーマットであり、Swift Package Manager も tag を使ってバージョン管理を行う仕組みなので、GitHub Action で文字列操作と GitHub API を組み合わせることでサクッと実現できるのではと考えました。
パッケージの分だけ更新チェックをするために動的に matrix を設定する
チームでは過去に CocoaPods や Swift Package Manager で管理しているライブラリの定期的な更新チェックを CI で自前で実現していたのですが、複数のライブラリのアップデートを1つの Pull Request で行うような仕組みになっていました。
このような仕組みだと、A、Bという更新可能なライブラリがあり、Aは更新が容易だがBはすぐには更新できない、というような場合にAも更新できない、という点で使いづらさを感じていました。
そこで、以下のステップのように各パッケージ毎に更新をチェックし必要なら Pull Request を作成できるようにする仕組みを目指しました。
- Mintfile に定義されている依存を取り出す
- 1で取り出したパッケージ毎にジョブを実行する
- 各ジョブでパッケージの最新バージョンをチェック
Using a matrix for your jobs - GitHub Docs
GitHub Actions には matrix キーワードを使って、定義されたパターン分の変数を使ってジョブを実行することができます。
jobs: example_matrix: strategy: matrix: version: [10, 12, 14] os: [ubuntu-latest, windows-latest]
この matrix は動的に設定することができるので、 Mintfile で定義した分のパッケージを matrix として設定するようにします。
まず、 Mintfile を1行毎に分割し配列の JSON 形式にします。
jobs: # Mintfile から依存関係を取得して、それを Matrix にするために JSON に変換して出力する prepare-mint-dependencies: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: make matrix for each mint dependencies id: set-matrix run: | MINT_DEPENDENCIES=$(cat Mintfile | sed -e 's/#.*$//' | sed -e '/^$/d' | jq -R -s -c 'split("\n") | map(select(. != ""))') echo "dependencies=${MINT_DEPENDENCIES}" >> $GITHUB_OUTPUT outputs: dependencies: ${{ steps.set-matrix.outputs.dependencies }}
例として JSON はこのようになっているイメージです。
["realm/SwiftLint@0.51.0","IBDecodable/IBLinter@0.5.0","nicklockwood/SwiftFormat@0.50.3","yonaskolb/XcodeGen@2.25.0","uber/mockolo@2.0.0","mono0926/LicensePlist@3.23.4","peripheryapp/periphery@2.12.3","SwiftGen/SwiftGen@6.4.0"]
次に、出力した outputs を使って matrix を定義します。
# 各依存関係のリポジトリを参照して、最新のバージョンを取得する check-update: runs-on: ubuntu-latest needs: prepare-mint-dependencies strategy: matrix: mint-dependency: ${{fromJson(needs.prepare-mint-dependencies.outputs.dependencies)}} fail-fast: false steps: - name: print dependencies run: | echo ${{ matrix.mint-dependency }}
こうすることで1回のワークフローを起点に依存しているパッケージ毎に更新をチェックすることができるようになります。
あとは、各ライブラリのリポジトリを見に行って最新バージョンがあるか確認し必要なら PR を作成すれば良いという流れになります。
ちなみに、最新バージョンを取得する場合は github-script が便利です。
# GitHub Scriptで最新のバージョンを取得する - name: find latest version from GitHub API id: get-latest-version uses: actions/github-script@v7 with: script: | const { data } = await github.repos.getLatestRelease({ owner: '${{ env.REPO_OWNER }}', repo: '${{ env.REPO_NAME }}', }) return data.tag_name result-encoding: string
おわりに
このようにして Mint で管理しているパッケージの更新チェックを GitHub Actions を使って実現することができました。 matrix を使うことである種プログラミング的にワークフローを制御できるので、自動化の幅が広がると感じています。
明日の19日目は ImazekiShota さんの記事です。お楽しみに!