Swift Package はローカルプレビューでは正常に動作していても、クラウド Mac のアーカイブジョブに組み込むと、アイコンが空白になったり、デフォルトの文言が表示されたり、JSON の読み込みに失敗したりすることがあります。特に厄介なのは、コンパイルエラーではなく、パイプラインが成功したままになる点です。SwiftPM は Bundle.module を処理しますが、必要なリソースがすべて宣言されているか、参照名が一致しているか、最終的なアプリバンドルにリソースが含まれているかまでは判断してくれません。テスターが画面を開いて欠落に気づくのを待つのではなく、リソースをビルド成果物の一部として検証する必要があります。
リソースが欠落する3つの箇所を特定する
調査を始める前に、どの層で問題が発生しているかを切り分けます。第1のケースは、ソースディレクトリにはファイルが存在するものの、Package.swift の target に対応するリソースが宣言されていない場合です。第2のケースは、リソースの .bundle は生成されているものの、呼び出し側のコードがメインアプリの bundle を使用している場合です。第3のケースは、ファイルがアプリバンドルに含まれていても、ファイル名の大文字と小文字、サブディレクトリ、またはローカライズ用ディレクトリが実行時の検索条件と一致していない場合です。
まず、画像1点、JSONファイル1点、ローカライズ文字列1件を読み込む最小限の再現用エントリーポイントを用意しておくことを推奨します。3種類すべてが失敗する場合は、bundle の選択を優先的に確認します。特定のファイルだけが失敗する場合は、リソースルールとパスを先に確認します。
「コンパイル成功」が証明するのは、コンパイラが現在の入力を受理したことだけです。実行時に必要なファイルが成果物に含まれていることまでは保証しません。
Package.swift では、.process と .copy のどちらを使用するかを明示します。画像、リソースディレクトリ、ローカライズコンテンツには通常 .process を使用し、元のディレクトリ構造やバイト列を維持する必要があるファイルに限って .copy を使用します。リポジトリのディレクトリ全体をリソースバンドルへ一括コピーしないでください。テストサンプル、一時出力、さらには配布すべきでない設定まで含まれるおそれがあります。
マニフェストでリソース契約を固定する
リソースの検証では、ファイル数を比較するだけでは不十分です。リソースの追加、古いリソースの削除、ファイル名の変更があっても、総数は変わらない場合があります。より確実な方法は、リポジトリ内に ci/expected-resources.txt を置き、リソース bundle のルートを基準としたパスを1行ずつ記録することです。
Assets.car
Defaults/config.json
en.lproj/Localizable.strings
zh-Hans.lproj/Localizable.strings
このマニフェストには、実行時に必須となるファイルだけを含めます。オンデマンドでダウンロードするコンテンツ、テストフィクスチャ、開発用プレビューリソースを混在させてはいけません。リソースの名前を変更する場合は、コードレビューでマニフェストの変更も同時に提示します。これにより、成果物の契約がどのように変わったかをレビュー担当者が確認できます。
リソースディレクトリ自体についても逆方向の検査が必要です。ソースには存在するものの宣言されていないファイルには警告を出し、マニフェストで要求されているのにビルド結果に存在しないファイルがあれば直ちに失敗させます。前者はリポジトリの整理に役立ち、後者は成果物の完全性を守ります。
ビルド後にアプリバンドルを直接検査する
並列実行された別のジョブが残した古い bundle によって検査が誤って成功しないよう、ジョブごとに独立した DerivedData を使用します。次のスクリプトはシミュレータ向けビルドの例です。実際のパイプラインでは scheme を置き換え、bundle 名の照合ルールをプロジェクトの実際のモジュール名に合わせて変更します。
set -euo pipefail
DERIVED="${RUNNER_TEMP:-/tmp}/resource-check-${BUILD_ID:-local}"
rm -rf "$DERIVED"
xcodebuild \
-scheme ExampleApp \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$DERIVED" \
build
APP=$(find "$DERIVED/Build/Products" -type d -name 'ExampleApp.app' -print -quit)
test -n "$APP"
COUNT=$(find "$APP" -type d -name '*FeatureKit*.bundle' | wc -l | tr -d ' ')
test "$COUNT" = "1"
BUNDLE=$(find "$APP" -type d -name '*FeatureKit*.bundle' -print -quit)
while IFS= read -r item; do
test -z "$item" && continue
test -e "$BUNDLE/$item" || {
printf 'missing resource: %s\n' "$item" >&2
exit 1
}
done < ci/expected-resources.txt
ここでは意図的に、最初に bundle の数が1つであることを検証しています。最初に一致した結果をそのまま使用すると、古い成果物、テスト用 bundle、同名のモジュールによって問題が隠れる可能性があります。また、検査対象は DerivedData の中間ディレクトリではなく、.app 内部の bundle でなければなりません。最終的な埋め込み結果を表すのは前者ではなく後者だからです。
頻出する落とし穴をゲートに変える
リソースの種類ごとに必要な検証方法は異なります。すべてを test -e だけで済ませてはいけません。
| リソースの種類 | 最低限の検査 | よくある失敗 |
|---|---|---|
| JSON | ファイルが存在し、解析できること | 空ファイルがコピーされている、または形式が不正 |
| ローカライズ文字列 | 対象言語のディレクトリが存在し、キーを読み取れること | 言語ディレクトリ名が誤っている、デフォルト文言にフォールバックする |
| 画像とカラー | コンパイル済みリソースが存在し、スモークテスト用画面で読み込めること | 名前の大文字と小文字が一致しない、またはメイン bundle を誤って使用している |
| テンプレートファイル | ハッシュまたは重要フィールドを検証すること | 生成処理がリポジトリ内のバージョンを上書きしている |
macOS で一般的に使用される作業ボリュームは大文字と小文字を区別しないため、IconDark と icondark の不一致が開発マシンでは表面化しないことがあります。ファイルシステムによる偶然の許容動作に依存せず、マニフェストからパスを取り出し、実際のパスと大文字・小文字を区別して文字単位で比較してください。JSON にはさらにパーサーを実行し、プロパティリストには plutil -lint を使用します。文字列ディレクトリについては、少なくとも必須言語と重要なキーを確認します。
Package で生成プラグインを使用している場合は、リソース検証より前に生成処理が実行されることを確認し、生成先をそのジョブ専用の一時パスに限定します。複数のジョブが同じ生成ディレクトリを共有すると、あるジョブが別ブランチのファイルを読み込み、再現が非常に困難な偽の成功を引き起こす可能性があります。
証拠を保存し、ジョブを再実行可能に保つ
失敗時に DerivedData 全体をアップロードしないでください。容量が大きくノイズも多いうえ、長期保存に適さないパス情報が含まれている可能性があります。より実用的な証拠パッケージには、リソースマニフェスト、実際の bundle のファイルツリー、ビルドログの末尾、パーサーのエラー、現在のコミット識別子を含めます。ファイルツリーは次のように生成できます。
find "$BUNDLE" -print \
| sed "s#^$BUNDLE/##" \
| LC_ALL=C sort \
> resource-bundle-tree.txt
ログとファイルツリーをアーカイブする前に、アクセストークン、ユーザーディレクトリ、一時的な認証情報を削除してください。ジョブの終了後には、独立した DerivedData と一時生成ディレクトリを削除し、次回の実行を必ず空のディレクトリから開始します。DPLYMAC 上の並列ランナーでも同じ原則に従う必要があります。ダウンロードキャッシュはジョブ間で再利用できますが、識別されていないビルド出力を再利用してはいけません。
最終的なゲートでは、宣言で何が要求されているか、最終的なアプリバンドルに何が含まれているか、実行時に実際の名前で読み取れるか、という3つの問いに答えられなければなりません。この3層それぞれに検査可能な結果を残せば、SwiftPM リソースの欠落は偶発的な画面不具合ではなく、コミット段階で特定できる通常のビルドエラーになります。
よくある質問
SwiftPMパッケージがビルドできても実行時にリソースが見つからないのはなぜですか?
ビルド成功は参照名や大小文字、最終的な配置まで保証しません。生成された.appと.bundleを直接調べ、実行時の名前と一致するか確認する必要があります。
検証対象はソースディレクトリとビルド成果物のどちらですか?
両方を確認します。未宣言ファイルはソース側で検出し、配布物に正しく組み込まれたかどうかは最終的なビルド成果物で判定します。
検証済みのワークフローを専有物理ノードで実行
タスクに合わせて M4 または M4 Pro、対象ノード、課金期間を選択し、注文前に構成と追加オプションを確認してください。