はじめに
カカクコムで キナリノの iOS アプリ を主に開発している奥野です。
キナリノの iOS チームでは半年ほど前、ライブラリの更新を自動化するために Renovate を導入してみました。しばらく経った今、使っているライブラリのバージョンが、最新版であるかを確認する作業が不要になり、だいぶ楽になったことを実感しています。
ただ、Renovate の導入を検証していた際、キナリノでは現在 GitLab を使っているのですが、私が GitLab Runner の仕様を知らなかったり、iOS で XcodeGen を使っている場合は対応が必要など、情報整理に時間がかかってしまいました。
そこで、今回は GitLab で Renovate を新たに導入したい人向けに、Renovate の概要を説明した上で、GitLab Runner のインストール方法から、XcodeGen を使っている場合の iOS の設定方法についてを紹介していきます。
- はじめに
- Renovate の概要
- GitLab Runner のインストールと設定
- iOS プロジェクトの設定
- GitLab CI/CD の設定
- 動作確認してマージリクエストを作成
- おわりに
- カカクコムでは共にサービスをつくる仲間を募集しています
Renovate の概要
ここでは Renovate ができることについて、過去の情報も踏まえて簡単に紹介します。
Renovate とは何か
Renovate はパッケージマネージャの依存関係を更新してくれる CLI ツールです。Github Apps や Docker イメージ、npm などで配布されています。
サポートされているパッケージマネージャは非常に多く、SwiftPM や CocoaPods、 Bundler などもサポートされています1。ただし、バージョンを統一するためのロックファイル(Package.resolved
など)は更新されないものもあるため、実際のアップデートには手作業も発生します。
また、Renovate には regex manager という、正規表現でバージョン等の情報を抽出できる機能があります。これによって、サポートされていないパッケージマネージャであっても、自動更新の対象とすることが可能となります。XcodeGen で定義された SwiftPM のライブラリは、この regex manager で対応しています。
(余談) Renovate の過去
過去の記事から分かるように、かつての Renovate には有料プランもあった様子です。しかし、2019年11月に WhiteSource 社が買収して完全無料化され2、その後 2022年5月に WhiteSource が社名変更して MEND となり3、現在に至ります。割と最近の出来事でなので、Renovate に関する古い記事にはご注意ください。
実際に Renovate を実行して作成されるマージリクエスト
Renovate を実行すると、1分経たずに次のようなマージリクエストが作られます。
Renovate の紹介は以上です。
次に、GitLab Runner のインストール方法に移っていきます。
GitLab Runner のインストールと設定
ここでは GitLab Runner のインストールから Renovate に必要な設定までを紹介します。
なお、この記事では Docker イメージの Renovate を使用するため、Docker を実行できる環境での作業を前提としています。
また、既に GitLab Runner がインストールされている場合、executor は docker
になっている必要があるので、確認してください。
GitLab Runner をインストールして実行する
次の手順で GitLab Runner の実行までを行います。これは Linux x86-64 の場合の手順 ですが、他の場合は Install GitLab Runner を参照してください。
- gitlab-runner をインストールする
- 実行権限の付与
- GitLab Runner ユーザー作成
- GitLab Runner のインストールと実行
sudo curl -L --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.m/latest/binariesgitlab-runner-linux-amd64"
sudo chmod +x /usr/local/bin/gitlab-runner
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner sudo gitlab-runner start
以上の手順で GitLab Runner が実行された状態になるので、次にこの Runner の設定を行います。
GitLab Runner を設定する
ここでは、GitLab Runner の GitLab への登録と、 executor
を docker
にする設定を行います。この手順も Linux でない場合は Register a runner を参照してください。
次のコマンドを実行すると対話式で設定できるので、添付画像のように入力します
sudo gitlab-runner register
設定項目 設定する値 Enter the GitLab instance URL 利用している GitLab の URL Enter the registration token GitLab で発行されたトークンを入力。特定プロジェクトのみで実行したい場合は Project runners のトークン、グループ内で使えるようにしたい場合は Group runners のトークンを入力します。 Enter a description for the runner 適当に入力 Enter tags for the runner 後に .gitlab-ci.yml
で指定する際のタグ名Enter optional maintenance note for the runner 適当に入力 Enter an executor docker
に設定(必須)Enter the default Docker image renovate/renovate:35.89
を入力。後に.gitlab-ci.yml
ファイルで指定できるため、他のイメージでも可能です。(任意)GitLab Runner は、デフォルトで Docker イメージの pull を毎回実行するため、速度改善のためにイメージがない場合のみ実行されるよう変更します。
vim /etc/gitlab-runner/config.toml
[[runners]] ... executor = "docker" [runners.docker] ... pull_policy = "if-not-present" # ← 追加する
詳しくは GitLab のドキュメント をご確認ください。
以上で GitLab Runner の設定は完了です。次は iOS プロジェクトの設定に移っていきます。
iOS プロジェクトの設定
キナリノの iOS プロジェクトでは、XcodeGen を使用しています。また、ライブラリは基本的に SwiftPM で管理しており、一部が CocoaPods に残っている状態です。
そのため、ここでは iOS プロジェクトに対する、XcodeGen を使った SwiftPM と CocoaPods に必要な Renovate の設定を紹介します。
現状の課題 Package.resolved
は自動更新ができない
SwiftPM で自動生成される Package.resolved
については、Renovate では更新されません。別途手動等で更新する必要があります。
現状の課題 セマンティックバージョンではない場合、自動更新ができない可能性
※ renovate のバージョン 35.8.1 における事象であり、36.0.0 からは一部変更があります
GitHub 上のライブラリには、バージョン管理が セマンティックバージョン ではないものがあります。例えば v3.4 というリリースは、接頭辞に v
が付いていることと4、パッチバージョンがないことから5、厳密にはセマンティックバージョンではないようです。
Renovate の regex manager はデフォルトで 厳密なセマンティックバージョン((※バージョン 36.0.0 から変更) 非厳密なセマンティックバージョン(v1
→ 1.0.0
と解釈しない)v1
→ 1.0.0
と解釈する)を使用してはいますが、ライブラリがセマンティックバージョンに則っていない場合、そのままでは最新版を検出できなかったり、できたとしても次の画像のような差分に v
が入るなど、期待した結果にならないことがありました。
そのため、Renovate を動作させた後は、全てのライブラリが最新版になっているか、差分に問題がないか、確認することをお勧めします。
Podfile
などバージョン指定の注意
Podfile
に記載されたライブラリはバージョン指定がない場合、 Renovate に検出されなかったり、パッチまで固定を行わないと自動更新されなかったりするので、ご注意ください。
# × 検出されない pod 'ライブラリ名' # △ これだとパッチのバージョンアップは検出されないことがあった pod 'ライブラリ名', '~> バージョン' # ○ パッチも検出された pod 'ライブラリ名', 'バージョン'
renovate.json を作成する
結果として、次のような renovate.json
ファイルを作成し、リポジトリ最上部の階層に配置しました。ここでは、最低限の設定のみを記載しており、自動マージされる機能などは割愛しています。
{ // 基本設定 "extends": ["config:base"], "timezone": "Asia/Tokyo", // PR(MR) の設定 "prHourlyLimit": 20, "prConcurrentLimit": 99, "draftPR": true, // CocoaPods と Bundler 以外は regex manager で対応 "enabledManagers": ["cocoapods", "bundler", "regex"], // yml ファイルに定義された SwiftPM と、 Makefile にバージョンを記入している XcodeGen を正規表現で対応 "regexManagers": [ { "fileMatch": ["(^|/)packages.yml$"], "matchStrings": ["url: https:\\/\\/github\\.com\\/(?<depName>.*?)(\\.git)?\\s*(majorVersion|minorVersion): (?<currentValue>.*?)\\s"], "datasourceTemplate": "github-releases", "versioningTemplate": "semver-coerced" }, { "fileMatch": ["^Makefile$"], "matchStrings": ["XCODEGEN_LOCKED_VERSION := (?<currentValue>.*?)\\s"], "datasourceTemplate": "github-releases", "depNameTemplate": "yonaskolb/XcodeGen" } ], "packageRules": [ // 自動更新の対象外としたいライブラリを定義 { "matchPackageNames": [ "fastlane", "jira-ruby" ], "enabled": false } ] }
この renovate.json
を作成するにあたっては、以下の記事などを参考にさせていただきました。
各設定について、ここでも簡単に触れたいと思います。
まず、 extends
では Renovate が用意している基本設定 config:base を引き継いでいます。
// 基本設定 "extends": ["config:base"], "timezone": "Asia/Tokyo",
次に、リポジトリで使用しているパッケージマネージャを設定しています。キナリノでは Bundler を CocoaPods 自体のバージョン管理などに使っています。
// CocoaPods と Bundler 以外は regex manager で対応 "enabledManagers": ["cocoapods", "bundler", "regex"],
ここに "regex"
を追加しないと、次の regexManagers
を定義しても正規表現による抽出は実行されないので、ご注意ください。
そして、次が XcodeGen で使っている yml ファイルから、SwiftPM を正規表現で抽出している箇所です。
"regexManagers": [ { "fileMatch": ["(^|/)packages.yml$"], "matchStrings": ["url: https:\\/\\/github\\.com\\/(?<depName>.*?)(\\.git)?\\s*(majorVersion|minorVersion): (?<currentValue>.*?)\\s"], "datasourceTemplate": "github-releases", "versioningTemplate": "semver-coerced" },
matchStrings
が複雑そうに見えますが、やっていることは単純で、次の 3 点を renovate が分かるように定義すれば良いだけです。
- バージョンを正規表現で
currentValue
として抽出する - リポジトリ名を正規表現で
depName
として抽出するか、 depNameTemplate で定義する - リポジトリのサービスを正規表現で
datasource
として抽出するか、 datasourceTemplate で定義する
細かな書き方については Renovate の例 で示されているので、そちらを参照してください。
"versioningTemplate": "semver-coerced"
では、バージョニングを 非厳密なセマンティックバージョン (v1 → 1.0.0 と解釈する) に設定しています。キナリノでは、指定しないと自動更新できないライブラリ (Gifu) があったためです。
また、今回は紹介しませんが、キナリノでは XcodeGen のバージョンもメンバーで揃えているので、 Makefile に定義されたバージョンを次のように確認しています。
{ "fileMatch": ["^Makefile$"], "matchStrings": ["XCODEGEN_LOCKED_VERSION := (?<currentValue>.*?)\\s"], "datasourceTemplate": "github-releases", "depNameTemplate": "yonaskolb/XcodeGen" }
あとは、自動更新の対象から除外したいものを設定しています。
"packageRules": [ ... { "matchPackageNames": [ "fastlane", "jira-ruby" ], "enabled": false } ]
他にも設定できる項目は数多くあるので、ドキュメント をみて、チームに合った設定を探すのが良いと思います。
.gitlab-ci.yml を設定する
.gitlab-ci.yml
には、次のような内容で設定します。このファイルがなければ、新たにプロジェクトの最上部の階層に作成します。
stages: - dependency renovate: image: renovate/renovate:35.89 # gitlab-runner register 時にデフォルトイメージを renovate に設定していれば不要 tags: - docker-runner # gitlab-runner register 時に設定したタグ stage: dependency script: - renovate --platform gitlab --endpoint $CI_SERVER_URL/api/v4 $CI_PROJECT_PATH rules: - if: $IS_RENOVATE && $CI_PIPELINE_SOURCE == "schedule"
image: renovate/renovate:35.89
は sudo register runner
を実行した際、デフォルトイメージに設定していれば、ここでは不要です。 tags
については gitlab-runner register
時に設定したタグ名を入力します。
$CI_
から始まるものは、GitLab で定義済みの変数 です。
$IS_RENOVATE
は他のスケジュールが実行されても Renovate は実行されないようにするために用意しました。
XcodeGen を使っている場合に Podfile.lock
が更新できないエラーの対処を Podfile
に行う
XcodeGen を使用している場合、Renovate を動かすと次のような Podfile.lock
を更新できない旨のエラーが発生します。
このエラーの対処法は次の記事を参考にさせていただきましたが、ここでも簡単に紹介したいと思います。
Podfile
に次のような処理を加えることで対処できます。
... is_renovate = ENV['HOME'] !~ /^\/Users\/.*/ if is_renovate then install! 'cocoapods', :integrate_targets => false end target 'kinarino' do use_frameworks! ... if is_renovate then current_target_definition.swift_version = '5.7.2' end ...
追加した処理がやっていることを説明すると、
if is_renovate then install! 'cocoapods', :integrate_targets => false end
ここは、Renovate による pod install
実行時は、Podfile
に :integrated_target: false
が設定されることで、Xcode の project ファイルへの組み込みを回避しています。
また、これを追加することで、Swiftのバージョンが取得できないエラーが追加で発生してしまうため、
if is_renovate then current_target_definition.swift_version = '5.7.2' end
の処理で Swift バージョンを明示することで対処しています。is_renovate
については環境に合わせて定義するのが良いと思います。
これで無事に Podfile.lock
は更新されるようになると思います。
それでは最後に、GitLab CI/CD の設定に移ります。
GitLab CI/CD の設定
ここでは、Renovate を定期実行する上で必要な GitLab CI/CD 設定について紹介します。
Renovate が定期実行されるパイプラインスケジュールを作成する
GitLab でパイプラインスケジュールを作成します。 IS_RENOVATE
に true
を設定します。
プロジェクトに CI/CD の変数を用意する
設定するプロジェクトにおいて、 CI/CD > 変数の画面で次の 2 つの変数を用意します。パイプラインスケジュールの変数として定義しても良いと思います。
RENOVATE_TOKEN
: GitLab で発行するプロジェクトのアクセストークンの値- 権限には
read_user
,api
,write_repository
の 3 つが必要
- GitLab Container registry にアクセスする場合は
read_registry
も追加
- 権限には
GITHUB_COM_TOKEN
: GitHub で発行する読み取り専用のアクセストークンの値
以上の手続きで、 GitLab で Renovate が定期実行される準備が整いました。
動作確認してマージリクエストを作成
dryRun で手動実行する
試しに、Renovate の動作を確認したい場合は、Docker が動く環境で次の --dry-run=full
を加えたコマンドで実行できます。Renovate Docs > dryRun
docker run -e GITHUB_COM_TOKEN={GitHub のトークン} --rm renovate/renovate:35.89 renovate --platform gitlab --token {GitLab のトークン} --endpoint {GitLab のURL}/api/v4 {グループ名/リポジトリ名} --dry-run=full
また、MR が作られる元のブランチは、リポジトリのデフォルトブランチとなります。もし、ブランチを指定したい場合は docker run
の後ろに -e RENOVATE_BASE_BRANCHES={ブランチ名}
を付け加えれば対応できます。ただ、挙動を確認したところ renovate.json
ファイルの参照先はデフォルトブランチの様子なので、その点は注意が必要です。Renovate Docs > baseBranches
docker run -e RENOVATE_BASE_BRANCHES={ブランチ名} -e GITHUB_COM_TOKEN={GitHub のトークン} --rm renovate/renovate:35.89 renovate --platform gitlab --token {GitLab のトークン} --endpoint {GitLab のURL}/api/v4 {グループ名/リポジトリ名} --dry-run=full
実行結果は次の添付画像のように、パッケージマネージャごとに検知された件数や、アップデート毎にブランチ作成など情報が出力されます。
この結果、検出されたライブラリの数が合っていたり、エラーが表示されていなければ、 Renovate の設定は完了です!
Renovate を実行して、マージリクエストを作成する
手動実行の方法で --dry-run=full
を外して実行するか、作成したスケジュールを実行すれば、Renovate が自動で更新のあるライブラリを検出して、次のようにマージリクエストを作成してくれると思います。
おわりに
この記事では、XcodeGen を使った iOS プロジェクトに対して、GitLab CI/CD で Renovate を定期実行し、ライブラリの最新版があれば、バージョン更新のマージリクエストが自動で作成される方法を紹介しました。
Package.resolved
が更新されないなどの課題は残りますが、キナリノでは、ライブラリの最新版のことを気にする必要がなくなり、待っていれば勝手にマージリクエストが作られるので、導入して良かったと感じています。
導入方法についてはこの記事で紹介しましたが、導入後は 運用して得た Tips などの各社テックブログも見て、チームに合ったものへ改善していきたいですね。