はじめに
今までXcodeGenを使用したことがなかったので、お勉強がてらXcodeGenを用いてマルチモジュール構成のプロジェクトを生成するサンプルプロジェクトを作成しました。
他記事でも言われていましたが、そこそこ学習コストが高く色々と躓いたので、その過程で得た知見などを紹介できればと思います。
モチベーション
XcodeGenを学習するモチベーションは以下の通りです。
- SPMでもマルチモジュール構成が実現できるが、どういった違いがあるのか知りたい
はじめはプロジェクトファイル(.pbxproj)をチーム開発でリポジトリ管理したときに、
コンフリクト祭りが起きる問題に今までのチーム開発でも感じていたので、
それを解決するツールとして導入を検討するために学習しようと思っていました。
しかし、Xcode16になってからはデフォルトでFolder構成になっていたり、Group構成からFolder構成に変換できる様になったことで、プロジェクトファイルのコンフリクト問題に頭を悩ませる必要がなくなった様です。
詳しくは以下記事をご参照ください。
環境
- Xcode: 16.2
- XcodeGen: 2.42.0
XcodeGenのセットアップ
XcodeGenのGithubリポジトリのREADMEを頼りにセットアップしていきます。
XcodeGenはCLIツールなので、まずはツールをローカルにインストールします。
インストール方法は以下がありますが、私はどのツールも大体Homebrewで入れちゃうので今回もHomebrewに頼ります。
- Mint
- MakeFile
- Homebrew
- SwiftPackage
brew install xcodegen
バージョン確認で、きちんとバージョンが表示されたらインストール完了です。
% xcodegen --version
Version: 2.42.0
プロジェクトファイル生成手順
XcodeGen で xcodeproj ファイルを生成する手順は以下の通りです
- spec ファイルを用意する
- spec ファイルに定義したターゲットのソースディレクトリを作成する
-
xcodegen genarate
を実行して xcodeproj ファイルを生成
※ specファイルとは、プロジェクトファイルの元となるファイルのことで、これを読み込んでXcodeGenはプロジェクトファイルを生成してくれます。
マルチモジュール構成の xcodeproj ファイルを生成してみる
想定する構成
- MainApp: アプリケーションターゲット
dependencies: [HogeFeature, Core]
testtargets: [TestHogeFeature] - HogeFeature: フィーチャーモジュール
dependencies: [Core]
testtargets: [TestHogeFeature] - Core: コアモジュール
dependencies: [] - TestHogeFeature: HogeFeatureの単体テスト
dependencies: [HogeFeature] - UITest: MainAppのUIテスト
dependencies: [MainApp]
spec ファイルの内容
ファイル名はproject.yml
にします。
xcodegen genarate
実行時に、どの spec ファイルから xcodeproj ファイルを作成するかを指定する必要がありますが、デフォルトでproject.yml
を探す様になっているので、今回はデフォルトのファイル名をつけておきます。(ルートの spec ファイルはデフォルトの名前にした方が、運用としてわかりやすいし実行時もオプションを省ける)
以下が今回サンプルプロジェクトにて作成した最終的なspecファイルの内容です。
name: MultimoduleSample
options:
bundleIdPrefix: taichi.com.multimoduleSample
minimumXcodeGenVersion: 2.41.0
xcodeVersion: 16.1
deploymentTarget:
iOS: 17.0
groupOrdering:
- order: [MainApp, HogeFeature, Core, TestHogeFeature, UITest]
configs:
Debug: debug
AppStore: release
settings:
base:
# DEVELOPMENT_TEAM: taichi sato
MARKETING_VERSION: 1.0.0
CURRENT_PROJECT_VERSION: 1.0.0
CODE_SIGN_STYLE: Automatic
# Swiftのバージョン
SWIFT_VERSION: 6.0
# 以降の設定はXcode14のデフォルト設定
ENABLE_USER_SCRIPT_SANDBOXING: YES
GCC_DYNAMIC_NO_PIC: NO
GCC_NO_COMMON_BLOCKS: YES
GCC_C_LANGUAGE_STANDARD: gnu17
CLANG_CXX_LANGUAGE_STANDARD: gnu++20
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS: YES
ENABLE_MODULE_VERIFIER: YES
GENERATE_INFOPLIST_FILE: YES
configs:
debug:
# ビルド成果物の最適化は行わずビルド速度を優先する
SWIFT_OPTIMIZATION_LEVEL: -Onone
# 以降の設定はXcode14のデフォルト設定
VALIDATE_PRODUCT: NO
GCC_OPTIMIZATION_LEVEL: "0"
appstore:
# ビルド速度は落ちるが、ビルド成果物の最適化を行う
SWIFT_OPTIMIZATION_LEVEL: -Owholemodule
# 以降の設定はXcode14のデフォルト設定
VALIDATE_PRODUCT: YES
GCC_OPTIMIZATION_LEVEL: "s"
targets:
MainApp:
type: application
platform: iOS
sources: [MainApp]
settings:
base:
INFOPLIST_FILE: MainApp/Info.plist
SWIFT_EMIT_LOC_STRINGS: YES
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
info:
path: MainApp/Info.plist
properties:
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
CFBundleShortVersionString: $(MARKETING_VERSION)
LSRequiresIPhoneOS: YES # iOSで実行する必要がある旨の設定
UILaunchScreen: ""
UIApplicationSupportsIndirectInputEvents: YES # アプリが一般的に間接入力メカニズムをサポートしていることを示すブール値(https://qiita.com/tamadeveloper/items/4be3c7e410831d2d5a4e)
UISupportedInterfaceOrientations~iphone:
[
UIInterfaceOrientationPortrait,
UIInterfaceOrientationLandscapeLeft,
UIInterfaceOrientationLandscapeRight,
]
dependencies:
- target: HogeFeature
HogeFeature:
type: framework
platform: iOS
sources: [HogeFeature]
dependencies:
- target: Core
Core:
type: framework
platform: iOS
sources: [Core]
TestHogeFeature:
type: bundle.unit-test
platform: iOS
sources: [TestHogeFeature]
dependencies:
- target: HogeFeature
UITest:
type: bundle.ui-testing
platform: iOS
sources: [UITest]
dependencies:
- target: MainApp
schemes:
MainApp:
build:
targets:
MainApp: all
run:
config: Debug
test:
config: Debug
gatherCoverageData: true
coverageTargets: all
targets:
- name: TestHogeFeature
parallelizable: true
randomExecutionOrder: true
- name: UITest
parallelizable: true
randomExecutionOrder: true
profile:
config: AppStore
analyze:
config: Debug
archive:
config: AppStore
HogeFeature:
templates:
- FeatureModuleScheme
templateAttributes:
testTargetName: TestHogeFeature
schemeTemplates:
FeatureModuleScheme:
templates:
- TestScheme
build:
targets:
${scheme_name}: all
TestScheme:
test:
config: Debug
gatherCoverageData: true
coverageTargets: all
targets:
- name: ${testTargetName}
parallelizable: true
randomExecutionOrder: true
各ターゲットのディレクトリを作成する
上記でspecファイルを共有しているのですが、じゃあこれでプロジェクトファイルが作れる!というわけでもなく、まずは各ターゲット毎のディレクトリを作成する必要があります。
今回の例では、以下ターゲットがあるので同名のディレクトリをxcodeproj
を生成したいディレクトリで作成します。
mkdir MainApp HogeFeature Core TestHogeFeature UITest
これをしないと、xcodegen genarate
実行時に以下のように怒られます
% xcodegen generate
3 Spec validations errors:
- Target "Core" has a missing source directory "~/work/swift/MultiModuleSample/Core"
- Target "HogeFeature" has a missing source directory "~/work/swift/MultiModuleSample/HogeFeature"
- Target "MainApp" has a missing source directory "~/work/swift/MultiModuleSample/MainApp"
・・・省略
これで晴れて、プロジェクトファイルが生成できる様になったはずです。
generate コマンドの実行
以下の様にコマンドを実行すると、同ディレクトリに xcodeproj ファイルが生成されます。
% xcodegen generate
⚙️ Generating plists...
⚙️ Generating project...
⚙️ Writing project...
Created project at ~/work/swift/MultiModuleSample/MultimoduleSample.xcodeproj
これでMultimoduleSample.xcodeprojが生成されたので、Xcodeで開いてみるとビルドが通るはずです。
specファイルの各項目の説明
name
name: MultimoduleSample
これはプロジェクトの名前に使用されます。
今回の場合はMultimoduleSample
を設定したので、生成されるxcodeproj
ファイルはMultimoduleSample.xcodeproj
となっています。
options
options:
bundleIdPrefix: taichi.com.multimoduleSample
minimumXcodeGenVersion: 2.41.0
xcodeVersion: 16.1
deploymentTarget:
iOS: 17.0
groupOrdering:
- order: [MainApp, HogeFeature, Core, TestHogeFeature, UITest]
生成するプロジェクトファイルのデフォルトの設定を、options
に指定した項目の値に上書きできます。
例えば、今回指定しているのは以下です。
bundleIdPrefix
bundleIdPrefix: taichi.com.multimoduleSample
プロジェクトファイルに含まれる各ターゲットのBundle Identifier
の接頭辞を指定できます。
例えば、MainApp
ターゲットのBundle Identifier
はtaichi.com.multimoduleSample.MainApp
になります。
minimumXcodeGenVersion
minimumXcodeGenVersion: 2.41.0
プロジェクトファイルを生成するときに使用するxcodegen
コマンドの実行可能最低バージョンを指定できます。
今回は2.42.0
を使用しているので上記設定ですとxcodegen
でプロジェクトファイルを生成できます。
もし以下の様に設定した場合は、今回の例で使用するxcodegen
ではプロジェクトファイルを生成できなくなります。
minimumXcodeGenVersion: 2.41.0
deploymentTarget
deploymentTarget:
iOS: 17.0
プラットフォーム毎(iOS
やmacOS
など)にプロジェクト全体のデプロイメントターゲットのバージョンを指定できます。
iOSに対して設定する場合は、ビルド設定のIPHONEOS_DEPLOYMENT_TARGET
に上書きして設定されます。
また、minimum deployments のバージョンも上書きされます。
後述しますが、ターゲット毎に設定できる項目もあります。
groupOrdering
groupOrdering:
- order: [MainApp, HogeFeature, Core, TestHogeFeature, UITest]
Xcodeでプロジェクトファイルを開いたときにターゲットディレクトリの並びを指定できます。
上記例ですと、上からMainApp, HogeFeature, Core, TestHogeFeature, UITest
となります。
今回紹介したoptions
の設定項目はほんの一例であり、以下ドキュメントに全量が記載されているので他に何があるか興味がある方はご参照ください。
configs
configs:
Debug: debug
AppStore: release
Configurationの指定が行えます。
XcodeのPROJECT画面にあるConfigurationsの部分を指定できます。
今回はDebug
とAppStore
のConfigurationを定義しています。
各Configuration項目の右辺にあるdebug
、release
でビルド設定などのデフォルト値が決定されます。
debug
であればデバッグ環境に合う設定(コンパイル速度を優先するような設定など)で、
release
であればリリースする用途に合う設定(ビルドの最適化を優先してパフォーマンスの良い成果物を生成する様な設定など)にデフォルトで設定されます。
ちなみに、こちらの設定を行わない場合はデフォルトで以下のような設定になります。(Xcodeからプロジェクトを生成した時と同じConfiguration設定)
configs:
Debug: debug
Release: release
settings
settings:
base:
# DEVELOPMENT_TEAM: taichi sato
MARKETING_VERSION: 1.0.0
CURRENT_PROJECT_VERSION: 1.0.0
CODE_SIGN_STYLE: Automatic
# Swiftのバージョン
SWIFT_VERSION: 6.0
# 以降の設定はXcode14のデフォルト設定
ENABLE_USER_SCRIPT_SANDBOXING: YES
GCC_DYNAMIC_NO_PIC: NO
GCC_NO_COMMON_BLOCKS: YES
GCC_C_LANGUAGE_STANDARD: gnu17
CLANG_CXX_LANGUAGE_STANDARD: gnu++20
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS: YES
ENABLE_MODULE_VERIFIER: YES
GENERATE_INFOPLIST_FILE: YES
configs:
debug:
# ビルド成果物の最適化は行わずビルド速度を優先する
SWIFT_OPTIMIZATION_LEVEL: -Onone
# 以降の設定はXcode14のデフォルト設定
VALIDATE_PRODUCT: NO
GCC_OPTIMIZATION_LEVEL: "0"
appstore:
# ビルド速度は落ちるが、ビルド成果物の最適化を行う
SWIFT_OPTIMIZATION_LEVEL: -Owholemodule
# 以降の設定はXcode14のデフォルト設定
VALIDATE_PRODUCT: YES
GCC_OPTIMIZATION_LEVEL: "s"
settings
では、プロジェクト全体のビルド設定(Build Settingsタブの設定)を上書きできるようになります。
ここで設定した項目は基本的にはプロジェクトに限らず各ターゲットのビルド設定にも上書きされますが、後述するターゲット毎にビルド設定を上書きした場合は、ターゲット単位の設定の方が適用されます。
settings
の下階層は以下に分かれます。
base
base:
MARKETING_VERSION: 1.0.0
CURRENT_PROJECT_VERSION: 1.0.0
CODE_SIGN_STYLE: Automatic
# Swiftのバージョン
SWIFT_VERSION: 6.0
# 以降の設定はXcode14のデフォルト設定
ENABLE_USER_SCRIPT_SANDBOXING: YES
GCC_DYNAMIC_NO_PIC: NO
GCC_NO_COMMON_BLOCKS: YES
GCC_C_LANGUAGE_STANDARD: gnu17
CLANG_CXX_LANGUAGE_STANDARD: gnu++20
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS: YES
ENABLE_MODULE_VERIFIER: YES
base
の下階層に定義されたビルド設定は、Configurations関係なく同じ値が設定されることになります。
configs
configs:
debug:
# ビルド成果物の最適化は行わずビルド速度を優先する
SWIFT_OPTIMIZATION_LEVEL: -Onone
# 以降の設定はXcode14のデフォルト設定
VALIDATE_PRODUCT: NO
GCC_OPTIMIZATION_LEVEL: "0"
appstore:
# ビルド速度は落ちるが、ビルド成果物の最適化を行う
SWIFT_OPTIMIZATION_LEVEL: -Owholemodule
# 以降の設定はXcode14のデフォルト設定
VALIDATE_PRODUCT: YES
GCC_OPTIMIZATION_LEVEL: "s"
configs
ではConfiguration毎に異なる値をビルド設定の項目に定義することができます。
-
groups
settingsGroups
で定義したビルド設定をグループ名を指定して適用することができる項目です。
settingsGroups
では再利用可能なビルド設定のまとまりを定義することができます。
例えば以下例では、common
というグループ名を定義し、プロジェクトのビルド設定に適用しています。
+ settingGroups:
+ common:
+ ENABLE_MODULE_VERIFIER: NO
settings:
+ groups:
+ - common
base:
# DEVELOPMENT_TEAM: taichi sato
MARKETING_VERSION: 1.0.0
CURRENT_PROJECT_VERSION: 1.0.0
CODE_SIGN_STYLE: Automatic
# Swiftのバージョン
SWIFT_VERSION: 6.0
# Info.plistがなければ自動で生成する
GENERATE_INFOPLIST_FILE: YES
# 以降の設定はXcode14のデフォルト設定
ENABLE_USER_SCRIPT_SANDBOXING: YES
GCC_DYNAMIC_NO_PIC: NO
GCC_NO_COMMON_BLOCKS: YES
GCC_C_LANGUAGE_STANDARD: gnu17
CLANG_CXX_LANGUAGE_STANDARD: gnu++20
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS: YES
ENABLE_MODULE_VERIFIER: YES
configs:
debug:
# ビルド成果物の最適化は行わずビルド速度を優先する
SWIFT_OPTIMIZATION_LEVEL: -Onone
# 以降の設定はXcode14のデフォルト設定
VALIDATE_PRODUCT: NO
GCC_OPTIMIZATION_LEVEL: "0"
appstore:
# ビルド速度は落ちるが、ビルド成果物の最適化を行う
SWIFT_OPTIMIZATION_LEVEL: -Owholemodule
# 以降の設定はXcode14のデフォルト設定
VALIDATE_PRODUCT: YES
GCC_OPTIMIZATION_LEVEL: "s"
base
、configs
、groups
では同じビルド設定を定義することもできますが、そうなった時に適用される優先順位は以下順になります。
configs
base
groups
つまりconfigs
に設定した値は優先度が高く、groups
に設定した値は優先度が低いです。
ビルド設定の項目は以下Appleの公式ドキュメントを見たり、Xcodeでプロジェクトを生成したときのデフォルトのビルド設定を見ながら設定しました。(設定項目を一つ一つ説明するのは流石に大変なので、ここではどうやって設定項目を決めたかというところにとどめておきます。)
以下はXcodeGenのsettings
関連のドキュメントです。
settingsGroups
settings
targets
targets:
MainApp:
type: application
platform: iOS
sources: [MainApp]
settings:
base:
INFOPLIST_FILE: MainApp/Info.plist
SWIFT_EMIT_LOC_STRINGS: YES
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
info:
path: MainApp/Info.plist
properties:
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
CFBundleShortVersionString: $(MARKETING_VERSION)
LSRequiresIPhoneOS: YES # iOSで実行する必要がある旨の設定
UILaunchScreen: ""
UIApplicationSupportsIndirectInputEvents: YES # アプリが一般的に間接入力メカニズムをサポートしていることを示すブール値(https://qiita.com/tamadeveloper/items/4be3c7e410831d2d5a4e)
UISupportedInterfaceOrientations~iphone:
[
UIInterfaceOrientationPortrait,
UIInterfaceOrientationLandscapeLeft,
UIInterfaceOrientationLandscapeRight,
]
dependencies:
- target: HogeFeature
HogeFeature:
type: framework
platform: iOS
sources: [HogeFeature]
dependencies:
- target: Core
Core:
type: framework
platform: iOS
sources: [Core]
TestHogeFeature:
type: bundle.unit-test
platform: iOS
sources: [TestHogeFeature]
dependencies:
- target: HogeFeature
UITest:
type: bundle.ui-testing
platform: iOS
sources: [UITest]
dependencies:
- target: MainApp
targets
ではプロジェクトの各ターゲットの定義を行うことができます。
targets
の一つ下の階層にターゲット名を定義し、それぞれのターゲット名の下階層に必要な設定項目を追加していきます。
今回定義した設定項目について、軽く見ていきます。
type
ターゲットの種類を指定します。
例として以下ターゲットの種類があります。
product type | 意味 |
---|---|
application | アプリケーションターゲット(@main が含まれる様なターゲットのイメージ) |
framework | Embed Framework用のターゲット(今回の例では子モジュールとして使用) |
bundle.ui-testing | UIテスト用のターゲット |
bundle.unit-test | 単体テスト用のターゲット |
その他にもさまざまな種類があり、以下公式ドキュメントに全量が記載されています。
platform
ターゲットのプラットフォームを指定できます。これにより、ビルド設定に指定したプラットフォームのデフォルト設定がされます。
今回の例ではiOSアプリ開発を想定しているので、指定しているプラットフォームはiOS
にしていますが、
以下例のようにマルチプラットフォーム対応にすることも可能です。
targets:
Sample:
type: framework
platform: [iOS, tvOS]
sources: [Sample]
こうした場合、Sample_iOS
とSample_tvOS
二つのターゲットが生成されプラットフォーム毎のターゲット構成になります。
supportedDestinations
こちらも先ほどのplatform
と似ていますが、ターゲットでサポートするプラットフォームを指定します。
違うこととしては、複数のプラットフォームを選択しても、ターゲットがプラットフォーム毎に生成されるのではなく、一つのターゲットで複数のプラットフォームをサポートすることになります。
例えば、以下の様な定義では、Sample
というターゲットではiOS
とtvOS
の両プラットフォームをサポートする様になります。
Sample:
type: framework
supportedDestinations: [iOS, tvOS]
sources: [Sample]
sources
ターゲットに含めるソースディレクトリを指定できます。
今回の例でHogeFeature
ではHogeFeature
というディレクトリをソースディレクトリと扱っています。
HogeFeature:
type: framework
platform: iOS
sources: [HogeFeature]
dependencies:
- target: Core
上記の様な記載にした場合、プロジェクトファイルが生成されるディレクトリと同階層にあるHogeFeature
ディレクトリをソースディレクトリとして扱い、HogeFeature
内も再起的に解析してくれます。
一つのターゲットに対して、複数のソースディレクトリを指定することも可能で、以下の様にすることでSample
、Sample2
両方のソースファイルをSample
ターゲットでコンパイルすることになります。
Sample:
type: framework
supportedDestinations: [iOS]
sources:
- Sample
- Sample2
他にもソースディレクトリやソースファイルの指定方法がありますので、そちらは公式ドキュメントをご覧ください。
dependencies
ターゲットの依存関係を定義することができます。
今回の例では、MainApp
はHogeFeature
とCore
に、HogeFeature
はCore
に依存させるようにしているのですが、その時の依存関係の定義は以下になります。
targets:
MainApp:
・・・省略
dependencies:
- target: HogeFeature
HogeFeature:
・・・省略
dependencies:
- target: Core
Core:
・・・省略
Core
がMainApp
のdependencies
に含まれていないのは、HogeFeature
側でCore
に依存しているためです。
今回はマルチモジュールにすることを目標としているため、ターゲット間での依存関係しか取り扱っていませんが、ビルド済みのFrameworkやSDK、SPMで管理されているライブラリなどの依存関係の定義をする方法ありますので、そちらは以下公式ドキュメントをご覧ください。
settings
ターゲット毎のビルド設定を定義することができます。
ここで定義したビルド設定は、上述しているプロジェクト全体に適用するビルド設定よりも優先度が高いです。
こちらの設定方法は、上述しているプロジェクト全体に適用するビルド設定方法と同じなので、設定方法は省略します。
info
Info.plistを自動生成し、Info.plistの項目の値を上書きすることができます。
今回の例では、MainApp
ターゲットにおいて、MainApp/Info.plist
にInfo.plistを生成し、properties
の下階層で上書きする項目と値を定義しています。
MainApp:
・・・省略
info:
path: MainApp/Info.plist
properties:
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
CFBundleShortVersionString: $(MARKETING_VERSION)
LSRequiresIPhoneOS: YES # iOSで実行する必要がある旨の設定
UILaunchScreen: ""
UIApplicationSupportsIndirectInputEvents: YES # アプリが一般的に間接入力メカニズムをサポートしていることを示すブール値(https://qiita.com/tamadeveloper/items/4be3c7e410831d2d5a4e)
UISupportedInterfaceOrientations~iphone:
[
UIInterfaceOrientationPortrait,
UIInterfaceOrientationLandscapeLeft,
UIInterfaceOrientationLandscapeRight,
]
上記で設定しているproperties
の内容は下記記事を参考にしています。
schemes
プロジェクトのスキーム構成を定義することができます。
スキームとは、Xcodeでいうと以下部分に当たります。
schemes:
MainApp:
build:
targets:
MainApp: all
run:
config: Debug
test:
config: Debug
gatherCoverageData: true
coverageTargets: all
targets:
- name: TestHogeFeature
parallelizable: true
randomExecutionOrder: true
- name: UITest
parallelizable: true
randomExecutionOrder: true
profile:
config: AppStore
analyze:
config: Debug
archive:
config: AppStore
HogeFeature:
templates:
- FeatureModuleScheme
templateAttributes:
testTargetName: TestHogeFeature
schemeTemplates:
FeatureModuleScheme:
templates:
- TestScheme
build:
targets:
${scheme_name}: all
TestScheme:
test:
config: Debug
gatherCoverageData: true
coverageTargets: all
targets:
- name: ${testTargetName}
parallelizable: true
randomExecutionOrder: true
schemes
の一つ下の階層で、任意のスキーム名を定義し、各スキームの各アクション(build
, run
, test
, profile
, analyze
, archive
)の設定などを定義していきます。
今回の例ではテストアクションの設定が中心でしたので、そちらの設定に関わる以下項目について説明します。
-
gatherCoverageData
コードカバレッジを取得するかしないかを設定します。(true
にすると取得するようになります。) -
coverageTargets
コードカバレッジを取得するターゲットを指定します。
基本的に依存しているターゲット全てを指定するall
で良いと考えていますが、配列でターゲット名を指定することもできます。 -
targets
テストターゲット(UIテストターゲットや単体テストターゲットなど)を指定します。
ターゲットはname
でターゲット名を指定します。
また、parallelizable
(並列テストをするかどうか)、randomExecutionOrder
(テストケース実行順をランダムにする可動化」)という設定もtargets
の下階層で設定できます。
テストは実行時間が短い方が良いというのと、テストケース毎に結合していることは望ましい状態ではないという観点から、例では両方の設定をtrue
にしています。
またHogeFeature
にはtemplete
という再利用可能なスキーム定義を用いています。
再利用可能なスキームはschemeTemplates
で設定することができます。
schemeTemplates
の一つ下の階層にテンプレート名を定義し、さらにテンプレート名の下の階層にschemes
の各ターゲットに行っていた方法と同じようにスキーム構造を定義することができます。
${scheme_name}
というものをschemeTemplates
の定義内で使用していますが、こちらはテンプレート使用元のスキーム名が展開されるようになっています。
また似たもので${testTargetName}
という記載もありますが、こちらはテンプレート使用元でtemplateAttributes
から何を渡すかを指定することができます。
結局HogeFeature
のスキームでは以下のような定義になっています。
全てのアクションでビルドすることができるようにして、テスト時は
TestHogeFeature
をテスト対象とするようにする
FeatureModuleScheme
という再利用可能なスキーム定義を定義した理由としては、FeatureModule
対応する単体テストターゲットを作成するようにしたいからです。(昨日毎にテストを分けることで複数人で開発する時のコンフリクトのリスクを軽減したい)
ただ対応するテストターゲットを紐づけるのは定型的な定義を書く必要があるので、schemeTemplates
によって定型的な定義の記述量を減らすようにしました。
そのほかスキーム定義についての詳細は以下公式ドキュメントをご確認ください。
XcodeGenは現環境で使う必要があるのか
一通りXcodeGenを使う方法を説明してみましたが、やはり学習コストは高いという意見に関しては同意です。
今までXcodeがよしなに設定したりしてくれていたものを、specファイルで自分で設定していくことになるので、普段意識しないビルド設定などにも目を向けなきゃだったり、specファイルの使用も膨大なのが原因かなと感じました。
そんなXcodeGenですが、大きな学習コストをかけてまで導入したいかというと、私は新しく立ち上げるプロジェクトに対して使用する必要はないかなと思いました。
その理由としては、以下のとおりです。
- プロジェクトファイルのコンフリクト問題を解決することができるツールではあるが、Xcode16からはツールを使わずともコンフリクトのリスクが激減したため、学習コストをかけてまで得られる旨みがない
- マルチモジュール構成において、依存関係がspecファイルで一目見てわかるメリットはあるが、SPMでもそれは同じで、SPMと比較したときに学習コストという観点ではSPMの方が直感的で低いという感想なのでSPMで対応した方が手軽なのではと思う
- XcodeGenは純正のツールではないので、Xcodeのアップデートなどにシームレスに対応できない場合が多い(そういう意味でもマルチモジュール構成という文脈ではSPMを支持したい)
当初の目標であった、SPMとの違いだったりにも若干上記で触れましたが、もう一つの違いとしてSPMではSPMに対応したライブラリしか扱えない(私が知る限り、、)のに対して、XcodeGenであればSPMだけでなくメジャーどころであるCocoaPodsやCarthageのライブラリにも対応していることです。
そのため、マルチモジュール構成を実現するときに、SPMに対応していないライブラリを扱う場合はXcodeGenを使用することを検討するのはアリかもです。
ただ、Apple純正のパッケージ管理ツールということもあり、SPMに対応しているライブラリは増えているのと、XcodeなどiOS開発周りのツール互換を考えても、マルチモジュール構成を行う際はSPMでできるならSPMを採用するでいいのかなというのが感想です。
おわり
なかなか長ったらしい記事になってしまいました、、
XcodeGen想像以上に難しかったですが、普段意識しないビルド設定だったりを意識することが増えたので、得るものはあったなと思います。
少しですが、ビルド設定周りの嫌悪感が減ったかなと思います。
今回サンプルで作成したプロジェクトはGitHubリポジトリにも上げていますので、よければご覧ください。
基本的に公式ドキュメントを参照してキャッチアップした内容ですが、解釈違いなどありましたらご指摘いただけますと幸いです