エンジニアリングガイド

クラウドMac CIでiOSビルド番号を一元管理する

クラウドMac CIでiOSビルド番号を一元管理する

クラウドMac上で2本のiOSアーカイブパイプラインが同時に動いています。一方は本番環境の問題を修正し、もう一方は通常リリースを準備しています。どちらもプロジェクトファイルから同じ CURRENT_PROJECT_VERSION を読み取るため、最終的にビルド番号は同じでもコードが異なる2つの成果物が生成されます。多くの場合、この問題が発覚するのはリリースシステムへの提出時です。その段階では、ファイル名とコミット履歴だけを見ても、どちらを残すべきか判断するのは困難です。

ビルド番号管理の目的は、単に値を「自動で1増やす」ことではありません。配布可能なすべての成果物について、誰が番号を発行したのか、どのコミットに対応するのか、アーカイブ内に実際に記録された値はいくつか、という3つの問いに答えられる状態を作ることです。

バージョン番号とビルド番号を区別する

MARKETING_VERSION3.8.0 のようにユーザーに表示されるバージョンです。CURRENT_PROJECT_VERSION はビルド番号であり、数字のみで構成された増加値にする必要があります。両者のライフサイクルは異なるため、コミットタグをそのまま両方のフィールドに書き込んではいけません。

フィールド 変更するタイミング 主な用途
MARKETING_VERSION 3.8.0 製品バージョンの変更時 機能バージョンの識別
CURRENT_PROJECT_VERSION 18427 配布可能なアーカイブの作成時 同一バージョンの成果物を区別
Gitコミット a1b2c3d コミットごと ソースコードの特定
パイプライン番号 5821 ジョブの実行ごと 実行記録の特定

リポジトリには安定したバージョン番号を保存できますが、複数の実行環境からビルド番号を同時に変更してコミットすべきではありません。並列ジョブがそれぞれ「読み取り、1加算、書き戻し」を実行すると、すべての処理が成功しても同じ値を取得する可能性があります。

ビルド番号はソースコードの変更結果ではなく、パイプラインへの入力として扱います。ソースコードはその値の使い方を定義し、オーケストレーションシステムは値の一意性を保証します。

ビルド番号の発行元を一つにする

単調増加するシーケンスを選ぶ

正式なアーカイブでは、パイプラインシステムのグローバル実行番号や、内部の調整ジョブがアトミックに割り当てるシーケンスなど、中央の発行元から整数を取得します。番号は次の3条件を満たす必要があります。

  1. 同じリリース先で重複しないこと。
  2. 新しい番号が、提出済みのすべての番号より大きいこと。
  3. コミット、ブランチ、ジョブの実行記録を逆引きできること。

git rev-list --count HEAD は、履歴を書き換えない単一ブランチのプロジェクトには適していますが、シャロークローン、リベース、複数のリリースブランチを使用する環境には向きません。異なるブランチで同じカウントになる可能性があり、履歴を書き換えると数字が減ることもあります。社内向けのデバッグビルドには利用できますが、複雑なリリースフローにおける唯一の信頼できる情報源にはできません。

OpsVMで並列ジョブを実行する場合は、オーケストレーション層で先に BUILD_SEQUENCE を生成し、個々のビルドノードへ渡します。ノードは番号を受け取って使用するだけにし、番号の取得競争や書き戻しを行わないようにします。

ジョブ開始時に不正な入力を拒否する

#!/bin/zsh
set -euo pipefail

: "${BUILD_SEQUENCE:?BUILD_SEQUENCE is required}"
: "${RELEASE_VERSION:?RELEASE_VERSION is required}"

if [[ ! "$BUILD_SEQUENCE" =~ ^[0-9]+$ ]]; then
  print -u2 "BUILD_SEQUENCE must contain digits only"
  exit 64
fi

if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  print -u2 "RELEASE_VERSION must use major.minor.patch"
  exit 64
fi

入力検証は、依存関係のインストールやコンパイルより前に実行します。これにより、変数の欠落が十数分後に配布不能なアーカイブとして判明する事態を防げます。

アーカイブコマンドで番号を注入する

パイプラインから project.pbxproj を変更する必要はありません。xcodebuild コマンドの末尾でビルド設定を上書きすれば、リポジトリに未コミットの変更を残さず、ログからビルドを再現しやすくなります。

archive_path="$PWD/output/App.xcarchive"
result_path="$PWD/output/Archive.xcresult"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -destination "generic/platform=iOS" \
  -archivePath "$archive_path" \
  -resultBundlePath "$result_path" \
  MARKETING_VERSION="$RELEASE_VERSION" \
  CURRENT_PROJECT_VERSION="$BUILD_SEQUENCE" \
  clean archive

上書きする値は、同じアーカイブコマンド内で渡します。別々にすると、テストでは一方の番号を使用し、アーカイブではプロジェクトのデフォルト値を読み込む可能性があります。プロジェクトに拡張機能が含まれる場合は、メインアプリと拡張機能が同じ設定を継承しているか確認してください。リリース規定で明示的に求められていない限り、targetごとに個別の番号を生成してはいけません。

アーカイブ前に、解決後の設定を確認することもできます。

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -showBuildSettings |
awk '/MARKETING_VERSION|CURRENT_PROJECT_VERSION/ { print }'

この確認は主に、スクリプト、設定ファイル、target単位の設定によってコマンドライン引数が意図せず上書きされていないかを検出するために行います。

ログを信用せず、アーカイブを直接検証する

コマンドが正常終了しても、確認できるのはアーカイブ処理が完了したことだけです。指定した値が最終的なアプリに反映された保証にはなりません。検証スクリプトでは、.xcarchive 内にあるメインアプリの Info.plist を読み取り、パイプラインの入力値と比較します。

app_path="$(find "$archive_path/Products/Applications" \
  -maxdepth 1 -name '*.app' -type d -print -quit)"

if [[ -z "$app_path" ]]; then
  print -u2 "Application bundle not found"
  exit 65
fi

plist="$app_path/Info.plist"
actual_version=$(/usr/libexec/PlistBuddy \
  -c "Print :CFBundleShortVersionString" "$plist")
actual_build=$(/usr/libexec/PlistBuddy \
  -c "Print :CFBundleVersion" "$plist")

[[ "$actual_version" == "$RELEASE_VERSION" ]] || exit 66
[[ "$actual_build" == "$BUILD_SEQUENCE" ]] || exit 67

検証に成功したら、バージョン番号、ビルド番号、完全なコミットハッシュ、アーカイブのチェックサム、パイプライン実行識別子を1つのプレーンテキストマニフェストに記録し、成果物と一緒に保存します。人が読みやすいファイル名は便利ですが、マニフェストやアーカイブ内部のフィールドの代わりにはなりません。

再試行、ブランチ、並列実行を処理する

再試行ルールをパイプライン層で固定する

コンパイル前に失敗し、アーカイブも生成されていない場合は、同じジョブを元の番号で再試行できます。アーカイブがすでに生成、アップロード、または後続処理へ送られている場合は、新しい番号を取得してください。古い番号は記録に残し、数字を連続させるためだけに再利用してはいけません。

複数のリリースブランチが同じリリース先を共有する場合は、ビルド番号の空間も共有する必要があります。ブランチごとに 1 から始めると整然として見えますが、ブランチの合流後に衝突が発生します。成果物の由来はブランチ名、コミットハッシュ、バージョン番号で表し、ビルド番号は一意性と増加性だけを担います。

最小チェックリスト

  • 番号は中央の一つの発行元からアトミックに割り当てる。
  • ビルドノードは番号を読み取るだけで、プロジェクトファイルを変更しない。
  • アーカイブコマンドで2つのバージョンフィールドを明示的に渡す。
  • メインアプリと拡張機能の継承関係を確認する。
  • 成功後にアーカイブ内部のフィールドを読み取って比較する。
  • 各成果物について、コミット、ジョブ、チェックサムの対応関係を保存する。
  • 成果物を生成済みの失敗ジョブでは、番号を再利用しない。
  • コンソールで現在選択可能な構成を確認した後、並列ノードはビルドだけを担当し、番号の割り当ては行わない。

これらの制約を適用すると、ビルド番号はリリース直前に一時的に変更する数字ではなくなります。ソースコード、実行記録、最終成果物を一貫して結び付ける安定したインデックスになります。ロールバックや並列ジョブの衝突が発生した場合も、チームは番号からアーカイブを特定し、さらに一意のコミットとパイプライン実行までさかのぼれます。

よくある質問

Gitのコミット数をiOSのビルド番号に使えますか?

履歴を書き換えない単一ブランチなら利用できます。リベース、浅いクローン、複数のリリースブランチがある場合は、CIが集中管理する単調増加の整数を使います。

失敗したジョブの再実行では同じビルド番号を使うべきですか?

アーカイブや配布用成果物が作られていなければ再利用できます。成果物が生成済みなら新しい番号を発行し、両方の実行を同じコミットへ関連付けます。

専用物理ノード

OpsVMでクラウドMacワークフローを構築

Apple Silicon構成3種類と販売中の6ノードから選択できます。実際の利用可能状況はコンソールでリアルタイムに確認できます。

構成を選んで注文する