XcodeGen+Cocoapods環境でのSwiftPackageManagerの導入

# はじめに
今回`Swift Package Manager`を導入してみました。
`Swift Package Manager`自体はすでに数年前からあり、Xcode11が発表された段階でXcodeに統合されました。
そのため既に導入しているプロジェクトが多いと思います。

しかし現在継続して運用しているプロジェクトでは`XcodeGen`+`CocoaPods`という環境であり導入が遅れていました。
その上記のような環境である点を踏まえ、導入手順やつまづいた箇所をまとめました。

### なぜ`Swift Package Manager`導入に思い至ったか
#### ビルド時間の解消に向けて
`CocoaPods`を導入しており、なおかつ`CocoaPodsBinary`も導入していました。
その為ある程度はプロジェクトビルド時におけるPods自体のビルド時間は抑えられていましたが、Binary化出来ずそのままになっていたPodが何個かありました。
プロジェクトの構成の問題もあるかもしれませんが、これらのPodが差分ビルド時でも毎回ビルドされていました。
`Swift Package Manager`に導入することにより毎回ビルドがされなくなるのではと考え導入を行いました。

#### 導入して変わったこと
* [初回] `xcodegen generate`後にResolve Packagesが発生　→　+30~40秒ほど
* [毎回] 1回当たりのビルド時間改善　→　-5~10秒ほど

上記の結果はPodsを複数`Swift Package Manager`に移行した合算になります。
移行したものは下記の通りになります。
* SVGKit
* Reachability
* GoogleSignIn
* Realm
* AFNetworking
* Firebase
* RxSwift
* RxOptional
* RxDataSources
* SwiftyXMLParser

移行したPodsが増えるほど1回当たりのビルド時間は減りますが、Resolve Packagesの時間は増えていきます。
しかしResolve Packagesは`xcodegen generate`後の初回のみ待てばよいのでトータルではマイナスになっています。


### 現在の環境
* Xcode 14.0.1
* XcodeGen 2.29.0
* CocoaPods 1.11.3
* CocoaPodsBinary 0.4.4

# 導入について
### 導入の手順
手順は以下の通りとなります。

1. Podfileから対象のPodの記載を削除
2. project.ymlの`packages:`にPackageを記載
3. project.ymlの`targets:`にPackageを記載

#### 1.Podfileから対象のPodの記載を削除
まず、Podfileから対象のPodを削除します。
Podでバージョンを指定していた場合、Swift Package Managerでも指定するのでメモして下さい。
次に`pod install`コマンドによりPodfile.lockを更新します。
これによりCocoaPodsで使用していたライブラリが削除されます。

#### 2.project.ymlの`packages:`にPackageを記載
Projectで導入するPackageを記載していきます。
まずはXcodeでPackageが提供されているか確認して下さい。
確認手順は以下の通りとなります。

1. Xcodeを開く
1. File -> Add Package から `Swift Package Manager`のウィンドウを開く(画像参照)
1. ウィンドウ右上の検索窓にURLを記入
1. 対象のPackageが表示され、プロジェクトに追加できればOK
![スクリーンショット 2022-12-21 12.06.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58367/38916c00-da99-bb82-a055-0ab1e3ca24fc.png)

次に確認したPackageをproject.ymlに記載します。
`packages:`の配下にPackage名とURL、そしてブランチやバージョン等の指定が必要になります。

```project.yml
name: SampleProject
packages:
  Firebase:
    url: https://github.com/firebase/firebase-ios-sdk
    exactVersion: 8.15.0
  GoogleSignIn:
    url: https://github.com/google/GoogleSignIn-iOS
    exactVersion: 6.1.0
  Realm:
    url: https://github.com/realm/realm-swift
    exactVersion: 10.16.0
```

こちらは備考になります。
Packageのバージョン指定には他の指定方法も行えます。
よく使うであろう記述方法をピックアップしておきます。

```project.yaml
exactVersion: 8.15.0 // バージョン指定：8.15.0を指定
maxVersion: 8.15.0 // バージョン指定：8.15.0以下を指定
branch: master // ブランチ指定：masterを指定
```

他の細かな指定方法については[こちら](https://github.com/yonaskolb/XcodeGen/blob/6f331720c90438070b67195f3b2327c96b8a1a2b/Docs/ProjectSpec.md)を確認して下さい。

### 3.project.ymlの`targets:`にPackageを記載
Targetsで導入するPackageを記載します。
こちらはPackageによっては`product:`の指定まで必要になります。

```project.yaml
targets:
    SampleTarget:
        dependencies:
            - package: GoogleSignIn
            - package: Realm
              product: RealmSwift
            - package: Firebase
              product: FirebaseAnalytics
            - package: Firebase
              product: FirebaseMessaging
            - package: Firebase
              product: FirebaseCrashlytics
            - package: Firebase
              product: FirebasePerformance
```

Packageにどのような`product`が含まれているかは`Package.swift`を閲覧することで確認出来ます。
Xcodeの`Show the Project Navigator`からPackageを選択することで`Package.swift`を確認することが出来ます。

``` Realm/Package.swift
let package = Package(
    name: "Realm",
    platforms: [
        .macOS(.v10_10),
        .iOS(.v11),
        .tvOS(.v9),
        .watchOS(.v2)
    ],
    products: [
        .library(
            name: "Realm",
            targets: ["Realm"]),
        .library(
            name: "RealmSwift",
            targets: ["Realm", "RealmSwift"]),
    ],
```

# 導入について - 個別編
### FirebaseCrashlytics
`CocoaPods`で導入している場合、BuildPhasesにスクリプトを記述していると思います。
しかし`CocoaPods`から`Swift Package Manager`に変更することにより書き換える必要があります。

* TARGESのBuildPhasesを開く
* RunScriptで行っているスクリプトを書き換える
  * 旧：`${PODS_ROOT}/FirebaseCrashlytics/run`
  * 新：`${BUILD_DIR%Build/*}SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run`

XcodeGenの場合はBuildPhasesでの書き換えではなく、project.yml内の`scripts:`を書き換える必要があります。

# まとめ
### 今後について
1回当りのビルド時間は減らすことが出来ました。
しかしブランチを切り替えて`xcodegen generate`を走らせた際のResolve Packagesで待たされるのが億劫に感じます。
キャッシュ化が行えないのか調べてみましたが基本的にはライブラリ側がxcframeworkに対応していないと難しそうです。
xcodegen自体から脱却するもありかと思いますので今後はそちらを調べてみようかと思います。

# 参考
* [Swift Package Manager で Firebase をインストールする](https://firebase.google.com/docs/ios/swift-package-manager?hl=ja)