Carthage使用時のcopy-frameworksでのinputfiles、outputfilesの指定を簡易化する

##はじめに

以前投稿した [XcodeのBuildPhase RunScript でxcfilelistを使う](https://qiita.com/iganin/items/a1add30465542c4466fe)の続きのような内容です。Carthage の [0.31.1](https://github.com/Carthage/Carthage/releases)がリリースされたタイミングで、Carthage使用時のRun Scriptでのcopy-frameworksでxcfilelistが使用できるようになりました。
@mono0926 さんの[一連のTweet](https://twitter.com/_mono/status/1039326842375655424)で言及されていますが、input fileに何かしら修正を加えようとするとprojectファイルの修正になるため、いくばくかの手間がかかります。しかしながら、 .xcfilelistファイルに対しての修正であれば単純なscriptで対応可能です。
対応方法等は@mono0926さんのTweetに尽きると思いますが、場合によってはinputfileにTestフレームワークのような特定のframeworkは入れたくない、といった要件があるかと思います。
それも考慮し本記事では、以下について記載します。

* copy-frameworksのinputfilelist、outputfilelistを生成するshell scriptを作成し、 shellを実行することでsinputfilelist、outputfilelistを自動生成するようにする
* copy-frameworksに含めたくないframeworkを除外する（テスト用のフレームワークなど）

##検証環境
以下の環境を使用しています。
Xcode, Carthageは下記に記載のVersion以降である必要があります。

* macOS High Sierra Version 10.13.6
* Xcode Version 10.0.0
* Carthage Version 0.31.1

## Carthageのcopy-frameworks部分の対応を自動化する
対応の概要は下記です。1は省略します。

1.　Carthageでのライブラリの導入（carthage update, Link Binary and librariesでの取り込み）
2.　copy-frameworksの設定でinput filelists, output filelistsを追加
3.　carthageのinputfilelist、outputfilelistを生成するshellscriptを作成・実行する

### copy-frameworksの設定でinput filelist、 output filelistを追加
下記は概要になりますので、詳細につきましては別途下記記事をご参照ください。
[XcodeのBuildPhase RunScript でxcfilelistを使う](https://qiita.com/iganin/items/a1add30465542c4466fe)
Build Phasesの Carthage用の Run Scriptに Input Filesの代わりにInput File Listsを記載します。
`$(SRCROOT)/ReduxSample/Support/CarthageInput.xcfilelist`、`$(SRCROOT)/ReduxSample/Support/CarthageOutput.xcfilelist`部分は任意のパスに書き換えてください。
<img width="821" alt="スクリーンショット 2018-11-09 23.17.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/154099/91d9a3b5-d243-497b-6ca0-80f48edac215.png">


### carthageのxcfilelistを自動生成できるようにする

当初はBuild Phasesにてxcfilelist作成用のShell Script実行のフェーズを追加することで完全に自動化対応されるようにしようかと考えていましたが、 Build Phasesの inputfilesやoutputfilesはビルド開始時点のものを見ているようで、Build Phases中に書き換えても反映されないようでした。（こちら挙動からの推測になりますので、誤りがある場合はご指摘ください）
そのため、現実的な案としては、手動でTerminalでscriptを叩く、になりそうです。

以下のShell Scriptは@mono0926 さんの[Tweet](https://twitter.com/_mono/status/1039326842375655424)で記載されていたShell Scriptを参考にして作成させていただきました。

2文目でInputファイルの書き換え、3文目でOutputファイルの書き換えを行なっています。該当スクリプトの場所や作成後のファイルの出力先のRootとしてgitのルートディレクトリを使用しています。（※ `git rev-parse --show-toplevel` ）
そのため、下記で使用しているScriptを使用するためには該当プロジェクトでGitを使用している必要があります。また`{YourProjectFileName}`部分はご自身のProject名に変更してください。

```Shell
GitRoot=$(git rev-parse --show-toplevel)
ls $GitRoot/{YourProjectFileName}/Carthage/Build/iOS | grep -e .framework$ | sed 's/.*/$(SRCROOT)\/Carthage\/Build\/iOS\/&/' > $GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/CarthageInput.xcfilelist
ls $GitRoot/{YourProjectFileName}/Carthage/Build/iOS | grep -e .framework$ | sed 's/.*/$(BUILT_PRODUCTS_DIR)\/$(FRAMEWORKS_FOLDER_PATH)\/&/' > $GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/CarthageOutput.xcfilelist
```

## 特定のframeworksをcopy-frameworksから除外する
例えば、RxSwiftをCarthageで導入した場合、Carthageのフォルダ内にテストフレームワークであるRxBlocking.framework, RxTest.frameworkが含まれます。これらはユニットテストなどの場合には必要ですが通常のデバックビルドやArchiveでは必要ではありません。
そのため、このようなライブラリは通常のビルドターゲットのfilielistからは除外する必要があります。以下でその方法を記載します。

やり方は色々あるかと思いますが、Testで使用したいライブラリを記載したxcfilelistをinput、outputそれぞれ,PrivateCarthageInput.xcfilelist、PrivateCarthageOutput.xcfilelistとして、これらに記載されているライブラリについては、ビルド用のファイルリストから除外するよう対応します。（名称は任意で問題ありません）

詳細は実際のShell Scriptに記載してありますが、以下で大まかな流れをお伝えします。Carthage/Build/iOSフォルダ内から*.frameworkを全て取り出し、一時的なxcfilelistを作成します。その後、そのxcfilelistと除外したいフレームワークの入ったxcfilelistとの差分をとり、その内容を実際に使用するxcfilelstとして吐き出します。
最後に一時ファイルとして作成したfilelistを削除します。
なお注意点といたしまして、joinがshでは使用できなかったため、下記コマンドはbashで実行する必要があります。

以下、実際に使用したShell Scriptのサンプルになります。
※先ほどと同様に`{YourProjectFileName}`はご自身のプロジェクト名にご変更ください。

```Shell
#!/bin/bash

#  making_carthage_filelist.sh
#  ReduxSample
#
#  Created by iganin on 2018/10/21.
#  Copyright © 2018 iganin. All rights reserved.

# GitRootの定義
GitRoot=$(git rev-parse --show-toplevel)

# 除外対象のファイルを含んだ状態でTempのインプット用のfileListを作成する
ls $GitRoot/{YourProjectFileName}/Carthage/Build/iOS | grep -e .framework$ | sed 's/.*/$(SRCROOT)\/Carthage\/Build\/iOS\/&/' > $GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/TempCarthageInput.xcfilelist

# 一時的に作成したInputFileListおよび除外対象ライブラリが記載されたxcfilelistまでのPathを変数として定義する
TempInputFileList=$GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/TempCarthageInput.xcfilelist
InputFilterFileList=$GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/PrivateCarthageInput.xcfilelist
# 除外対象のライブラリを行から取り除く
join -v 1 <(cat $TempInputFileList|sort -u) <(cat $InputFilterFileList|sort -u) > $GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/CarthageInput.xcfilelist

# 除外対象のファイルを含んだ状態でTempのアウトプット用のfilelistを作成する
ls $GitRoot/{YourProjectFileName}/Carthage/Build/iOS | grep -e .framework$ | sed 's/.*/$(BUILT_PRODUCTS_DIR)\/$(FRAMEWORKS_FOLDER_PATH)\/&/' > $GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/TempCarthageOutput.xcfilelist

# 一時的に作成したOutputFileListおよび除外対象ライブラリが記載されたxcfilelistまでのPathを変数として定義する
TempOutputFileList=$GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/TempCarthageOutput.xcfilelist
OutputFilterFileList=$GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/PrivateCarthageOutput.xcfilelist
# 除外対象のライブラリを行から取り除く
join -v 1 <(cat $TempOutputFileList|sort -u) <(cat $OutputFilterFileList|sort -u) > $GitRoot/{YourProjectFileName}/{YourProjectFileName}/Support/CarthageOutput.xcfilelist

# 一時的に作成したfilelistを削除する
rm $TempInputFileList
rm $TempOutputFileList
```

## 所感
Carthageのcopy-frameworksに必要なinput filesを手動で取り込むのは、手間となることが多く、忘れがちでもあります。Carthageの対象フレームワークが増えると重複して登録してしまうようなことや、必要なフレームワークを追加し忘れてしまうようなこともあります。
この部分を自動化することで、そのような手作業によるミスを減らすことができ、より効率的に開発できるのではないでしょうか。

## 参考文献
* [コマンドラインで集合演算](https://qiita.com/highfrontier/items/610cd285f0c0de480ac9)
* [XcodeのBuildPhase RunScript でxcfilelistを使う](https://qiita.com/iganin/items/a1add30465542c4466fe)
* [Git tips: ローカルリポジトリのルートやカレントディレクトリとの相対パスを取得する&その応用](https://qiita.com/NPoi/items/97f085f6578be3559ac5)