クラウドMacで数週間にわたってビルドを実行し続けたとき、最も起こりやすい環境問題は、ツールがインストールされていないことではありません。実際には、異なる担当者が異なるタイミングでツールを追加していることが問題になります。あるジョブは jq に依存し、別のスクリプトは swiftlint が存在することを前提とし、一時的なトラブルシューティングで追加したformulaも複数残っています。新しいマシンへ移行するとき、リポジトリをコピーするだけでは、こうしたシステム依存関係を再現できません。Brewfileは「どのツールが必要か」を管理するのに適していますが、厳密なバージョンロックファイルではありません。信頼できる運用にするには、依存関係の宣言、バージョンの記録、クリーンアップ手順を分けて管理します。
すべてをエクスポートする前にマシンを棚卸しする
まずHomebrewのパスと現在のアーキテクチャを確認し、スクリプトが過去の別のパスを使い続けないようにします。
command -v brew
brew --prefix
uname -m
brew doctor
続いて、トップレベルのformula、インストール済みの全バージョン、バックグラウンドサービスをそれぞれ確認します。
brew leaves
brew list --formula --versions
brew services list
brew leaves は、主に明示的にインストールしたトップレベルのツールを表示するため、完全な一覧よりも宣言の出発点に適しています。一方、全バージョンの一覧は、そのままBrewfileへ入れるのではなく、ビルド環境の証拠として保存します。
ビルドジョブが動いている間は、アップグレードやクリーンアップを実行しないでください。まずジョブの割り当てを停止し、コンパイラ、データベース、補助サービスを使用しているプロセスがないことを確認します。
長期間使われているマシンでは、まず候補ファイルをエクスポートし、1行ずつ精査します。
mkdir -p ci/homebrew
brew bundle dump --force --file=ci/homebrew/Brewfile.candidate
git diff -- ci/homebrew/Brewfile.candidate
候補ファイルには、個人用ツール、一時的なデバッグソフトウェア、プロジェクトとは無関係なGUIアプリケーションが含まれている場合があります。正式な一覧へ追加するのは、ビルドやトラブルシューティングで実際に必要な項目だけにします。
Brewfileを要件の宣言として扱う
iOSの継続的ビルドに使う基本的な一覧は、短く保つことができます。
brew "git"
brew "jq"
brew "swiftlint"
brew "xcbeautify"
ファイルを ci/homebrew/Brewfile としてコミットし、インストール時にはパスを明示します。
brew bundle check --file=ci/homebrew/Brewfile
brew bundle install --file=ci/homebrew/Brewfile --no-upgrade
check はジョブの開始時に実行するのに適しています。宣言された要件が満たされているかだけを検証するものであり、ビルドジョブのたびに全面的なアップグレードを自動実行すべきではありません。install --no-upgrade を使えば、不足しているツールを追加しつつ、通常のビルドによって既存環境が意図せず変更される可能性を抑えられます。
役割ごとにBrewfileを分割する
同じVMKeepクラウドMacで複数種類の作業を実行する場合は、基本用とプロジェクト用に一覧を分けることを推奨します。たとえば、基本レイヤーにはGit、JSON処理、ログ関連のツールだけを置き、プロジェクトレイヤーにはコーディング規約の検査やリリース支援に使うツールを追加します。インストール順序は、基本レイヤーを先、プロジェクトレイヤーを後に固定します。
すべてのリポジトリを、増え続ける1つのグローバルBrewfileでカバーしてはいけません。そのような構成では、クリーンアップのたびに影響範囲を判断しにくくなります。また、新しいプロジェクトが過去から残っているツールを、自分に必要な依存関係だと誤認する原因にもなります。
バージョンと実行コンテキストを個別に記録する
通常、Brewfileは一般的なformulaを厳密なバージョンに固定しません。2台のマシンに同じ一覧があるだけでは、同じ結果が得られるとは限りません。環境を変更するたびに、実際のバージョンスナップショットを保存します。
{
date -u
sw_vers
xcodebuild -version
brew --version
brew list --formula --versions
} > ci/homebrew/toolchain.snapshot.txt
バージョンスナップショットはパイプラインの成果物として保存できます。また、レビュー済みの環境アップグレード後に、運用記録へコミットすることもできます。差異が発生した場合は、まずXcode、macOS、Homebrew、直接依存するツールのバージョンを比較し、次にプロジェクトのロックファイルを確認します。最初からすべてのキャッシュを削除してはいけません。
バージョンの境界を明確にする
brew pin が防止できるのは、現在のマシン上でのローカルなアップグレードだけです。別の新しいマシンでも同じ過去バージョンを取得できるとは限りません。そのため、短期的な保護には使えますが、固定されたマシンイメージ、プロジェクト専用のバージョン管理ツール、検証済みのバイナリ成果物の代わりにはなりません。
成果物へ直接影響するツールについては、起動時にスクリプトで許容バージョン範囲を確認し、範囲外なら明確に失敗させます。一方、ログを読みやすく整形するだけのツールには、より広い範囲を許容できます。重要でない差異によってビルド全体が中断されるのを避けるためです。
プレビューとアイドル時間を利用して安全に整理する
実際にクリーンアップする前に、Brewfileに含まれていない項目を確認します。
brew bundle cleanup --file=ci/homebrew/Brewfile
この段階では、出力内容の確認だけを行います。表示されたformulaが、他のプロジェクト、LaunchAgent、バックグラウンドサービスから利用されていないことを確認してから、次のコマンドを実行します。
brew bundle cleanup --file=ci/homebrew/Brewfile --force
brew autoremove
brew cleanup
共有マシンでは特に注意が必要です。現在のリポジトリのBrewfileに含まれていないformulaでも、別のジョブが使用している可能性があります。より確実に分離するには、長期的なワークロードの種類ごとに専用の物理ノードを割り当てます。少なくとも、実行アカウント、作業ディレクトリ、依存関係の一覧はそれぞれ分離してください。
クリーンアップ後は brew bundle check を再実行し、最小限の検証フローを実施します。ツールのバージョンを表示し、プロジェクト設定を解析し、成果物をアップロードしないビルドを1回完了させます。この3つがすべて成功して初めて、環境変更は完了です。
環境変更をレビュー可能なイベントにする
安定した運用では、ビルドスクリプトが場当たり的に brew install を実行することを許可すべきではありません。ツールを追加するコミットには、Brewfileの変更、用途の説明、バージョンスナップショット、ロールバック方法をまとめて含めます。アップグレードは専用の作業時間帯に行い、代表的なプロジェクトを検証してからジョブの割り当てを再開します。
日常的な確認項目は、次の4つに絞ることを推奨します。
brew bundle checkが成功するか。- Xcodeと主要なformulaのバージョンが想定どおりか。
- Brewfileに未レビューの変更がないか。
- 宣言されていないバックグラウンドサービスが動き続けていないか。
Brewfileの価値は、あらゆるツールを自動的にインストールすることではありません。マシン上の既成事実だったシステム依存関係を、リポジトリ内で議論、比較、ロールバックできる宣言へ変えることにあります。バージョンスナップショットと慎重なクリーンアップを組み合わせれば、クラウドMacの環境問題をその場の推測ではなく、証拠に基づく設定差異として扱えるようになります。
よくある質問
BrewfileだけでHomebrewパッケージの正確なバージョンを固定できますか?
できません。Brewfileは必要なツールを宣言しますが、通常のformulaはリポジトリの更新に従います。厳密な再現にはバージョン記録、固定したマシンイメージ、個別のバージョン管理が必要です。
共有クラウドMacでbrew bundle cleanup --forceをすぐ実行してもよいですか?
推奨しません。まず--forceなしで削除候補を確認し、ほかのジョブが利用していないことを調べてから、稼働していない時間帯に実行してください。
クラウドMacでこのワークフローの検証を続ける
3種類のApple Silicon構成から、最適なメモリ、ストレージ、ノード、利用期間を選べます。