ハイライト
Xcode 13 では、マルチプラットフォーム フレームワーク ターゲット、ビルド ルール、スクリプトの入出力依存関係、xcfilelist および xcconfig 構文機能が追加され、複雑なプロジェクトで使用するターゲットの数が減り、クロスプラットフォーム ビルドを管理し、作業の重複を減らすことができます。
主要内容
クロスプラットフォーム アプリを長期間開発すると、プロジェクト ファイルの保守が困難になります。
Fruta は、macOS、iOS、watchOS を同時にビルドするサンプル プロジェクトです。これには、プラットフォームごとに 1 つずつ、合計 3 つのフレームワーク ターゲットがあります。これらには同じ共有コードのセットが含まれていますが、macOS 固有のファイルのみが異なります。
この構造の問題は単純明快です。ビルド設定を変更するたびに、3 つのターゲットを同期する必要があります。ソース ファイルを追加するたびに、3 つのソースのコンパイル ビルド フェーズがすべて追加されていることを確認してください。一度でも失敗すると、プラットフォームはビルド時に失敗します。
Xcode 13 によって与えられる最初のステップは、複数のプラットフォーム フレームワークをマルチプラットフォーム フレームワーク ターゲットに結合することです。開発者が [サポートされているプラットフォーム] を [任意のプラットフォーム] に設定すると、Xcode は自動的に [マルチプラットフォーム ビルドを許可] をオンにします。このターゲットは、必要に応じて、サポートされているプラットフォームごとに 1 回ビルドされます。
プラットフォームの違いはそのまま維持されます。フルッタさんのIngredient+macOS.swiftこれは macOS ビルドにのみ参加する必要があるため、[ソースのコンパイル] でこのファイルのプラットフォーム フィルターを設定し、macOS のみをチェックします。
プロジェクトが大きくなると、ビルド速度の問題も発生します。以前は、多くのチームがスクリプトの実行フェーズにカスタム スクリプトを詰め込んでいました。スクリプトはすべてのファイルを一度に処理するため、Xcode がどの入力がどの出力に影響を与えるかを認識することが難しくなり、並列実行が困難になります。
このセッションの 2 番目の焦点は、ビルド ルールに分割できる作業を移行することです。それぞれ.recipeファイルは独立して生成されます.compiledrecipe書類。ビルド システムは、入力ファイルごとにタスクを分割し、依存関係に基づいてタスクをスケジュールできます。
分割できない残りの作業は、引き続きスクリプトフェーズに配置されます。たとえば、複数の説明ファイルを 1 つのアーカイブ ファイルにマージします。重要なのは、入力ファイルのリストと出力ファイルを宣言することです。 Xcode は、スクリプトがどのファイルを読み取り、どのファイルに書き込むかを認識して、正しい順序を確保し、保守的なシリアル化を回避します。
最後の問題はビルド設定にあります。複雑なプロジェクトの一般的なアプローチは、Xcode UI で多くの設定をクリックすることです。時間が経つと、誰が補償対象なのかを確認することが難しくなります。 Xcode のレベル ビューには、デフォルト値、プロジェクト xcconfig、プロジェクト設定、ターゲット xcconfig、ターゲット設定、および最終的に解決された値を表示できます。.xcconfigファイルは構成をテキスト ファイルに保存して、再利用、レビュー、組み合わせを容易にします。
詳細
マルチプラットフォーム フレームワーク ターゲット
(01:10) Xcode 13 はマルチプラットフォーム フレームワーク ターゲットをサポートしています。 Fruta には当初、macOS、iOS、watchOS という 3 つのフレームワーク ターゲットがありました。講演の中で、macOS フレームワーク ターゲットの [サポート対象プラットフォーム] を [任意のプラットフォーム] に変更し、Xcode が自動的に [マルチプラットフォーム ビルドを許可] を [はい] に設定しました。
Supported Platforms = Any Platform
Allow Multiplatform Builds = Yes
キーポイント:
Supported Platforms = Any Platform: 同じフレームワーク ターゲットで複数の Apple プラットフォームをサポートします。Allow Multiplatform Builds = Yes: サポートされている各プラットフォームの必要に応じて、このターゲットをビルドするようにビルド システムに通知します。- このセットアップにより、ビルド フェーズとビルド設定の重複が減ります。
(03:06) プラットフォーム固有のソース コードは、プラットフォーム フィルターを通じて処理されます。フルッタさんのIngredient+macOS.swiftmacOS ビルドでのみコンパイルされます。
Compile Sources:
Ingredient+macOS.swift
Filters: macOS
キーポイント:
Compile Sources: 現在のターゲットのコンパイルに参加するソース ファイルを制御します。Ingredient+macOS.swift: 例では macOS 固有のファイル。Filters: macOS: このファイルは macOS ビルドにのみ含めてください。
スキームのビルド順序と暗黙的な依存関係
(04:59) Xcode のスキーム ビルド オプションでは、Apple は依存関係の順序を使用することを推奨しています。依存関係グラフに従ってターゲットを並行して構築します。手動順序は非推奨になり、スキームの順序がプロジェクトの依存関係と一致しない場合、ビルドが遅くなり、サイクル エラーが発生する可能性があることが講演で明確に述べられています。
Scheme > Edit Scheme > Build:
Build Order = Dependency Order
Find Implicit Dependencies = On
キーポイント:
Build Order = Dependency Order: Xcode に依存関係グラフに従ってビルド順序を調整させます。Find Implicit Dependencies = On: Xcode がリンカー フラグとリンクされたライブラリに基づいてターゲットの依存関係を自動的に推測できるようにします。- 一部の依存関係は明示的なターゲット依存関係では直接追加できないため、この組み合わせはプロジェクト間の依存関係シナリオに適しています。
ビルド ルールを使用して独立した入力ファイルを並列処理する
(06:29) Fruta のプロセス レシピ スクリプトは元々、複数のレシピ ファイルを順番に処理していました。各レシピの計算は互いに独立しているため、Lecture はそれをビルド ルールに移動しました。
File Pattern: *.recipe
Output Files:
$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).compiledrecipe
キーポイント:
File Pattern: *.recipe: このルールによって処理される入力ファイルと一致します。$(DERIVED_FILE_DIR): Apple では、生成されたファイルをビルド システムによって管理されるディレクトリに書き込むことをお勧めします。$(INPUT_FILE_BASE): 現在の入力ファイルのベース名を取得します。.compiledrecipe: 各入力ファイルは出力ファイルに対応します。
(08:22) ビルド ルール スクリプトは、一度に 1 つの入力ファイルを処理します。講義内のスクリプトは、現在の入力パスと最初の出力パスを外部スクリプトに渡します。
"$SRCROOT/Scripts/gen-code.sh" "$SCRIPT_INPUT_FILE" "$SCRIPT_OUTPUT_FILE_0"
キーポイント:
"$SRCROOT/Scripts/gen-code.sh": プロジェクト内で外部スクリプトを呼び出します。スクリプトの内容はプロジェクト ファイルの外に残されるため、ソース コードの管理が容易になります。"$SCRIPT_INPUT_FILE": 現在のルールによって処理されている入力ファイルの絶対パス。"$SCRIPT_OUTPUT_FILE_0": [出力ファイル] テーブルの最初の出力パス。- 二重引用符: パス内のスペースと特殊文字を処理します。
(08:45) ビルド ルールは、デフォルトでアーキテクチャごとに 1 回実行されます。たとえば、Mac App ターゲットに arm64 と x86_64 があり、4 つの入力を乗算した場合、8 回実行されます。 Fruta の出力は CPU アーキテクチャとは関係がないため、アーキテクチャごとに 1 回実行はキャンセルされます。
Build Rule:
Run once per architecture = Off
キーポイント:
Run once per architecture = Off: アーキテクチャに依存しない同じ出力が繰り返し生成されるのを防ぎます。- 該当するシナリオ: CPU アーキテクチャに関係のないテキスト、リソース インデックス、構成ファイル、その他の製品を生成します。
- 出力がオブジェクト コードなどのアーキテクチャに依存するアーティファクトである場合、デフォルトの動作を保持する必要があります。
スクリプトフェーズの入力と出力を宣言する
(10:15) 一部のジョブでは、すべての入力を一度に読み取る必要があります。 Fruta は、複数の記述ファイルを 1 つのファイルにマージします。この種の作業は、脚本段階で続行するのが適しています。
# Package up the recipes.
echo "packaging..."
for i in $(seq 0 $(expr ${SCRIPT_INPUT_FILE_LIST_COUNT} - 1)) ; do
infile_="SCRIPT_INPUT_FILE_LIST_$i"
eval infile=\$$infile_
while IFS= read -r file; do
cat "$file" >> "$SCRIPT_OUTPUT_FILE_0"
done < "$infile"
done
キーポイント:
SCRIPT_INPUT_FILE_LIST_COUNT: 入力ファイルリストの数。SCRIPT_INPUT_FILE_LIST_$i: i 番目の入力ファイルリスト変数名をインデックスで取得します。eval infile=\$$infile_: 変数名を実際のファイル リスト パスに解析します。while IFS= read -r file: xcfilelist のファイル パスを 1 行ずつ読み取ります。cat "$file" >> "$SCRIPT_OUTPUT_FILE_0": 各入力ファイルの内容を最初の出力ファイルに追加します。
(11:34) 入力ファイルが多い場合は、ビルドフェーズファイルリスト(.xcfilelist)管理。 1 行に 1 つのパスがあり、#冒頭のメモ。
$(SRCROOT)/Recipes/Instructions/berry-blue.md
$(SRCROOT)/Recipes/Instructions/carrot-chops.md
$(SRCROOT)/Recipes/Instructions/hulking-lemonade.md
$(SRCROOT)/Recipes/Instructions/kiwi-cutie.md
$(SRCROOT)/Recipes/Instructions/lemonberry.md
$(SRCROOT)/Recipes/Instructions/love-you-berry-much.md
$(SRCROOT)/Recipes/Instructions/mango-jambo.md
$(SRCROOT)/Recipes/Instructions/one-in-a-melon.md
$(SRCROOT)/Recipes/Instructions/papas-papaya.md
$(SRCROOT)/Recipes/Instructions/pina-y-coco.md
キーポイント:
$(SRCROOT): プロジェクトのソースコードのルートディレクトリ。- 各行: スクリプトフェーズ入力ファイル。
.xcfilelist: プロジェクト ファイルの競合を減らすために、大量の入力をプロジェクト ファイルからテキスト ファイルに移動します。
(11:57) スクリプトフェーズでは、このファイルリストを参照し、出力パスを宣言する必要があります。
Input File Lists:
$(SRCROOT)/FileList.xcfilelist
Output Files:
$(PROJECT_TEMP_DIR)/instructions.mdarchive
キーポイント:
Input File Lists: スクリプトがどの入力リストを読み取るかを Xcode に指示します。$(SRCROOT)/FileList.xcfilelist: 講義内のファイルリストへのパス。Output Files: スクリプトがどのファイルを書き出すかを Xcode に指示します。$(PROJECT_TEMP_DIR)/instructions.mdarchive: マージされた出力ファイルのパス。
(12:50) スクリプト フェーズとビルド ルールは、一連の環境変数を受け取ります。スクリプトは、これらの変数を通じて Xcode によって解析された入力パスと出力パスを読み取る必要があります。
SCRIPT_INPUT_FILE_COUNT
SCRIPT_INPUT_FILE_n
SCRIPT_INPUT_FILE_LIST_COUNT
SCRIPT_INPUT_FILE_LIST_n
SCRIPT_OUTPUT_FILE_COUNT
SCRIPT_OUTPUT_FILE_n
SCRIPT_OUTPUT_FILE_LIST_COUNT
SCRIPT_OUTPUT_FILE_LIST_n
キーポイント:
SCRIPT_INPUT_FILE_COUNT:入力ファイルテーブル内のパスの数。SCRIPT_INPUT_FILE_n: n 番目の入力ファイルの絶対パス。n は 0 から始まります。SCRIPT_INPUT_FILE_LIST_n: n 番目に解析された入力ファイル リストのパス。SCRIPT_OUTPUT_FILE_n: n 番目の出力ファイルの絶対パス。SCRIPT_OUTPUT_FILE_LIST_n: n 番目の解析された出力ファイル リストのパス。
作業の重複を避けるために集約ターゲットを使用する
(13:15) マルチプラットフォーム ターゲットは、複数のプラットフォーム用に構築されます。 Fruta の SmoothieKit は、iOS 用に 1 回、watchOS 用に 1 回ビルドされます。元のスクリプト フェーズは、両方のビルドで同じ出力パスを書き込もうとします。 Xcode では、ビルド全体で指定されたパスの出力を生成できるのは 1 つのタスクだけであることが必要なため、ビルドは失敗します。
Aggregate target: Resources
Run Script: package recipes
SmoothieKit target:
Target Dependencies: Resources
キーポイント:
Aggregate target: Resources: ホスト プラットフォームに依存しないスクリプト処理。Run Script: package recipes: 元のフレームワーク ターゲットのスクリプト、入力、出力を集約ターゲットに移動します。Target Dependencies: Resources: SmoothieKit をこの集計ターゲットに依存させます。- 結果: スクリプトは 1 回だけ実行され、iOS と watchOS の両方のバリアントが正しい順序でビルドされます。
ビルド設定の階層と xcconfig 構文
(16:46) ビルド設定のレベル ビューには、複数の構成レベルが表示されます。トークで示される順序には、SDK のデフォルト値、プロジェクト レベルの構成設定ファイル、プロジェクト レベルの設定、ターゲット構成設定ファイル、ターゲット レベルの設定、そして最終的に解決された値が含まれます。
Default value
Project-level .xcconfig
Project-level settings
Target-level .xcconfig
Target-level settings
Resolved value
キーポイント:
Default value: 現在の SDK によって提供されるデフォルト値。Project-level .xcconfig: プロジェクトレベルの構成ファイルの値。Project-level settings: プロジェクト ファイル内のプロジェクト レベルの設定。Target-level .xcconfig: ターゲットレベルのプロファイル値。Target-level settings: プロジェクト ファイル内のターゲット レベルの設定。Resolved value: Xcode 解析後の最終値。
(18:17).xcconfigの基本的な形式は、名前、代入演算子、および値です。
MY_BUILD_SETTING_NAME = "A build setting value"
キーポイント:
MY_BUILD_SETTING_NAME:ビルド設定名。=: 代入演算子。"A build setting value":設定値。
(18:30).xcconfig条件付き構文をサポートします。このトークでは、構成、アーキテクチャ、および SDK 条件をリストしており、SDK 条件はワイルドカード マッチングをサポートしています。
MY_BUILD_SETTING_NAME = "A build setting value"
MY_BUILD_SETTING_NAME[config=Debug] = -debug_flag
MY_BUILD_SETTING_NAME[arch=arm64] = -arm64_only
MY_BUILD_SETTING_NAME[sdk=iphone*] = -ios_only
キーポイント:
- 1 行目: デフォルト値。
[config=Debug]: デバッグ構成でのみ有効です。[arch=arm64]: arm64 アーキテクチャでのみ有効です。[sdk=iphone*]: iPhone SDKと一致、*ワイルドカード処理に使用されます。
(19:50) ビルド設定は他のビルド設定と組み合わせることができます。評価は内側から外側へと行われます。
IS_BUILD_SETTING_ENABLED = NO
MY_BUILD_SETTING_NO = -use_this_one
MY_BUILD_SETTING_YES = -use_this_instead
MY_BUILD_SETTING = $(MY_BUILD_SETTING_$(IS_BUILD_SETTING_ENABLED))
キーポイント:
IS_BUILD_SETTING_ENABLED = NO:コントロール設定値。MY_BUILD_SETTING_NO:制御値はNO実際に使用される設定。MY_BUILD_SETTING_YES:制御値はYES実際に使用される設定。$(MY_BUILD_SETTING_$(IS_BUILD_SETTING_ENABLED)): まず内部を見つけますNO、そして分析しますMY_BUILD_SETTING_NO。
(21:08) ビルド設定の評価ではパス演算子もサポートされています。
$(MY_PATH:dir)
$(MY_PATH:file)
$(MY_PATH:base)
$(MY_PATH:suffix)
$(MY_PATH:standardizepath)
キーポイント:
:dir: ディレクトリを取得します。:file:ファイル名を取得します。:base: 拡張子なしのベース名を取得します。:suffix: 拡張機能を取得します。:standardizepath: 標準化されたパス。
(21:21) 各パス演算子には対応する置換形式があり、デフォルト演算子は空の値のデフォルト値を提供します。
$(MY_PATH:dir=/tmp)
$(MY_PATH:file=/better.swift)
$(MY_PATH:base=another)
$(MY_PATH:suffix=m)
$(MY_PATH:default=YES)
キーポイント:
:dir=/tmp: ディレクトリ部分を置き換えます。:file=/better.swift: ファイル名の部分を置き換えます。:base=another:ベース名を置き換えます。:suffix=m:拡張子を置き換えます。:default=YES: 元の値が空の場合に使用されますYES。
(21:36).xcconfig他のファイルを含めることもできます。 required include にはファイルが存在する必要があります。見つからない場合はコンパイルエラーとなります。オプションのインクルードは、ファイルが存在する場合にのみロードされます。
#include "common.xcconfig"
#include? "ci.xcconfig"
キーポイント:
#include "common.xcconfig": ファイルを含める必要があります。ファイルが存在しない場合は失敗します。#include? "ci.xcconfig": オプションの組み込み。ファイルが存在しない場合でも失敗しません。- パスは、Xcode プロジェクト ファイルの場所を基準にして解決されます。
(22:20) この講演では CI シナリオが示されています。開発マシンは、Swift コンパイラが時間のかかる式についてより早く警告することを望んでいますが、CI マシンは遅いため、しきい値が高くなります。この方法では、オプションで ci.xcconfig を common.xcconfig に組み込み、デフォルトの演算子を使用してデフォルト値を設定します。
// common.xcconfig
#include? "ci.xcconfig"
OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -warn-long-expression-type-checking=$(MAX_EXPRESSION_TIME:default=200)
// ci.xcconfig
MAX_EXPRESSION_TIME = 500
キーポイント:
#include? "ci.xcconfig": オーバーレイ ファイルを CI マシンにのみロードします。OTHER_SWIFT_FLAGS = $(inherited) ...: 既存の Swift コンパイル パラメーターを保持し、新しいパラメーターを追加します。$(MAX_EXPRESSION_TIME:default=200): ローカル設定がない場合は 200 を使用します。MAX_EXPRESSION_TIME = 500:CI マシンは、別のファイルを介してデフォルトをオーバーライドします。
重要ポイント
-
やるべきこと: iOS、macOS、watchOS の共有フレームワークをマルチプラットフォーム ターゲットにマージします。 実行する価値がある理由: Xcode 13 のマルチプラットフォーム フレームワーク ターゲットは、ビルド フェーズとビルド設定を統合して、同期エラーを減らすことができます。 開始方法: 既存のフレームワーク ターゲットを選択し、サポートされるプラットフォームを任意のプラットフォームに設定し、プラットフォーム フィルターを使用してプラットフォーム固有のファイルを処理します。
-
作業内容: リソース コード生成スクリプトをスクリプト実行フェーズからビルド ルールに分割します。 実行する価値がある理由: 各入力ファイルが個別に出力を生成する場合、ビルド ルールにより、Xcode は並行してスケジュールを設定し、入力と出力の依存関係に基づいて再実行するかどうかを決定できます。 開始方法: ビルド ルールを追加する
*.recipeこのタイプのファイル モードでは、出力は次の場所に書き込まれます。$(DERIVED_FILE_DIR)、スクリプトは次のようになりますSCRIPT_INPUT_FILEそして書きますSCRIPT_OUTPUT_FILE_0。 -
やるべきこと: すべてのカスタム スクリプトの入力ファイル リストと出力ファイルを完成させます。 これを実行する価値がある理由: Xcode は、スクリプトがどのファイルを読み書きするかを認識すると、ファイルを正しい順序に配置し、保守的なシリアル化を減らすことができます。 開始方法: 作成する
.xcfilelist、スクリプトの実行フェーズの入力ファイル リストでそれを参照し、出力ファイルにスクリプト製品のパスを書き込みます。 -
何をすべきか: プラットフォームに依存しないパッケージ化スクリプトを集約ターゲットに移動します。 実行する価値がある理由: マルチプラットフォーム ターゲットは複数のプラットフォーム用に構築され、同じ出力パスは 1 つのタスクでのみ生成できます。集約ターゲットを使用すると、共有作業を 1 回だけ実行できます。 開始方法: Resources などの集約ターゲットを作成し、そこにスクリプト、入力、出力を移動して、フレームワーク ターゲットにターゲットの依存関係を追加させます。
-
やるべきこと: を使用します。
.xcconfigデバッグ、ローカル、CI のコンパイル パラメータを管理します。 実行する価値がある理由: 構成をテキスト ファイルに保存すると、環境ごとに再利用、レビュー、結合することができます。 開始方法: 公開設定を入れますcommon.xcconfig、使用#include? "ci.xcconfig"CI カバレッジ値をロードするには、次を使用します。$(MAX_EXPRESSION_TIME:default=200)ローカルのデフォルトを提供します。
関連セッション
- Xcode Cloud の紹介 — Xcode Cloud がビルド、テスト、配布を Apple の CI/CD サービスにどのように接続するかを学びます。
- Xcode Cloud ワークフローを探索する — ワークフローを使用して分析、テスト、アーカイブ、配布を自動化する方法を学びます。
- 高度な Xcode Cloud ワークフローをカスタマイズする — Xcode Cloud のスクリプティング、ソース コード管理、サービス統合を詳細にカスタマイズします。
- 静的アナライザーを使用してバグを早期に検出する — Xcode 静的アナライザーを使用して、実行前にコードの問題を見つけます。
- テストを繰り返して信頼性の低いコードを診断する — 繰り返しテストを実行して、散発的な障害や不安定なコードを特定します。
コメント
GitHub Issues · utterances