iOS
Carthage
iOSDay 5

RomeでCarthageのビルドコストを下げよう

はじめに

iOSアプリ開発を行うとき、ライブラリの導入にCarthageを使うことがあると思います。
Carthageは、CocoaPodsに比べて事前にビルドを行うためコンパイル時間が短い、ワークスペースが弄られないといったメリットがあります。
前者の利用で使っているところも多いのではないでしょうか

Carthageのビルド時間は長い?

Carthageはその特性上、一度全てのライブラリをビルドしてフレームワークを作成する必要があるため、環境構築に時間がかかります。
またSwiftのABI安定化はまだなので、XcodeをアップデートしてSwiftのバージョンが変わると再度ビルドする必要があり、地獄をみます。
ライブラリのアップデートが必要になった時も同じく地獄をみます。

このビルド時間問題で影響を受けるのは、数人〜数十人のエンジニアを抱えているチームだと思っています。
個人開発など、自分だけが再ビルドすれば良いのであれば特に問題にはならないと思いますが、他の方々もビルドする必要があると、その分進捗が死にます。
実際にCarthageのビルドがボトルネックとなったことがあり、割と笑えません。

よくある解決方法として、生成されたフレームワークをGitに含めるという方法もありますが、
使っているライブラリの量によってはウンGBになることもあり、リポジトリが肥大化するという問題が発生します。
またチーム内でXcodeのバージョンを揃えていない場合、前述したSwiftバージョン問題が発生する可能性があります。

…とはいえ、何らかの方法でフレームワークをキャッシュしてうまいことできないかなぁと調べていたところ、Romeというツールがいい感じだったのでご紹介いたします。

Romeとは

Carthageの生成物をオブジェクトストレージに共有できるツールです。
現在Amazon S3, S3互換のあるMinio, Cephに対応しています。

GitHub - blender/Rome: A cache tool for Carthage

使い方

READMEに記載されている通りなのですが、HomebrewかCocoaPods経由で導入ができます。
今回はHomebrewを使います。

brew install blender/homebrew-tap/rome

AWS S3の認証情報を設定する

キャッシュ先としてS3を利用しますので、
バケットへのアクセス権限を持ったユーザーの作成とアクセスキーID, シークレットキーを入手します。

~/.bash_profile
export AWS_ACCESS_KEY_ID=<ACCESS_KEY_ID>
export AWS_SECRET_ACCESS_KEY=<SECRET_ACCESS_KEY>
export AWS_REGION=<REGION>

Romeファイルの作成

次に、プロジェクトのあるディレクトリでRomefileを作成します。中身はYAMLで書いていきます。

Cache(必須)

s3Bucketに作成したバケット名を指定します。ローカルにキャッシュすることも可能です。

Romefile
cache:
  s3Bucket: ios-xxapp-carthage-cache
#    local: ~/Library/Caches/Rome
Repository Map

RomeはCartfile.resolvedを参照するため、リポジトリ名とフレームワーク名が異なる場合うまくキャッシュができません。依存ライブラリに関しても同様です。
Repository Mapでリポジトリ名とフレームワーク名を明記する必要があります。
例えばFacebook-SDK-Swiftですと、フレームワーク名は「FacebookCore」, 「FacebookLogin」, 「FacebookShare」となるので、これらを指定する必要があります。

Romefile
repositoryMap:
- Facebook-SDK-Swift:
    - name: FacebookCore
    - name: FacebookLogin
    - name: FacebookShare
- facebook-objc-sdk:
    - name: FBSDKCoreKit
    - name: FBSDKLoginKit
    - name: FBSDKShareKit
- xxxx-ios:
    - name: XXXX

Ignore Map

キャッシュする必要のない、したくないライブラリを指定できます。

Romefile
ignoreMap:
- xxxxx:
    - name: xxxxx

共有されているライブラリを扱う

S3からダウンロードする
rome download --platform iOS
S3へアップロードする
rome upload --platform iOS
S3に共有されていないライブラリを表示
rome list --missing --platform iOS

--cache-prefixについて

そのまま使っていると、s3Bucketで指定した階層にダウンロード/アップロードされてしまうため、チーム内のSwiftのバージョンが統一されていない場合Swiftバージョン問題が発生します。
これを回避するために、Romeではオプションとして--cache-prefixが用意されており、Swiftのバージョンごとにprefixを分けることができます。

rome download --platform iOS --cache-prefix Swift4_2

READMEには、xcrun swift --versionで取得したバージョンを使う方法が記載されていました。
私はこちらを使っています。

--cache-prefix `xcrun swift --version | head -1 | sed 's/.((.)).*/\1/' | tr -d "()" | tr " " "-"`

Gitから引っ張ってきた時とかにいい感じにライブラリを揃える

S3から引っ張ってきた後、未ビルドでアップロードされていないものだけをビルドしてアップロードするようにします。
ライブラリを追加した時や、gitからcheckout/pullした時に呼び出すといい感じにやってくれるはず。

cache_prefix=`xcrun swift --version | head -1 | sed 's/.*\((.*)\).*/\1/' | tr -d "()" | tr " " "-"`
platform='iOS'

rome download --platform $platform --cache-prefix $cache_prefix
rome list --missing --platform $platform --cache-prefix $cache_prefix | awk '{print $1}' | xargs sh -c "carthage update --platform $platform --cache-builds; rome upload --platform $platform --cache-prefix $cache_prefix"

感想

Romeのおかげでビルド地獄から抜け出せそうな気がしてます。ありがとうRome

参考

GitHub - blender/Rome: A cache tool for Carthage