1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

XcodeGenでマルチモジュール構成のプロジェクトを生成する

Last updated at Posted at 2025-01-20

はじめに

今まで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 ファイルを生成する手順は以下の通りです

  1. spec ファイルを用意する
  2. spec ファイルに定義したターゲットのソースディレクトリを作成する
  3. 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 Identifiertaichi.com.multimoduleSample.MainAppになります。

image.png

  • minimumXcodeGenVersion
minimumXcodeGenVersion: 2.41.0

プロジェクトファイルを生成するときに使用するxcodegenコマンドの実行可能最低バージョンを指定できます。
今回は2.42.0を使用しているので上記設定ですとxcodegenでプロジェクトファイルを生成できます。
もし以下の様に設定した場合は、今回の例で使用するxcodegenではプロジェクトファイルを生成できなくなります。

minimumXcodeGenVersion: 2.41.0
  • deploymentTarget
deploymentTarget:
    iOS: 17.0

プラットフォーム毎(iOSmacOSなど)にプロジェクト全体のデプロイメントターゲットのバージョンを指定できます。
iOSに対して設定する場合は、ビルド設定のIPHONEOS_DEPLOYMENT_TARGETに上書きして設定されます。
また、minimum deployments のバージョンも上書きされます。
後述しますが、ターゲット毎に設定できる項目もあります。

  • groupOrdering
groupOrdering:
    - order: [MainApp, HogeFeature, Core, TestHogeFeature, UITest]

Xcodeでプロジェクトファイルを開いたときにターゲットディレクトリの並びを指定できます。
上記例ですと、上からMainApp, HogeFeature, Core, TestHogeFeature, UITestとなります。

image.png

今回紹介したoptionsの設定項目はほんの一例であり、以下ドキュメントに全量が記載されているので他に何があるか興味がある方はご参照ください。

configs

configs:
  Debug: debug
  AppStore: release

Configurationの指定が行えます。
XcodeのPROJECT画面にあるConfigurationsの部分を指定できます。
今回はDebugAppStoreのConfigurationを定義しています。
各Configuration項目の右辺にあるdebugreleaseでビルド設定などのデフォルト値が決定されます。
debugであればデバッグ環境に合う設定(コンパイル速度を優先するような設定など)で、
releaseであればリリースする用途に合う設定(ビルドの最適化を優先してパフォーマンスの良い成果物を生成する様な設定など)にデフォルトで設定されます。

image.png

ちなみに、こちらの設定を行わない場合はデフォルトで以下のような設定になります。(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関係なく同じ値が設定されることになります。
image.png

  • 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毎に異なる値をビルド設定の項目に定義することができます。
image.png

  • 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"

baseconfigsgroupsでは同じビルド設定を定義することもできますが、そうなった時に適用される優先順位は以下順になります。

  1. configs
  2. base
  3. 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_iOSSample_tvOS二つのターゲットが生成されプラットフォーム毎のターゲット構成になります。

image.png

supportedDestinations

こちらも先ほどのplatformと似ていますが、ターゲットでサポートするプラットフォームを指定します。
違うこととしては、複数のプラットフォームを選択しても、ターゲットがプラットフォーム毎に生成されるのではなく、一つのターゲットで複数のプラットフォームをサポートすることになります。

例えば、以下の様な定義では、SampleというターゲットではiOStvOSの両プラットフォームをサポートする様になります。

  Sample:
    type: framework
    supportedDestinations: [iOS, tvOS]
    sources: [Sample]

image.png

sources

ターゲットに含めるソースディレクトリを指定できます。
今回の例でHogeFeatureではHogeFeatureというディレクトリをソースディレクトリと扱っています。

 HogeFeature:
    type: framework
    platform: iOS
    sources: [HogeFeature]
    dependencies:
      - target: Core

上記の様な記載にした場合、プロジェクトファイルが生成されるディレクトリと同階層にあるHogeFeatureディレクトリをソースディレクトリとして扱い、HogeFeature内も再起的に解析してくれます。

一つのターゲットに対して、複数のソースディレクトリを指定することも可能で、以下の様にすることでSampleSample2両方のソースファイルをSampleターゲットでコンパイルすることになります。

Sample:
    type: framework
    supportedDestinations: [iOS]
    sources:
      - Sample
      - Sample2

他にもソースディレクトリやソースファイルの指定方法がありますので、そちらは公式ドキュメントをご覧ください。

dependencies

ターゲットの依存関係を定義することができます。

今回の例では、MainAppHogeFeatureCoreに、HogeFeatureCoreに依存させるようにしているのですが、その時の依存関係の定義は以下になります。

targets:
  MainApp:
    ・・・省略
    dependencies:
      - target: HogeFeature
 HogeFeature:
    ・・・省略
    dependencies:
      - target: Core
 Core:
    ・・・省略

CoreMainAppdependenciesに含まれていないのは、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でいうと以下部分に当たります。

image.png

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リポジトリにも上げていますので、よければご覧ください。

基本的に公式ドキュメントを参照してキャッチアップした内容ですが、解釈違いなどありましたらご指摘いただけますと幸いです

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?