[iOS][Carthage] ビルド時間短縮のため外部ライブラリの一部をCocoaPodsからCarthageに移行した

プロジェクトが育つにつれビルド時間が長くなって開発効率が落ちてきていたので、改善の一環としてネックとなっていたCocoaPods管理下のライブラリ群の一部をCarthageに移管した。
今更感はさておき復習も兼ねて記事化と共有。

# 1. :arrow_down: Carthage インストール
Carthageが入っていない場合、インストール

```
$ brew update
```

```
$ brew install carthage
```

Carthageのバージョンが古い場合、アップデート

バージョン確認

```
$ carthage version
0.29.0
```

アップデート

```
$ brew upgrade carthage
```

.pkgでもインストール可能な模様。
https://github.com/Carthage/Carthage/releases

# 2. :page_facing_up: Cartfileの作成
Carthageのパッケージファイルを作成。

```
$ vi Cartfile
```

|パッケージマネージャ|CocoaPods|Carthage|Swift Package Manager|
|---:|:---:|:---:|:---:|
|パッケージファイル|Podfile|Cartfile|Package.swift|
|ロックファイル|Podfile.lock|Cartfile.resolved|Package.pins|

# 3. :pencil: PodfileからCartfileに移行
## 3-1. Cartfileの編集
指定方法は極めてシンプル。
とりあえずAlamofireから入れてみる。

```
github "Alamofire/Alamofire"
```

バージョン指定もpodと書き方は同じ

```
# 4.5.0固定
github "Alamofire/Alamofire" == 4.5.0
# 4.5以降
github "Alamofire/Alamofire" >= 4.5
# 4.5と互換性があるバージョン
github "Alamofire/Alamofire" ~> 4.5
```
参考: [ライブラリ管理ツールCarthageのCartfileの書き方](http://egg-is-world.com/2016/02/01/carthage-cartfile/)
但し、互換指定に関してCocoaPodsとは結果が異なるため注意が必要。

## 3-2. ビルド
Cartfileを保存したら `carthage update` を実行してビルド。

```
$ carthage update --platform iOS
```
`--platform iOS` はプラットフォームの指定。macOSやwatchOSが必要な人は指定なしで。
`--cache-builds` というオプションもあってビルド済のライブラリはスキップしてくれる。ビルドキャッシュのバージョンとかもみてくれている模様。 `xcode-select --switch`した時も大丈夫。
`--no-use-binaries` はGitHubに上がっているバイナリデータを使わないオプション。0.20.0以降、このオプションが無くても自動的に判定してSwiftバージョンが異なる場合などソースコードからビルドしてくれるようになったらしいのでこれはもう不要。

`carthage update` と `carthage bootstrap` は何が違うのかというと、
`update` はCartfileの記述からCartfile.resolvedにバージョンを書き込むのでアップデートされる可能性がある。
`bootstrap` はCartfile.resolvedで指定されているバージョンを使用する。

単にcheckoutしてビルドしたい時とか、CIでの実行は以下で実行するようにした。

```
$ carthage bootstrap --platform iOS --cache-builds
```

ビルドが終わると `プロジェクトルート/Carthage/Build/iOS` 以下に.frameworkができている。

## 3-3. Linked Frameworks and librariesに追加
プロジェクト > ターゲット > Generalタブ > Linked Frameworks and libraries に3-2でビルドした.frameworksを追加する。

`+` > `Add Other...` を押して `プロジェクトルート/Carthage/Build/iOS/Alamofire.framework` を選択し追加。
直接FinderからDropしてもOK。

メインターゲットに自前のEmbed Frameworkを含んでいてそれも同じframeworkに依存している場合、そのframeworkのターゲット > Generalタブ > Linked Frameworks and libraries にも同様に追加しておく。

あとUnit TestやUI Testのターゲットにも同様に追加しておきましょう。

- アプリのターゲット > Generalタブ > Linked Frameworks and libraries
- Unit Testのターゲット > Generalタブ > Linked Frameworks and libraries
- UI Testのターゲット > Generalタブ > Linked Frameworks and libraries
- 自前frameworkのターゲット > Generalタブ > Linked Frameworks and libraries

## 3-4. Build Phaseの追加
プロジェクト > ターゲット > Build PhaseにRun Scriptを追加する。
Shellの内容

```
/usr/local/bin/carthage copy-frameworks
```
Input Filesの追加

```
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
```

UI Testのターゲットにも同様の追加が必要。

## 3-5. podからの除外
Podfileの `pod 'Alamofire'` の記述を削除して `pod install` しておく

# 4. lint対象からの除外
lintツールとか入れている場合、そのままビルドするとframework内の記述が怒られてしまいビルドが通らないので、対象から除外しておく必要がある。
SwiftLintの場合

```yaml:.swiftlint.yml
...
excluded:
  - Pods
  - Carthage # 追加
...
```

# xcode-select
複数のXcodeバージョンを使い分けている人は、意図せずxcode-selectの向き先が想定していないXcodeバージョンを向いていることがあり、プロジェクトの設定とframeworkのSwiftのバージョンが合わない場合にビルドエラーになるので要チェック。

確認

```
$ xcode-select -print-path
```

変更

```
$ sudo xcode-select --switch /Applications/Xcode8.3.3.app
```

# 5. :black_nib: ライセンス表示
CocoaPodsでは自動的にライセンス表示のためのplistが作られるのでPodfileでSettings.bundle内にコピーすれば良いだけだったが、Carthageはシンプルが故に自前で行う必要がある。
併用しているのでCocoaPodsが作ったplistをベースにCarthage管理下のライブラリのライセンス情報を追加して新たなplistを生成してSettings.bundle内にコピーするシェルを書いてみた。（もっと効率良い書き方や方法があったらお教えください）

```shell:acknowledgements.sh
#!/bin/sh 

if [ $# -ne 1 ]; then
  echo "Error: Parameter is required."
  exit 1
fi

# Move to project root directory
cd $1

# Copy plist file to Settings.bundle
inPlistPath="Pods/Target Support Files/Pods-<APP_NAME>/Pods-<APP_NAME>-acknowledgements.plist"
outPlistPath="<APP_NAME>/Settings.bundle/Acknowledgements.plist"
cp "$inPlistPath" $outPlistPath

# Read contents of Carthage/Checkouts
directories="Carthage/Checkouts/*"

#  Add license to plist
for directory in $directories; do

  licenseTextPath="${directory}/LICENSE"
  licenseText=`cat $licenseTextPath`
  libraryName="`basename $directory`"
  item="<dict><key>FooterText</key><string>${licenseText}</string><key>Title</key><string>${libraryName}</string><key>Type</key><string>PSGroupSpecifier</string></dict>"
  keyPath="PreferenceSpecifiers.0"

  # Insert to plist file
  plutil -insert $keyPath -xml "$item" $outPlistPath

done

```

これをメインターゲット > Build Phase > New Run Script Phase して実行しておけば、ビルドごとに勝手に上書きされるようになる。

```
${PROJECT_DIR}/acknowledgements.sh ${PROJECT_DIR} 
```

# 6. :tools: プロジェクトをビルド
念のためCleanしてからビルド。
他のライブラリも同様に移行したい場合、ステップ3を行う。

# 7. その他
自分の場合、移行に伴い下記のタスクも必要になった。
- CI環境でCarthageに対応しておく。
- プロジェクトに README.md とか含んでいる場合、Carthageの記述を追加しておく。

# 感想
下記のライブラリをCarthageにしたがビルドが非常に早くなった！

```
github "Alamofire/Alamofire"
github "realm/realm-cocoa"
github "johnsundell/unbox"
github "mxcl/PromiseKit"
github "SnapKit/SnapKit"
```
自前で対応しないとならない部分もあるけど、いろいろ勝手に作られないし、何よりもわかりやすい。将来的にはCocoaPodsは廃止したいなぁ。
Bitrise でもCarthageステップを追加して対応し、２回目以降はworkflow実行時間が30%ほど削減できた :thumbsup:

---
[Carthageで必ずソースコードからインストールする場合](https://project-flora.net/2016/11/09/carthageで必ずソースコードからインストールする場合/)
[コマンドラインでplistを操作（データ追加・編集・削除）](https://qiita.com/trakwkbys/items/a94c4d43342e96352bde)