大規模な iOS リポジトリをクラウド Mac 上で数週間運用していると、あるブランチで突然スクリプトが argument list too long を返す一方、隣接するブランチでは引き続き正常にアーカイブできることがあります。まず DerivedData を削除したり、Xcode の一時的な不具合だと決めつけたりしないでください。このエラーは通常、execve が返す E2BIG に起因します。コマンド引数、各引数の終端文字、継承された環境が、プロセスに許容された領域を合計で使い切った状態です。
障害が発生している層を特定する
最初に、完全なコマンドと終了ステータスを保存します。ログに「スクリプトフェーズが失敗した」としか表示されない場合は、該当する Run Script で一時的に set -x を有効にし、失敗しているのが find、rm、アーカイブツール、コードジェネレーター、コンパイラドライバーのどれなのかを確認します。環境全体を公開ログへ出力してはいけません。トークンが含まれている可能性があるため、記録するのは変数名、バイト数、機密情報を除去したコマンド構造だけにします。
典型的な原因は三つあります。ワイルドカードによって一度に数万件のパスが展開されるケース、スクリプトがすべてのソースファイルを一つの変数へ連結するケース、CI が巨大な JSON、証明書の内容、複数行の設定を環境変数として注入するケースです。長いワークスペースパスや、繰り返し指定される -I、-F、-D 引数も、利用可能な余裕を継続的に消費します。
小規模なブランチで実行できても、コマンドの構造が正しいとは限りません。ファイル数が増えたときに初めて失敗するのであれば、多くの場合、入力の受け渡し方法に上限を考慮した設計がありません。
引数と環境の余裕を測定する
macOS で利用できる上限は、コマンド文字列の長さだけでは判断できません。まず、ビルドジョブと同じユーザーおよび起動方法で測定します。
getconf ARG_MAX
python3 - <<'PY'
import os
limit = os.sysconf("SC_ARG_MAX")
env_bytes = sum(len(k) + len(v) + 2 for k, v in os.environ.items())
largest = sorted(
((len(k) + len(v) + 2, k) for k, v in os.environ.items()),
reverse=True
)[:10]
print("arg_max", limit)
print("environment_bytes", env_bytes)
for size, key in largest:
print(size, key)
PY
この結果は比較のために使うものであり、全量を使用可能な予算とみなしてはいけません。システムには、ポインタ、終端文字、プロセス起動時のオーバーヘッドを格納する領域も必要です。運用上は十分な余裕を確保し、環境のサイズが過去の基準値から急増していないかを確認します。
肥大化した変数を特定する
まず PATH、検索パス、一時ディレクトリ、依存関係マネージャーの引数、CI が注入する変数を確認します。JSON 設定が数十 KB に達している場合は、アクセス権を制限した一時ファイルへ書き込み、子プロセスにはそのファイルパスだけを渡します。PATH を繰り返し追加する場合は、各ステップで再連結するのではなく、先に重複を取り除きます。
ツールチェーンを壊さずに継承環境を縮小する
完全な Xcode ビルドを空の環境から直接開始してはいけません。HOME、PATH、一時ディレクトリ、デベロッパーディレクトリが欠けると、新たな障害を引き起こします。より安全なのは、個々のツールに対して許可リストを用意する方法です。
env -i \
HOME="$HOME" \
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
TMPDIR="$TMPDIR" \
DEVELOPER_DIR="$DEVELOPER_DIR" \
/bin/zsh -lc 'xcrun --find xcodebuild'
本番ジョブでは、実際の依存関係に応じてロケール、キャッシュディレクトリ、プロキシ設定も追加する必要があります。機密情報は有効期間の短いファイル経由で渡し、使用後に削除します。通常の真偽値フラグや短い識別子は、引き続き環境変数として保持できます。SetMini 上の自動化ジョブでも、環境の初期化を一つのエントリースクリプトに集約し、対話型シェル、CI runner、Xcode スクリプトがそれぞれ同じ設定を追加しないようにします。
長いファイル一覧をコマンドラインの外へ移す
汎用スクリプトでは NUL 文字で区切る
ファイル名にはスペースや改行が含まれる可能性があるため、for f in $(find ...) を使用してはいけません。バッチ処理をサポートするツールでは、find -print0 と xargs -0 を組み合わせられます。
find "$PWD/Artifacts" -type f -name '*.dSYM' -print0 |
xargs -0 -n 50 /usr/bin/file
-n 50 によって、各バッチに明確な上限を設定できます。ツールが標準入力から一覧を読み込める場合は、プロセスの起動回数を減らすため、標準入力を優先して使用します。
Xcode フェーズではファイルリストを使用する
Run Script の入力と出力は .xcfilelist に記述し、Input File Lists と Output File Lists に設定します。これにより Xcode が依存関係を追跡でき、スクリプト側で数千件のパスを一つのコマンドへ展開する必要もなくなります。コンパイラやリンカーがレスポンスファイルを明示的にサポートしている場合は、固定的な引数をレスポンスファイルへ書き込めます。ただし、すべてのサードパーティーツールが @file 構文を解釈できると仮定してはいけません。
ビルド設定から大量のオプションを渡している場合、共通値は .xcconfig に移します。スクリプトが受け取るのは必要最小限の引数だけにし、パスの集合はファイルリスト、構造化設定は一時ファイルで渡します。この三種類の入力を分離すると、ログの監査も容易になります。
増加に対するゲートと回帰チェックを設ける
修正後は、少なくともファイル数が最も多く、パスが最も長いブランチでクリーンビルドを一度実行し、続けてインクリメンタルビルドを一度実行します。ファイル名にスペースが含まれていてもスクリプトが正しく動作すること、バッチ処理の失敗時に非ゼロのステータスを返すこと、終了処理ですべての一時ファイルが削除されることを確認します。
CI の開始時に ARG_MAX、環境の総バイト数、最大の変数名を記録できますが、変数値は記録しません。環境サイズについてチームの基準値を定め、しきい値を超えた時点で警告を出します。システムがプロセスの作成を拒否するまで待ってはいけません。増加するファイル集合については、ファイル数と最長パスの長さも同時に記録します。
最終的な目標は、現在のコマンドをどうにか上限未満へ収めることではありません。リポジトリの規模に応じてコマンド長が線形に増加しない構造へ変えることです。環境を小さく保ち、ファイル集合を専用の経路で渡し、バッチ処理に固定上限を設けることで、E2BIG は偶発的な障害ではなく、事前に検出可能な設定上の問題になります。
よくある質問
同じビルドコマンドが特定のブランチだけで失敗するのはなぜですか?
引数と継承環境が同じ上限を共有するためです。ファイル数、パス長、依存関係、注入される変数が増えたブランチだけ上限を超える場合があります。
xargsとレスポンスファイルはどう使い分けますか?
一般的な一括処理はNUL区切りのxargsを使い、コンパイラやリンカが正式対応している場合はレスポンスファイルを優先します。
ARG_MAXを増やせば解決できますか?
恒久対策にはなりません。環境変数を減らし、重複する引数を整理し、長い入力をファイル、標準入力、または上限付きのバッチへ移してください。
いつでも接続できるクラウドMacに開発環境を移行
2種類のApple Silicon構成と4つのデータセンターから選択できます。リソースは他の利用者と共有されず、実際の利用状況はコンソールでリアルタイムに確認できます。