酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

xcframeworks に対応した Rome を使って Carthage のキャッシュをS3にアップロードして Bitrise で超高速にビルドしよう! #bitrise #bitrisearticle

CarthageのキャッシュをS3で共有できる「Rome」がxcframeworksに対応して帰ってきた!!この記事はiOS Advent Calendar 2021の12日目の記事です。

Romeとは?

ライブラリを事前にビルドしておいてiOSアプリのビルド時間を短縮するプロダクトに「Carthage」がある。Carthageの成果物をローカル・またはS3のリモートでキャッシュしておいてBitriseなどのCIサービスでもビルド時間を短縮するプロダクトに「Rome」がある。どちらもポエニ戦争に由来する名称でCarthageもRomeもググラビリティが最悪である。

github.com

「Rome」のxcframework対応が長い間止まっていたので、さくさんはxcframework対応のために Carthage/Buildディレクトリをzipで固めてS3に置いておき、Bitriseでビルドするたびにzipファイルをダウンロードする方法で対応していた。

2021年11月26日に「Rome」がxcframeworkに対応して帰ってきた。本記事ではさくさんが個人開発しているアプリにRomeを再導入するまでの手順を書き残している。過去に Carthage および Rome を使ったことない方には不親切な内容となっているがご了承いただきたい。

はじめに

2021年11月にさくさんはM1チップ(Apple Silicon)搭載のMacBook Pro (16-inch, 2021)に移行した。このタイミングで前々より計画していたxcframework対応を一気に進めることになった。

Rosetta上でXcodeとiOSシミュレータを使えばxcframework対応をする必要はなかったが、全社的にRosettaを使わない方針でいくことになった。四苦八苦試行錯誤しながら会社で開発しているアプリのM1対応を進めた。Twitterでうめきながら愚痴りながらなんとか環境を整えることができた。副産物的に「Four Cropper」が macOS でも動くようになった。

xcframework対応にあたって可能な限りCocoaPodsの利用をやめてCarthageに移行させることになった。Carthageでxcframeworkでビルドしたい場合には以下のコマンドで実行すればよい。

carthage update --use-xcframeworks --platform ios --cache-builds

Carthageでビルドできなかったライブラリは SwiftPM か CocoaPods を使ってインストールすることで回避できた。Carthage での xcframework対応の話はこれで終わりだ。

Carthage/Build ディレクトリを zip でまとめて Bitrise でダウンロードしていたが……

さくさんは会社でも個人でもアプリのビルドには Bitrise を使っている。git にプッシュすると Bitrise でビルドして TestFlight にアップロード・配信する流れができている。xcframework 対応にあたり問題になったのが、Carthage の Carthage/Build ディレクトリをどうやって Bitrise で扱うかだ。

アプリに Realm が含まれているとBitriseの最大ビルド時間である1時間半を大幅に超えてしまい、ビルドが強制的にabortされてしまうため、Bitrise上ではcarthage bootstrapは使えない。さくさんは Carthage/Build ディレクトリをzipで固めてS3にアップロードしておき、Bitriseでビルドするたびにzipファイルをダウンロードする方法で対応していた。

Bitriseでは resource-archive ステップを使うと、zipファイルをダウンロードして解凍の流れを実行してくれる。bitrise.ymlでは以下のように書く。指定したBuild.zipをダウンロードして、FourCropper/Carthage/以下に解凍してくれる。

workflows:
  primary:
    steps:
〜〜省略〜〜
    - cocoapods-install@2: {}
    - resource-archive@2:
        inputs:
        - extract_to_path: FourCropper/Carthage/
        - archive_url: https://example.com/XXXXXXXXXXXX/Build.zip

Build.zip は単純に以下のスクリプトで生成した Carthage/Build ディレクトリを圧縮したファイルである。s3cmdなどのコマンドを使ってBuild.zipをS3へアップロードするだけで簡単だ。

carthage update --use-xcframeworks --platform ios --cache-builds
cd /Users/ch3cooh/works/FourCropper_ios/FourCropper/Carthage/
rm -f Build.zip
zip -r Build.zip Build/

ライブラリの少ないアプリの場合はこの方法で問題ないが、前述の Realm などクソデカライブラリ容量の大きなライブラリが含まれているとBuild.zipは1GB近くになってしまう。Bitriseの resource-archive ステップで1GB近いのzipファイルをダウンロードするには10分ほど掛かってしまう。早期になんとかしたいと考えていたが代替案がなくそのままになっていた。

Rome の v0.24.0.65 で xcframeworks に対応したので、この Carthage/Build ディレクトリ丸ごと圧縮作戦をやめてRomeに切り替えていく。

Romeの初期設定

Carthageの設定に関しては本エントリでは扱わない。Romeを使うための準備方法を紹介していく。

Romeのインストール

RomeはHomebrewでインストールすることもできるが、Bitriseでの利用を前提にして取り回しの効くCocoaPods経由でインストールさせる。Podfileに追記しておく。

pod 'Rome'

CarthageのキャッシュをS3へアップロードするための準備

S3にxcframeworkをアップロードしたいので、ローカルPCからAWSへ接続するための準備をおこなう。過去にAWS SDKを使ったことがあれば設定は必要ないと思う。

AWSのIAMとバケットを作成する

AWSのIAMとバケットを作成する。

  • AmazonS3FullAccess ポリシーを持つユーザー
  • Carthage/Build のビルド済み .xcframework を格納する S3バケット

アクションを縛りたいがうまく行かなかったのでバケットポリシーは全許可にしている。

{
    "Version": "2012-10-17",
    "Id": "PolicyXXXXXXXXXX",
    "Statement": [
        {
            "Sid": "StmtXXXXXXXXX",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::XXXXXXX_S3_BUCKET_NAME"
        }
    ]
}
AWSの認証情報を入力する

aws configurateを使っても良いが、Romeの説明でコマンドラインを使ってたのでそれに倣った。

mkdir -p ~/.aws/
touch ~/.aws/credentials
touch ~/.aws/config
~/.aws/credentials
[default]
aws_access_key_id=AKIXXXXXXXXXX
aws_secret_access_key=UvPzXXXXXXXXXXXXXXXXXXXX
~/.aws/config
[default]
region=ap-northeast-1

Romefileの書き方

基本的にRomeはCartfile.resolvedを参照してよしなにしてくれる。

しかしリポジトリ名と生成されるxcframeworkの名称が異なる場合はキャッシュ対象から除外されてしまう。たとえばSentryのリポジトリ名はsentry-cocoaだが生成されるxcframeworkの名前はSentry.xcframeworkとなっている。細かいところでは-_の違いでも対象から除外されてしまう。たとえばUITextView-Placeholder なのに生成されるのが UITextView_Placeholder.xcframework の場合などである。

Romefileの書き方には難解な部分がある。さくさんが個人開発しているFour Cropperで実際に使っているCartfileとRomefileの例を紹介する。

FourCropperのCartfileは下記の通りだ。FourCropper自体が小規模なアプリなので利用しているライブラリは少ない。

github "yhirano/LicensePlistViewController"
github "bizz84/SwiftyStoreKit"
github "TimOliver/TOCropViewController"
github "takecian/SwiftRater"
github "devxoul/UITextView-Placeholder"
github "getsentry/sentry-cocoa"

Romefile では前述したようにリポジトリ名と生成されるxcframeworkの名称を関連付けする必要がある。FourCropperのRomefileは下記の通りだ。

cache:
  s3Bucket: XXXXXXX_S3_BUCKET_NAME

repositoryMap:
- TOCropViewController:
    - name: CropViewController
    - name: TOCropViewController
- sentry-cocoa:
    - name: Sentry
- UITextView-Placeholder:
    - name: UITextView_Placeholder

AWS SDKの場合リポジトリひとつに対して生成される xcframework が20以上あるので設定がややこしくなる。iOSエンジニア各位はどのライブラリをインストールしたらなんの xcframework が生成されているのか理解していると思うので、おそらくこの関連付けの作業は難なくこなせるかと思う。

以上でRomeの設定はできた。

ローカルPCでのRomeの使い方

S3へCartageのキャッシュのアップロードするにはローカルPC上で下記のコマンドを実行する。毎回すべてのライブラリをアップロードするのはS3の転送量的にも所要時間的にも勿体無いので、差分のみをアップロードする方法を採るべきだろう。

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

./Pods/Rome/rome download --use-xcframeworks --cache-prefix $SWIFT_VERION
carthage update --use-xcframeworks --platform ios --cache-builds
./Pods/Rome/rome list --missing --use-xcframeworks --cache-prefix $SWIFT_VERION | awk '{print $1}' | xargs -I {} ./Pods/Rome/rome upload "{}" --use-xcframeworks --cache-prefix $SWIFT_VERION

ローカルでXcodeを使う際には事前に下記のコマンドを実行する。これだけでS3から必要なxcframeworkをダウンロードできる。

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

./Pods/Rome/rome download --use-xcframeworks --cache-prefix $SWIFT_VERION

BitriseでのRomeの使い方

BitriseでRomeを使う際には一点注意することがある。xcframeworksに対応したRome v0.24.0.65は S3からキャッシュデータをdownloadする際にかならず失敗してしまう問題がある。すでにIssueが起票されているので修正はされると思うので、それまでの回避策を含めてBitriseでのRomeの使い方を紹介する。

Install Carthage/Build ステップ が成功しても失敗しても、次のステップから普通に実行したい場合どうするのか?

GUI上から「発生したエラーを無視してスキップする設定」ができないので、bitrise.ymlでis_skippable: trueを付与すれば良い。

    - cocoapods-install@2: {}
    - script@1:
        title: Install Carthage/Build
        is_skippable: true 
        inputs:
        - content: |-
            #!/usr/bin/env bash
            cd FourCropper
            export SWIFT_VERION=`xcrun swift --version | head -1 | sed 's/.*\((.*)\).*/\1/' | tr -d "()" | tr " " "-"`
            ./Pods/Rome/rome download --use-xcframeworks --cache-prefix $SWIFT_VERION
    - cache-push@2:
        inputs:
        - cache_paths: |-
            $BITRISE_CACHE_DIR
            FourCropper/Carthage -> FourCropper/Cartfile.resolved
            FourCropper/Pods -> FourCropper/Podfile.lock
            FourCropper/vendor -> FourCropper/Gemfile.lock

bitrise.ymlの設定が終われば、あとはBitriseから該当のAWS S3にアクセスできるようにアクセスキーを登録しておこう。AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION を忘れないで。

以上でBitriseでRomeを使う設定は完了だ。

xcframeworks対応のRomeを使ってみた結果ビルド速度が超高速になった!

Carthage/Buildまるごとzipで固める作戦から Rome に移行した。ビルド時間がどれだけ早くなったのかを確認していこう。

Four Cropperでのビルド時間は約12分から約9分へ短縮した。1ビルドの所要時間を約25%軽減することができた。

Four Cropperでのビルド時間は約12分から約9分へ短縮した

別のアプリAでのビルド時間は約16分から約13分へ短縮した。アプリAではアプリ内DBとしてRealmを使っている。1ビルドの所要時間を約20%軽減することができた。

アプリAでのビルド時間は約16分から約13分へ短縮した

別のアプリBでのビルド時間は約50分から約37分へ短縮した。このアプリではresource-archiveステップで9分〜10分かかっていた。この時間がマルッとなくなったのが嬉しい。1ビルドの所要時間を約25%軽減することができた。

アプリBでのビルド時間は約50分から約37分へ短縮した

追記:突然Romeが使えなくなることがある……

Rome v0.24.0.65 はやはり不安定なのかもしれない。とあるライブラリのインストール先を「元リポジトリ」と「フォークしたリポジトリ」とのどちらにするか実験するために、ライブラリのアップデート+Romeへのアップロードを繰り返していたところ、突然以下のエラーが発生するようになってromeが使えなくなってしまった。

rome: device /dev/random cannot be grabbed
CallStack (from HasCallStack):
  error, called at ./Crypto/Random/Entropy/Unix.hs:60:20 in cryptonite-0.26-1BsgpeCLUsqwiNhmB6AoC:Crypto.Random.Entropy.Unix

最終的には romeを一旦削除して再インストール、もう一度試したら正常に動くようになった。今回はCocoaPods経由でインストールしているためPodsディレクトリを削除して pod install で事なきを得た。

さいごに

この記事をみて Bitrise を使ってみたくなった方がもしいれば「https://app.bitrise.io/referral/a927d5dbff07cc1d」からBitriseのアカウントを作成してくれると嬉しい。

現在Bitriseの無料カウントでは1ビルドあたり30分までビルド時間を使うことができる。この紹介リンクからアカウントを作るとビルド時間が5分伸びるのでとても便利になる。GitHubのアカウントを持っていれば数クリックでBitriseのアカウントも作成できる。この機会にBitriseを知った方には是非利用して欲しい。