中規模のSwiftリポジトリでクラウドMac上の全件スタイル検査を実行する場合、ボトルネックになりやすいのはツール自体の速度ではありません。マージリクエストのたびに、変更されていないソースコード、生成ディレクトリ、外部依存関係まで繰り返し走査していることが主な原因です。さらに、開発者のローカル環境とCIで異なるバージョンを使っていると、同じコード行に対して異なる判定が出ることもあります。解決策はルールを無効にすることではなく、ツールのバージョン、設定ファイル、差分計算のロジックをまとめてリポジトリで管理することです。
2つのツールの役割を明確に分ける
SwiftFormatは、インデント、改行、余分な空白など、結果が一意に決まり自動修正できる書式処理に適しています。SwiftLintは、強制キャスト、長すぎる関数、命名規則違反といったリスクのあるパターンの検出に向いています。両方のツールで同じルールを管理すると、フォーマットを実行した直後に静的検査で拒否される可能性があります。
まずは、ルールの担当を示す簡単な表を作成します。
| 検査対象 | 担当ツール | マージリクエストでの動作 |
|---|---|---|
| インデント、空白、引数の改行 | SwiftFormat | 検査のみ実行し、自動で書き換えない |
| 強制キャストと強制アンラップ | SwiftLint | 失敗として扱い、ファイル位置を出力 |
| 生成コードと外部依存関係 | 両方から除外 | 差分ファイル一覧に含めない |
| 既存の警告 | SwiftLintのベースライン | 新たに追加された問題だけを阻止 |
CIでは、ソースコードを書き換えるコマンドを直接実行しないでください。ゲートは差分の報告だけを担当し、開発者がローカルで修正してからコミットすることで、リポジトリの状態を再現しやすくします。
バージョンとルールをリポジトリに固定する
クラウドMacに「現在インストールされている」ツールへ依存してはいけません。まずチームで検証済みのバージョンを選び、想定するバージョンをスクリプトに明記します。以下ではSwiftFormat 0.54.3とSwiftLint 0.55.1を例にしていますが、実際のプロジェクトでは独自に検証したバージョンへ置き換えてください。
.swiftformatは簡潔な内容で構いません。
--swiftversion 5.10
--indent 4
--wraparguments before-first
--exclude .build,DerivedData,Vendor,Generated
.swiftlint.ymlでは、対象となるソースの範囲と除外ディレクトリを明示します。
included:
- Sources
- Tests
excluded:
- .build
- DerivedData
- Vendor
- Generated
only_rules:
- force_cast
- force_try
- trailing_whitespace
- unused_import
ルールファイルも必ずコードレビューの対象にします。ルールを追加するときは、まず専用ブランチでリポジトリ全体を検査し、警告の規模を確認したうえで、一括修正するかベースラインを作成するかを決めます。数百件もの既存警告をそのままマージリクエストのゲートに持ち込むと、最終的にチームが失敗を無視するようになってしまいます。
マージリクエストのSwift差分を正しく抽出する
git diff HEAD^を直接使う方法は、コミットが1つだけのブランチにしか適していません。マージリクエストに複数のコミットが含まれている場合や、リベースされた場合には、ファイルを取りこぼす可能性があります。より確実なのは、現在のコミットと対象ブランチの共通祖先を計算し、追加、コピー、変更、名前変更されたファイルを抽出する方法です。
set -euo pipefail
expected_format="0.54.3"
expected_lint="0.55.1"
[[ "$(swiftformat --version)" == "$expected_format" ]]
[[ "$(swiftlint version)" == "$expected_lint" ]]
base_ref="${LINT_BASE_REF:-origin/main}"
merge_base="$(git merge-base HEAD "$base_ref")"
changed=("${(@0)$(git diff --name-only -z --diff-filter=ACMR "$merge_base" HEAD)}")
swift_files=()
for file in "${changed[@]}"; do
[[ "$file" == *.swift ]] || continue
[[ "$file" == .build/* ]] && continue
[[ "$file" == DerivedData/* ]] && continue
[[ "$file" == Vendor/* ]] && continue
[[ "$file" == Generated/* ]] && continue
swift_files+=("$file")
done
(( ${#swift_files[@]} > 0 )) || exit 0
swiftformat --lint "${swift_files[@]}"
swiftlint lint --strict --force-exclude "${swift_files[@]}"
このスクリプトでは、ファイル一覧をNULL文字で区切っているため、パスに空白が含まれていても誤って分割されません。--diff-filter=ACMRで削除済みファイルを除外し、検査ツールに存在しないパスが渡されるのを防ぎます。対象ブランチ名は環境変数から渡すため、同じスクリプトをデフォルトブランチにもリリースブランチにも使用できます。
シャロークローンに対処する
git merge-baseが共通祖先を見つけられない場合、通常はスクリプトの不具合ではなく、CIが取得したコミット履歴が浅すぎることが原因です。スクリプトを実行する前にチェックアウトの深さを増やし、対象ブランチの参照が存在することを確認してください。HEAD^へ戻すと、ブランチ構造によってゲートの結果が変わってしまうため避けるべきです。
失敗を特定可能かつ再現可能にする
ゲートが失敗したとき、ログから少なくとも3つの情報を確認できる必要があります。使用したツールのバージョン、比較対象とした基準コミット、検査対象になったファイルです。バージョンが一致しない場合は、大量の書式差分を発生させながら処理を続けるのではなく、直ちに停止させます。
ローカルで再現するときは、対象ブランチの参照を取得して同じスクリプトを実行するだけです。
git fetch origin main
LINT_BASE_REF=origin/main zsh Scripts/lint-changed-swift.zsh
よくある誤りは、検査コマンドをパイプラインの後ろにつなぎ、別の終了コードを読み取ってしまうことです。また、見栄えのよいログを作るために|| trueを使い、本来の失敗を握りつぶしてしまうケースもあります。スクリプトでset -euo pipefailを有効にすると、いずれかのツールが0以外のステータスを返した時点でジョブが終了します。CI側でログを整形する必要がある場合は、まず元の終了コードを保存し、概要を出力してから保存した値で終了してください。
2層の運用方針で長期的なコストを抑える
差分ゲートが保証するのは「今回の変更で新しい問題を持ち込んでいない」ことだけであり、既存コードのすべてが現在のルールに準拠していることまでは証明できません。そのため、検査を2層に分けます。
- マージリクエストごとに差分検査を実行します。高速で安定し、ローカルでも完全に再現できることを目標にします。
- 定期ジョブでリポジトリ全体を検査し、ベースラインのずれ、無効になった除外設定、長期間未対応の既存警告を検出します。
- ルールの更新は独立したコミットにし、機能変更と混在させません。これにより、フォーマットによる大規模な変更をレビューしやすくなります。
- 生成ディレクトリは生成元を明確にし、ツール設定で除外するとともに差分スクリプトでも取り除きます。これにより、生成タスクの実行順序が結果に影響するのを防ぎます。
導入前にもう一度、対象ブランチの参照が存在するか、ツールのバージョンが完全に一致しているか、名前変更されたファイルが含まれているか、削除済みファイルが除外されているか、空白を含むパスを安全に処理できるか、Swiftの変更がない場合に正常終了するかを確認してください。これらを確認して初めて、コードスタイルゲートは「ときどきエラーになるスクリプト」ではなく、信頼できるコミット境界になります。
よくある質問
差分検査だけで全ファイル検査は不要になりますか?
不要にはなりません。プルリクエストでは差分検査を使い、未変更の既存ファイルに残る違反は定期的な全ファイル検査で確認します。
SwiftFormatとSwiftLintで同じ規則を有効にすべきですか?
役割を分けるべきです。自動修正可能な整形はSwiftFormat、危険な記述やチーム規約はSwiftLintに担当させ、重複規則は片方だけで管理します。
クラウドMac miniで次の開発・ビルドタスクを実行
2種類のM4構成、4つのレンタル期間、販売中の5つのノードから選択できます。実際の利用可能状況は、コンソールにリアルタイムで表示される情報をご確認ください。