Xcodeプロジェクトの生成ツール「XcodeGen」のセットアップ&操作方法

## はじめに

もともと気になっており、先日の勉強会で[XcodeGenのLT](https://speakerdeck.com/giginet/xcodegenchao-ru-men)を聞いて、触ってみようと思いました。

触ってみた結果、業務でも導入したいと考えています。
「プロジェクトファイルがコンフリクトしない」という特徴は、複数人による開発では非常に大きなメリットになるためです。

## 「XcodeGen」とは？

Xcodeプロジェクトファイル（ `.xcodeproj` ）を定義ファイル（ `project.yml` ）から生成する、Swift製のコマンドラインツールです。

YAMLでプロジェクトの設定を書くことで、プロジェクトファイルが自動生成されます。
そのため、プロジェクトファイルを自分たちで編集する必要がなくなります。

![xcodegen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/8f912c3d-6e54-5721-3bca-862aae5b0875.png)

__XcodeGenが行うことは「プロジェクトファイルを自動生成する」に尽きる__ ので、それがわかるとメリットやこの先の説明が理解しやすいです。

## 用語解説

本記事では用語を以下のように定義します。

- プロジェクトファイル  
`.xcodeproj` ファイルを指す
- ワークスペース  
`.xcworkspace` ファイルを指す  
CocoaPodsの使用時に必要となる
- 定義ファイル  
`project.yml` を指す  
公式では「project spec」と呼んでいる

## XcodeGenのメリット

XcodeGenの導入によるメリットを紹介します。

### プロジェクトファイルがコンフリクトしなくなる

一番のメリットといっても過言ではないと思います。
複数人によるiOSアプリ開発で誰もが経験する「ファイル追加によるプロジェクトファイルのコンフリクト」が発生しなくなります。

理由は、定義ファイルの `source:` でフォルダを指定すると、フォルダ内のファイルを自動的にプロジェクトへ追加するためです。

```yml:project.yml
targets:
  Foo:
    sources:
      - Foo # 「Foo」フォルダ内のファイルが自動的に追加される
```

つまり、ファイルを追加・リネーム・削除するのみでは定義ファイルを変更する必要がありません。

### プロジェクトツリーが自動ソートされる

実際のファイルパスから自動的にプロジェクトツリーを生成します。
その際、ファイル名順に自動でソートされます。

### プロジェクトの設定を簡単に変更できる

ターゲットやCarthageで管理しているライブラリを簡単に追加できます。
定義ファイルはテキストなので、その他の設定も簡単に変更できます。

## XcodeGenのデメリット

### 学習コストがかかる

ライブラリ全般にいえるデメリットですが、XcodeGenの概要や使い方のキャッチアップにコストが掛かります。
本記事で多少コストが下がれば幸いです。

### プロジェクトファイルの生成に手間がかかる

メリットで「ファイル変更時に定義ファイルの修正不要」と説明しましたが、プロジェクトファイルを生成し直す必要はあります。
CocoaPodsを使っていると、さらにプロジェクトファイルからワークスペースを生成しなければいけません。

私はMakefileを作って手間を削減しています。
https://qiita.com/uhooi/items/16a870eaae24b46103fb#プロジェクトファイルの生成

## XcodeGenを導入すべきケース

XcodeGenのメリットやデメリットがわかったところで、どういうケースでXcodeGenを導入すべきなのか紹介します。
あくまで私の意見なので、参考程度に捉えてください。

### プロジェクトファイルのコンフリクトに悩んでいる

大人数による開発などでプロジェクトファイルのコンフリクトに悩んでいる場合、導入を検討する価値があると思います。

### 新規開発時

定義ファイルの作成経験があればコピペして使い回せるため、新規開発時のプロジェクト作成時間を短縮できます。
また、新規開発時はファイルの追加を頻繁に行うため、コンフリクトを防げます。

## 環境

- OS：macOS Mojave 10.14.6
- Swift：5.1.3
- Xcode：11.3.1 (11C504)
- XcodeGen：2.11.0

## セットアップ

### XcodeGenのインストール

[Mint](https://github.com/yonaskolb/Mint)からインストールします。

```diff:Mintfile
+ yonaskolb/xcodegen@2.11.0
```

```shell-session
$ mint bootstrap
```

手動でインストールするには、公式ドキュメントをご参照ください。
https://github.com/yonaskolb/XcodeGen#installing

XcodeGenのセットアップ自体は、インストールのみで完了します。

## 定義ファイルの作成

定義ファイルをプロジェクトのルートフォルダに新規作成します。

本記事では「Foo」をプロジェクト名およびプロジェクトのルートフォルダとします。

```shell-session
$ cd Foo
$ touch project.yml
```

## 定義ファイルの説明

ここからは定義ファイルに最低限必要な記述をひとつずつ説明します。

設定できる全ての内容までは説明し切れないので、詳細は公式ドキュメントをご参照ください。
https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md

### name

プロジェクト名を指定します。

```yaml:project.yml
name: Foo
```

指定した文字列がプロジェクトファイル名になります。
<img width="119" alt="スクリーンショット 2020-01-29 12.15.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138245/8c8da14e-54c0-ef19-7b02-265b32adbbc3.png">

### options

プロジェクトのオプションを設定します。

#### bundleIdPrefix

Product bundle identifier（以下「バンドルID」と呼ぶ）のプリフィクスを指定します。

本記事では「com.theuhooi」をプリフィクスとします。

```yaml:project.yml
options:
  bundleIdPrefix: com.theuhooi
```

プリフィクスを指定すると、各ターゲットのバンドルIDが自動で「{プリフィクス}.{ターゲット名}」に設定されます。
そのため、ターゲットごとに `PRODUCT_BUNDLE_IDENTIFIER` を指定する必要がなくなります。

```yaml:project.yml
options:
  bundleIdPrefix: com.theuhooi

targets:
  Foo:
    settings:
      base:
        PRODUCT_BUNDLE_IDENTIFIER: com.theuhooi.Foo # 不要

  FooTests:
    settings:
      base:
        PRODUCT_BUNDLE_IDENTIFIER: com.theuhooi.FooTests # 不要

  FooUITests:
    settings:
      base:
        PRODUCT_BUNDLE_IDENTIFIER: com.theuhooi.FooUITests # 不要
```

マルチプラットフォームなどでターゲット名とバンドルIDを合わせない場合は、明示的に指定してください。

#### developmentLanguage

LocalizationsのDevelopment Languageを指定します。

デフォルトは英語で、日本語にする場合は `ja` を指定します。

```yaml:project.yml
options:
  developmentLanguage: ja
```

`project.pbxproj` 上のキーは `developmentRegion` なので、指定を間違えないようご注意ください。

#### deploymentTarget

デプロイメントターゲットをプラットフォームごとに指定します。

iOSアプリ開発の場合、「iOS」のみ指定すればOKです。

```yaml:project.yml
options:
  deploymentTarget:
    iOS: 13.0
```

#### xcodeVersion

Xcodeのバージョンを指定します。

```yaml:project.yml
options:
  xcodeVersion: "11.3.1" # 変わらない？
```

指定してもプロジェクトファイルに反映されなかったので、何か条件があるか、私が意味を理解していないかのどちらかです。

[コメント](https://qiita.com/uhooi/items/16a870eaae24b46103fb#comment-caf0bc6cb13697f5464e) で教えていただいたのですが、ダブルクォーテーションで括らないと不正な値として扱われ、デフォルト値が使われるとのことです。

#### findCarthageFrameworks

Carthageで管理しているフレームワークのインストール時、依存しているフレームワークも自動でインストールするかどうかの設定です。

```yaml:project.yml
options:
  findCarthageFrameworks: true
```

フレームワークのつくりや、私の使い方の問題かもしれませんが、依存しているのに自動でインストールされなかったり、余計なフレームワークまでインストール（テストターゲットのみで使うのに製品ターゲットにもインストール）されたりしたため、私は指定していません。

#### carthageExecutablePath

Carthageの実行パスを指定します。

CarthageをMintで管理している場合、以下のように指定する必要があります。

```yaml:project.yml
options:
  carthageExecutablePath: mint run Carthage/Carthage carthage
```

デフォルトが `carthage` なので、CarthageをHomebrewでインストールした場合は不要です。

### settings

全ターゲット共通で使うBuild Settingsを設定します。
個別に設定する場合はターゲット内の `settings` に記述します。

私はバージョンとビルド番号、Development Team、Debug Information Formatを指定しています。
Debug Information FormatはBuild Configurations（以下「ビルド構成」と呼ぶ）が `Debug` （以下「デバッグビルド」と呼ぶ）時のみ指定しています。

```yaml:project.yml
settings:
  base:
    MARKETING_VERSION: 1.0.0
    CURRENT_PROJECT_VERSION: 1
    DEVELOPMENT_TEAM: XXXXXXXXXX
  config:
    debug:
      DEBUG_INFORMATION_FORMAT: "dwarf-with-dsym"
```

`MARKETING_VERSION` と `CURRENT_PROJECT_VERSION` をそれぞれバージョンとビルド番号に紐付けるには、 `Info.plist` の修正が必要です。
（TARGETS > {製品ターゲット} > General の `Version` , `Build` を手動で変更すると、自動で修正されます）

```diff:Info.plist
        <key>CFBundleShortVersionString</key>
-       <string>1.0</string>
+       <string>$(MARKETING_VERSION)</string>
        <key>CFBundleVersion</key>
-       <string>1</string>
+       <string>$(CURRENT_PROJECT_VERSION)</string>
```

バージョンとビルド番号を全ターゲットで共通にしているのは、製品とApp Extensionのバージョンを合わせないと警告が発生するためです。

### packages

Swift Package Manager（以下「SwiftPM」と呼ぶ）で管理するライブラリを指定します。

SwiftPMはCocoaPodsの `Podfile` のようにライブラリのバージョンを管理するファイルがなく、本来はXcode上で操作してプロジェクトファイルで管理します。
XcodeGenを使うとGUIで管理しなくて済みます。

構造は以下の通りです。

```yaml:project.yml
packages:
  {パッケージ名}:
    url: {GitHubなどのURL}
    version: {バージョン}
```

バージョンを指定する方法は他にもありますが、他の開発者とバージョンを揃えたいので `version` でバージョンを指定しています。

私は以下のように指定しています。

```yaml:project.yml
packages:
  Rswift:
    url: https://github.com/mac-cain13/R.swift.Library
    version: 5.3.0
  Gedatsu:
    url: https://github.com/bannzai/Gedatsu
    version: 1.2.0
  SwiftPrettyPrint:
    url: https://github.com/YusukeHosonuma/SwiftPrettyPrint
    version: 1.0.0
```

### targets

各ターゲットの設定です。
構造は以下の通りです。

```yaml:project.yml
targets:
  {ターゲット名}:
   {設定…}
```

#### type

ターゲットのタイプを指定します。

```yaml:project.yml
targets:
  Foo:
    type: application

  FooTests:
    type: bundle.unit-test

  FooUITests:
    type: bundle.ui-testing
```

よく指定するタイプの一覧です。

|type|説明|
|:--|:--|
|application|製品ターゲット|
|bundle.unit-test|単体テストターゲット|
|bundle.ui-testing|UIテストターゲット|

#### platform

対象とするプラットフォームを指定します。

```yaml:project.yml
targets:
  Foo:
    platform: iOS
```

配列で指定するとターゲット名の末尾にプラットフォーム名が付くため、対象プラットフォームが1つのみで `bundleIdPrefix` を使う場合は配列にしない方がいいです。

```yaml:project.yml
targets:
  Foo:
    platform: [iOS] # ターゲット名が「Foo-iOS」となる
```

#### sources

ターゲットに含むファイルまたはフォルダを指定します。

基本的にはターゲットと同名のフォルダのみ指定すればOKです。

```yaml:project.yml
targets:
  Foo:
    sources:
      - Foo

  FooTests:
    sources:
      - FooTests

  FooUITests:
    sources:
      - FooUITests
```

注意点として、 __すでに存在するファイルを元にプロジェクトツリーを生成する__ ため、プロジェクトファイルの生成時に存在しないファイル（R.swiftやMockoloなどで自動生成するファイル）が追加されません。

クックパッドさんのブログの通りに指定することでこの問題を回避できます。
https://techlife.cookpad.com/entry/2019/04/26/110000

```yaml:project.yml
targets:
  Foo:
    sources:
      - Foo
      # Mockoloで生成するファイル
      - path: "Foo/Generated/MockResults.swift"
        optional: true
        type: file
      # R.swiftで生成するファイル
      - path: "Foo/Generated/R.generated.swift"
        optional: true
        type: file
```

#### settings

ターゲットのBuild Settingsを設定します。

私は `Info.plist` のパス、 `{プロジェクト名}.entitlements` のパス、開発言語、Other Linker Flags、Other Swift Flagsを指定しています。

```yaml:project.yml
targets:
  Foo:
    settings:
      base:
        INFOPLIST_FILE: Foo/Info.plist
        CODE_SIGN_ENTITLEMENTS: Foo/Foo.entitlements
        DEVELOPMENT_LANGUAGE: jp
        OTHER_LDFLAGS: $(inherited) $(OTHER_LDFLAGS) -ObjC
      configs:
        debug:
          OTHER_SWIFT_FLAGS: $(inherited) -Xfrontend -warn-long-expression-type-checking=500 -Xfrontend -warn-long-function-bodies=500

  FooTests:
    settings:
      base:
        INFOPLIST_FILE: FooTests/Info.plist

  FooUITests:
    settings:
      base:
        INFOPLIST_FILE: FooUITests/Info.plist
```

XcodeGenでは `Info.plist` の生成も行えますが、コンフリクトしにくいので私は既存のファイルを読み込むのみに留めています。

`CODE_SIGN_ENTITLEMENTS` について、NFCタグの読み取りなど、特別に許可している場合のみ設定が必要です。

`DEVELOPMENT_LANGUAGE` について、 `jp` を指定しないとiOSの使用言語が日本語でも `UIActivityViewController` が日本語表記にならなかったので指定しています。

`OTHER_LDFLAGS` について、Firebase Analyticsで必要です。

`OTHER_SWIFT_FLAGS` について、型推論と関数のビルドに時間がかかる場合に警告を発生するようにしています。

#### dependencies

各ターゲットが依存しているフレームワークなどを指定します。
Carthageで管理しているフレームワークを指定する場合、 `- carthage: {フレームワーク名}` と記述するのみで済むのが便利です。
同様にSwiftPMで管理しているライブラリは `- package: {ライブラリ名}` と記述します。

私の場合、製品ターゲットはCarthageやSwiftPMで管理しているフレームワーク、テストターゲットは製品ターゲットのみを指定しています。

```yaml:project.yml
targets:
  Foo:
    dependencies:
      - package: Rswift
      - package: Gedatsu
      - package: SwiftPrettyPrint
      - carthage: Alamofire

  FooTests:
    dependencies:
      - target: Foo

  FooUITests:
    dependencies:
      - target: Foo
```

#### preBuildScripts

Build Phasesの最初に実行するスクリプトを指定します。

私の場合、製品ターゲットでR.swiftとMockoloのファイル生成スクリプトを記述しています。

```yaml:project.yml
targets:
  Foo:
    preBuildScripts:
      # R.swiftでリソースファイルの生成
      - path: ./Scripts/XcodeGen/rswift.sh
        name: Generate Resources with R.swift
        inputFiles:
          - $TEMP_DIR/rswift-lastrun
        outputFiles:
          - $SRCROOT/$TARGET_NAME/Generated/R.generated.swift

      # Mockoloでモックの生成
      - path: ./Scripts/XcodeGen/mockolo.sh
        name: Generate Mocks with Mockolo
        outputFiles:
          - $SRCROOT/$TARGET_NAME/Generated/MockResults.swift
```

`script` で直接スクリプトを記述できますが、定義ファイルが長くなるため、私はルートフォルダに `Scripts/XcodeGen` フォルダを作成し、そこにXcodeGenで使うスクリプトをまとめて格納しています。
その場合、 `path` でスクリプトのパスを指定します。

```bash:./Scripts/XcodeGen/rswift.sh
if which mint >/dev/null; then
  xcrun --sdk macosx mint run R.swift rswift generate "$SRCROOT/$TARGET_NAME/Generated/R.generated.swift"
else
  echo "warning: Mint not installed, download from https://github.com/yonaskolb/Mint"
fi
```

```bash:./Scripts/XcodeGen/mockolo.sh
if which mint >/dev/null; then
  rm -f $SRCROOT/$TARGET_NAME/Generated/MockResults.swift
  xcrun --sdk macosx mint run uber/mockolo mockolo --sourcedirs $SRCROOT/$TARGET_NAME --destination $SRCROOT/$TARGET_NAME/Generated/MockResults.swift --mock-final
else
  echo "warning: Mint not installed, download from https://github.com/yonaskolb/Mint"
fi
```

#### postCompileScripts

Build PhasesでCompile Sourcesの直後に実行するスクリプトを指定します。

私の場合、製品ターゲットでSwiftLintとSpellCheckerのチェックスクリプトを記述しています。

```yaml:project.yml
targets:
  Foo:
    postCompileScripts:
      # SwiftLintで静的解析と自動修正
      - path: ./Scripts/XcodeGen/swiftlint.sh
        name: Run SwiftLint

      # SpellCheckerでスペルチェック
      - path: ./Scripts/XcodeGen/spellchecker.sh
        name: Run SpellChecker
```

```bash:./Scripts/XcodeGen/swiftlint.sh
if which mint >/dev/null; then
  xcrun --sdk macosx mint run swiftlint swiftlint autocorrect --format
  xcrun --sdk macosx mint run swiftlint swiftlint
else
  echo "warning: Mint not installed, download from https://github.com/yonaskolb/Mint"
fi
```

```bash:./Scripts/XcodeGen/spellchecker.sh
if ! which mint >/dev/null; then
  echo "warning: Mint not installed, download from https://github.com/yonaskolb/Mint"
  exit 0
fi

git_path=/usr/local/bin/git
files=$($git_path diff --diff-filter=d --name-only -- "*.swift" "*.h" "*.m")
if (test -z $files) || (test ${#files[@]} -eq 0); then
  echo "no files changed."
  exit 0
fi

options=""
for file in $files
do
  options="$options $SRCROOT/$file"
done

xcrun --sdk macosx mint run SpellChecker SpellChecker --yml $SRCROOT/spell-checker.yml -- $options
```

### schemes

スキームの設定です。

私はデフォルト（製品ターゲットと同名）のスキームのみ設定しており、 ~~説明に疲れてきた~~ 定義ファイルの全体像とXcode上のスキーム編集画面を見比べた方がわかりやすいので、本記事で詳細な解説はしません。

```yaml:project.yml
schemes:
  Foo:
    build:
      targets:
        Foo: all
    run:
      config: Debug
    test:
      config: Debug
      gatherCoverageData: true
      coverageTargets:
        - Foo
      targets:
        - name: FooTests
          parallelizable: true
          randomExecutionOrder: true
        - name: FooUITests
          parallelizable: true
          randomExecutionOrder: true
    profile:
      config: Release
    analyze:
      config: Debug
    archive:
      config: Release
```

### 定義ファイルの全体像

以上を踏まえ、私の定義ファイルの全体像を載せます。

```yaml:project.yml
name: Foo

options:
  bundleIdPrefix: com.theuhooi
  deploymentTarget:
    iOS: 13.0
  developmentLanguage: ja
  xcodeVersion: "11.3.1"
  carthageExecutablePath: mint run Carthage/Carthage carthage

settings:
  base:
    MARKETING_VERSION: 1.0.0
    CURRENT_PROJECT_VERSION: 1
    DEVELOPMENT_TEAM: XXXXXXXXXX
  configs:
    debug:
      DEBUG_INFORMATION_FORMAT: "dwarf-with-dsym"

packages:
  Rswift:
    url: https://github.com/mac-cain13/R.swift.Library
    version: 5.3.0
  Gedatsu:
    url: https://github.com/bannzai/Gedatsu
    version: 1.2.0
  SwiftPrettyPrint:
    url: https://github.com/YusukeHosonuma/SwiftPrettyPrint
    version: 1.0.0

targets:
  Foo:
    type: application
    platform: iOS
    sources:
      - Foo
      - path: "Foo/Generated/MockResults.swift"
        optional: true
        type: file
      - path: "Foo/Generated/R.generated.swift"
        optional: true
        type: file
    settings:
      base:
        INFOPLIST_FILE: Foo/Info.plist
        CODE_SIGN_ENTITLEMENTS: Foo/Foo.entitlements
        DEVELOPMENT_LANGUAGE: jp
        OTHER_LDFLAGS: $(inherited) $(OTHER_LDFLAGS) -ObjC
      configs:
        debug:
          OTHER_SWIFT_FLAGS: $(inherited) -Xfrontend -warn-long-expression-type-checking=500 -Xfrontend -warn-long-function-bodies=500
    dependencies:
      - package: Rswift
      - package: Gedatsu
      - package: SwiftPrettyPrint
      - carthage: Alamofire
    preBuildScripts:
      - path: ./Scripts/XcodeGen/rswift.sh
        name: Generate Resources with R.swift
        inputFiles:
          - $TEMP_DIR/rswift-lastrun
        outputFiles:
          - $SRCROOT/$TARGET_NAME/Generated/R.generated.swift
      - path: ./Scripts/XcodeGen/mockolo.sh
        name: Generate Mocks with Mockolo
        outputFiles:
          - $SRCROOT/$TARGET_NAME/Generated/MockResults.swift
    postCompileScripts:
      - path: ./Scripts/XcodeGen/swiftlint.sh
        name: Run SwiftLint
      - path: ./Scripts/XcodeGen/spellchecker.sh
        name: Run SpellChecker

  FooTests:
    type: bundle.unit-test
    platform: iOS
    sources:
      - FooTests
    settings:
      base:
        INFOPLIST_FILE: FooTests/Info.plist
    dependencies:
      - target: Foo

  FooUITests:
    type: bundle.ui-testing
    platform: iOS
    sources:
      - FooUITests
    settings:
      base:
        INFOPLIST_FILE: FooUITests/Info.plist
    dependencies:
      - target: Foo

schemes:
  Foo:
    build:
      targets:
        Foo: all
    run:
      config: Debug
    test:
      config: Debug
      gatherCoverageData: true
      coverageTargets:
        - Foo
      targets:
        - name: FooTests
          parallelizable: true
          randomExecutionOrder: true
        - name: FooUITests
          parallelizable: true
          randomExecutionOrder: true
    profile:
      config: Release
    analyze:
      config: Debug
    archive:
      config: Release
```

GitHub Gistにも上げています。
https://gist.github.com/uhooi/730c7aa286e730b392796a159e4d75f5

## バージョン管理から無視する

プロジェクトファイルのコンフリクトを防ぐため、生成されたプロジェクトファイルとワークスペースをバージョン管理の対象外にします。

Gitを使っている場合、以下を「.gitignore」に追加するのみでOKです。

```diff:.gitignore
+ *.xcodeproj
+ *.xcworkspace
```

## 操作方法

### プロジェクトファイルの生成

定義ファイルが存在するフォルダへ移動し、 `xcodegen generate` を実行するのみです。

Mintで管理している場合は、先頭に `mint run xcodegen` を付けます。

```
$ mint run xcodegen xcodegen generate
```

私はMakefileを作成し、 `make generate-xcodeproj` を実行すると「プロジェクトファイルの生成→CocoaPods用ワークスペースの生成→ワークスペースをXcodeで開く」を一括で行うようにしています。

```makefile:Makefile
PRODUCT_NAME := Foo

.PHONY: generate-xcodeproj
generate-xcodeproj:
	mint run xcodegen xcodegen generate
	bundle exec pod install
	make open

.PHONY: open
open:
	open ./${PRODUCT_NAME}.xcworkspace
```

## 注意：Xcode 11.5以上でMintを使うにはワークアラウンドが必要

2020/08/03現在、Xcode 11.5以上でMintをRun Script Phaseで使う場合、ワークアラウンドが必要です。
Mintの記事に詳細を記載したのでご参照ください。
https://qiita.com/uhooi/items/6a41a623b13f6ef4ddf0#注意xcode-115以上のrun-script-phaseで使うにはワークアラウンドが必要

## おわりに

最後まで読み、XcodeGenを使ってみようと思っていただければ幸いです☺️

まだ業務で使っておらず、個人でも使い倒していないため、問題などありましたらご指摘くださると嬉しいです。

## 参考リンク

- [yonaskolb/XcodeGen: A Swift command line tool for generating your Xcode project](https://github.com/yonaskolb/XcodeGen)
- [XcodeGen超入門 - Speaker Deck](https://speakerdeck.com/giginet/xcodegenchao-ru-men)
- [XcodeGenによる新時代のiOSプロジェクト管理 - クックパッド開発者ブログ](https://techlife.cookpad.com/entry/2019/04/26/110000)
- [2019年上半期のiOSアプリ開発振り返り – 株式会社キュリオシティソフトウェア](http://curiosity.co.jp/2019ios/)  
本記事よりXcodeGenのメリデメが詳しく書かれています
- [XcodeGen Example](https://gist.github.com/d-date/44510c5b4b3baf820033453bfeb235c3)
- [ShareExtension/project.yml at master · YutoMizutani/ShareExtension](https://github.com/YutoMizutani/ShareExtension/blob/master/project.yml)
- [UhooiPicBook/project.yml at develop · uhooi/UhooiPicBook](https://github.com/uhooi/UhooiPicBook/blob/develop/project.yml)