【XcodeGen】で、AdHoc配信までできるiOSプロジェクトを作った話

# tldr
- xcodegenでちゃんとAdHoc配信までできる設定をするのは結構大変

-> なので、自分で頑張って作ってみました。あとXcodeGenの考察を色々します。

# はじめに
どうも。新卒1年目のiOSエンジニアです。

- XcodeGenでプロジェクト管理をする
- 完成したアプリはdeploygateで配信する

という要件のiOSプロジェクトに先日まで参加していたのですが、色々苦労したので、そのときの話を語ります。

# そもそもXcodeGenって
iOSプロジェクトを複数人で開発する場合、プロジェクトのファイルを管理する`.xcodeproj`ファイルのコンフリクトがよく起こります。大規模開発だとよく起こるトラブル(らしい)です。

これを解消するのが[XcodeGen](https://github.com/yonaskolb/XcodeGen)で、プロジェクトのソースやビルド設定などをyamlファイルで定義し、yamlファイルとソースコードから開発環境となる`.xcodeproj`ファイルを一意に生成できるツールです。即ち、`.xcodeproj`がgit上に無くても、yamlファイルさえあれば開発環境を他の開発者のPCで再現できるようになります。

 **「`.xcodeproj`ファイルを共有せずに済む仕組みとツールがあれば、そもそもコンフリクトとか起こらないよね！ :smile: 」** という寸法です。

# Xcodegenを使う
## XcodeGenでgenerateするまで
READMEを見れば大体わかりますが、インストールして、

```bash
$ brew install xcodegen
```
依存しているライブラリとソースコードを入れるディレクトリの準備をして
(ソースコードを入れるディレクトリは先に用意しないと`xcodegen generate`できません)

```bash
$ cd (プロジェクトルート)
$ mkdir (ソースコードを入れるディレクトリ)

$ pod init
$ vi Podfile
$ pod install

$ vi Cartfile
$ carthage bootstrap --no-use-binaries --cache-builds --platform ios
```
yamlファイルを書いて (後述)

```bash
$ vi project.yaml
```
generateします。歯車3つ出てきたら成功です :tada: 

```bash
$ xcodegen generate
Loaded project:
  Name: testProject
  Targets:
    testProject: iOS application
⚙️  Generating plists...
⚙️  Generating project...
⚙️  Writing project...
Created project at ($PROJECT_ROOT)/.xcodeproj

$ ls
$PROJECT_NAME         Package.swift         Pods                  Sources               project.yml
Cartfile              Podfile               README.md             Tests                 testProject.xcodeproj
```

生成されたら容赦無くプロジェクトを開いてやりましょう。CocoaPodsを使う場合は`.xcworkspace`も一緒に生成されているので無問題です :thumbsup: 

```bash
$ open testProject.xcworkspace
```

## project.yamlを書く
### 配信しない(とりあえずprojectファイルを生成して開発したい)場合
これ↓だけあればとりあえずprojectファイルが作れます。

```yaml:project.yaml
name: testProject
  deploymentTarget:
    iOS: "11.0"
targets:
  testProject:
    platform: iOS 
    type: application
    sources:
      - path: testProject
      - path: testPeoject/R.generated.swift
        optional: true
    info:
      path: testProject/Info.plist
      properties:
        CFBundleDevelopmentRegion: ja_JP
        UISupportedInterfaceOrientations: [UIInterfaceOrientationLandscapeRight]
        UIRequiresFullScreen: "YES"
        UILaunchStoryboardName: "LaunchScreen"
        NSCameraUsageDescription: "カメラ撮影をするため" 
        CFBundleDisplayName: "AppName"
        CFBundleIdentifier: "com.skamada.testProject"
    dependencies:
      - carthage: RxSwift
      - carthage: RxCocoa
      - carthage: RxRelay
      - carthage: SwiftyBeaver
      - carthage: Reachability
      - carthage: Nuke
    preBuildScripts:
      - path: /bin/sh
        name: Run R.swift
        inputFiles:
          - $TEMP_DIR/rswift-lastrun
        outputFiles:
          - $SRCROOT/$PROJECT_NAME/R.generated.swift
        script: |
                "Pods/R.swift/rswift" generate "$SRCROOT/testProject/R.generated.swift"
```

ここで使っているのは

- `name` プロジェクト名。
- `deploymentTarget` 対応する(buildする)OSバージョン 
    - `sources` ソースコードを格納するディレクトリ名。ディレクトリはあらかじめ作っておきましょう。
    - `optional` R.swiftのR.generated.swiftなど、ビルドして初めて生成されるファイルはoptional指定が必要です。
    - `info` `Info.plist`に設定したいことを羅列します。`CF~~~`とか`UI~~~`とか、`Row Keys`での指定が必要です。XcodeのInfo.plistで`右クリック > Show Row Keys/Values`で1つずつ確認してみましょう。Info.plistの生成からしてくれます。
    - `dependencies` 依存するcarthageライブラリや、他のターゲット(`target`)、フレームワーク (`framework`)、sdk(`sdk`)を指定すれば、勝手に設定してくれます。
    - `preBuildScripts` R.swiftのように、ビルドの前後にスクリプトを走らせたい場合は設定しましょう。`postBuildScripts`とか、`postCompileScripts`とかもあります。

### AdHoc配信をしたい(証明書、プロビ周りの設定をしたい)場合 

こんなの↓を追加で設定してあげましょう。AdHoc配信のために必要なものはここでは説明しません。このあたりの記事を読んでみましょう。
[【iOS】複雑な証明書周りをあっさり整理してみた](https://shigu493.com/%E3%80%90ios%E3%80%91%E8%A4%87%E9%9B%91%E3%81%AA%E8%A8%BC%E6%98%8E%E6%9B%B8%E5%91%A8%E3%82%8A%E3%82%92%E3%81%82%E3%81%A3%E3%81%95%E3%82%8A%E6%95%B4%E7%90%86/)

```yaml:project.yaml
    settings:
      base:
        PRODUCT_BUNDLE_IDENTIFIER: "com.skamada.testProject"
        CODE_SIGN_STYLE: Manual
      configs:
        debug:
          CODE_SIGN_IDENTITY: iPhone Developer
          PROVISIONING_PROFILE_SPECIFIER: Project-profile-debug
          DEVELOPMENT_TEAM: # development teamのID
        release:
          CODE_SIGN_IDENTITY: iPhone Distribution
          PROVISIONING_PROFILE_SPECIFIER: Project-profile-release
          DEVELOPMENT_TEAM: # development teamのID
    scheme:
      targets:
        testProject
```
- `settings` ここ↓の画面で設定できることをあれこれ設定できます。
<img width="1172" alt="スクリーンショット 2019-08-08 20.23.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203273/1df41134-8f4b-f0e8-23e0-6e801022d4e6.png">
  - base: `debug`とか`release`とかに共通して設定したいことを記述すると一気に設定してくれます。
    - `PRODUCT_BUNDLE_IDENTIFIER` 配信時の `Bundle ID`です。 `CFBundleIdentifier`と別個に設定しないといけないみたいです。
  - configs: `debug`とか`release`とかで分けて設定したい場合は記入しましょう。
    - `CODE_SIGN_IDENTITY` `iPhone Developer`とか、`iPhone Distribution`とかです。
    - `PROVISIONING_PROFILE_SPECIFIER` プロビの名前を書きましょう。
    - `DEVELOPMENT_TEAM` Apple Developerの**チームのID**を記入しましょう。チーム名ではありません。 (ここで数時間ハマりました)
  - `scheme` `Build Scheme`↓の設定です。この設定をしないと、Xcodeさんの気分次第で`No Scheme`になってしまいます。  
  <img width="254" alt="スクリーンショット 2019-08-08 20.31.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203273/d8838c28-70bc-90ba-c1e0-73cc36eb12cf.png">

## `.gitignore`に追記する
`.xcodeproj`と`Info.plist`はproject.yamlから一意に生成できる
∴ `.xcodeproj`と`Info.plist`はわざわざgit共有しなくてもいい  
ので、gitignoreに追加してあげましょう。 

```bash:.gitignore
Info.plist
*.xcworkspace
*.xcodeproj
```

## project.yamlを作るのに苦労したところ

配信設定などは特に、project.yamlを作るのに結構苦労しました。

- なぜ苦労したか
    - `DEVELOPMENT_TEAMはチーム名でなくてチームID`に気づくのに時間がかかった
    - プロジェクトを完成させるのが最優先で、project.yamlを完成させるのは必須ではなかったから
    - 足りない設定は最悪プロジェクト生成後に手作業で生成すればいいから  

DEVELOPMENT_TEAMはともかく、モチベーション的な要素が大きかった。
面倒ではあるけど、プロビの設定とかはXcode上でも2クリックくらいで済むので後回しになってました。何より先にちゃんと要件を満たしたコードを書かないと！と思うのが実装者の性。。。？

# メリットとデメリットの整理
## メリット
### `.xcproject`ファイルがコンフリクトしない
そもそもgit共有しない運用ができるので。
### targetの追加が楽
ビルドターゲットの追加は`project.yaml`に数行追加するだけで済むので楽になるはずです。大規模になると恩恵が大きそう。
## デメリット
### ファイルツリーが強制的に同じ位置になる  
「自分にとって見やすい配置」とかあると思うんです。でもXcodeGenだとアルファベット順強制です。 
ちなみに、XcodeGenを使用するとこの仕様のおかげでファイルツリー構成が同じになるので、`.xcodeproj`をgitに共有しても`.xcproject`コンフリクトはあんまり起こらない（はず）です。[^1]

[^1]: [.gitignoreで`.xcodeproj`を共有しないようにする](https://qiita.com/s-kamada/items/c7003bd19238dd8f70f0#gitignore%E3%81%AB%E8%BF%BD%E8%A8%98%E3%81%99%E3%82%8B)のは私オリジナルの運用方法です。コンフリクトのリスクを最小限に抑えたいならそもそも共有しなければいいじゃん、っていう発想になるよね :thinking: ってことでこの運用にしました。

### 学習コストがかかる
ツールを導入するので当たり前ですが。設定を1つ追加するたびにREADMEを漁ることになるので学習コスト高めです。
### `git checkout`のたびに`generate`が必要
ライブラリ構成やファイルツリー構成がproject.yamlに依存するせいか、作業ブランチを変更するたびに`generate`しないとうまく動作しません。iOS開発に慣れている人ほど面倒くさいと感じるはず。
### `generate`と `pod install`はセットでやる
podとxcodeの仕様上仕方ないんだと思いますが、`xcodegen generate`をするたびに`pod install`しないと、podで導入したライブラリは認識されません。
即ち、作業ブランチを変える際は

`git checkout ~~` -> `xcodegen generate` -> `pod install`

の3コマンドが必須になります。
3コマンドとなると結構手間なので、コマンドのaliasを作成するなどして対応するのが吉です。

# さいごに：無理して全部のプロジェクトに導入しなくていい。でも1回試してみよう
当然導入するとプロジェクトメンバー全員に対して大きめの学習コストが発生するし、XcodeGen導入によって受けられる恩恵はプロジェクト規模にかなり依存する印象でした(規模大きいほど恩恵大)。試しに使ってみてもいいですがどのプロジェクトに導入するかは考えた方が良さそうです。
とはいえ恩恵はしっかりあるので、せひ一度試してみてください。