酢ろぐ!

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

BitriseでXcode12+Carthageを使ってiOSアプリをビルドしよう!

Xcode 12になって悲しいことにCarthageが使えなくなりました。説明するまでもありませんが、CarthageはiOSアプリ開発の主要なパッケージマネージャーのひとつです。

開発を止められないお仕事アプリは即日ですべてCocoaPodsへ移行しました。CocoaPodsは導入が簡単なので大好きなのですが、ビルド時間が長くなる傾向にあります。デバッグ実行のたびにつらみが増します。過去に「Bitriseで iOSアプリのビルド速度を cocoapods-binary を使って高速化する - 酢ろぐ!」で紹介したように、CI上ではcocoapods-binaryを使ってビルド時間の短縮を狙うことが可能です。

Apple公式であるSwiftPMへの移行も検討しましたが、Bitriseでキャッシュが効かずCIでのビルド時間が増えてしまうので不採用になりました。

Xcode 12の劇的なリリースから1ヶ月経って状況も落ち着いてきたので、Bitrise+Xcode12でCarthageを使ってiOSアプリをビルドするように戻しました。

実行環境

  • macOS 10.15.7 (19H2)
  • Xcode 12.0.1
  • Carthage v0.36.0

古いバージョンのCarthageを使っている場合には、Mac HomebrewでCarthageをアップデートしておくと良いかもしれません。

brew upgrade Carthage

Carthageは頻繁にアップデートがおこなわれないのでみんな最新のものを使っているかと思います。

carthage.shを準備する

この項目はまるっと 「Carthage/Xcode12Workaround.md at master · Carthage/Carthage · GitHub」 からの引用が多いです。WorkaroundスクリプトはXcodeのバージョンによって変わってしまうかもしれませんので、随時適切なスクリプトに読み替えてください。本記事では2020/10/16時点のスクリプトを利用しています。

carthageのコマンドの代わりに carthage.shスクリプトを使います。

mkdir -p bin
touch bin/carthage.sh

Carthage/Xcode12Workaround.md at master · Carthage/Carthage · GitHub」 から、スクリプトをまるっとコピーします。

# carthage.sh
# Usage example: ./carthage.sh build --platform iOS

set -euo pipefail

xcconfig=$(mktemp /tmp/static.xcconfig.XXXXXX)
trap 'rm -f "$xcconfig"' INT TERM HUP EXIT

# For Xcode 12 make sure EXCLUDED_ARCHS is set to arm architectures otherwise
# the build will fail on lipo due to duplicate architectures.

CURRENT_XCODE_VERSION=$(xcodebuild -version | grep "Build version" | cut -d' ' -f3)
echo "EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$CURRENT_XCODE_VERSION = arm64 arm64e armv7 armv7s armv6 armv8" >> $xcconfig

echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$(XCODE_PRODUCT_BUILD_VERSION))' >> $xcconfig
echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig

export XCODE_XCCONFIG_FILE="$xcconfig"
carthage "$@"

本プロジェクトでのディレクトリ構成

なお、本記事での紹介に使っているプロジェクトのディレクトリ構成は下図のようになっています。ルートディレクトリにプロジェクトファイルは置いていません。

f:id:ch3cooh393:20201016095935p:plain

BitriseのScriptステップでこの carthage.sh を実行しますので、本記事を読んで「Bitriseで動かないよ〜」という方がいれば適宜読み替えてください。

Bitriseでの設定

ローカルPC上でCarthage経由でライブラリをインストールするのは以下のコマンドを実行すればよいです。

chmod +x TweetClips/bin/carthage.sh
TweetClips/bin/carthage.sh bootstrap --platform iOS --cache-builds --project-directory TweetClips

このコマンドをBitrise上で再現する場合にはCarthageステップを使わずにScriptステップを使います。Bitrise上でのステップは以下のように書きました。

workflows:
  primary:
    steps:
    {{ 〜〜〜中略〜〜〜 }}
    - cocoapods-install@1.11.0: {}
    - script@1:
        title: install carthage
        inputs:
        - content: |-
            #!/usr/bin/env bash
            chmod +x TweetClips/bin/carthage.sh
            #export GITHUB_ACCESS_TOKEN="$GITHUB_ACCESS_TOKEN"
            TweetClips/bin/carthage.sh bootstrap --platform iOS --cache-builds --project-directory TweetClips

BitriseでCarthageを使っている方はご存知かと思いますが、Cartfileがルートディレクトリに存在しない場合エラーを吐くので、--project-directoryオプションでCartfileを置いている場所を指定しています。詳しくは「Bitriseでトラブル発生!Cartfile.resolved がルートディレクトリにないリポジトリのビルドが通らない - 酢ろぐ!」をご覧ください。

export GITHUB_ACCESS_TOKEN〜の部分はあってもなくても良いですが、CIサービス上でのビルド時間を短縮することができるので調べてみてください。具体的には下記のエラーを防ぐことができます。

API rate limit exceeded for xxx.xxx.xxx.xxx. (But here’s the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)

トラブル:Carthageディレクトリがキャッシュされない

Carthageステップを利用しているとBitrise側で自動的にキャッシュをよしなにしてくれます。

今回はScriptステップでcarthage.shを実行しているため、自動的にはキャッシュをプッシュしてもらえません。悲しい。cache-pushステップでキャッシュするディレクトリを手動で追加をする必要があります。

    - cache-push@2:
        inputs:
        - cache_paths: |-
            $BITRISE_CACHE_DIR
            TweetClips/Carthage -> TweetClips/Cartfile.resolved

GUI側で設定するのであればこの部分を編集してください。

f:id:ch3cooh393:20201016132613p:plain

トラブルシューティング:Command PhaseScriptExecution failedエラーが発生する

何もしていないのに下記のエラーが発生する。

The file couldn’t be saved.
Command PhaseScriptExecution failed with a nonzero exit code

macOSの再起動で発生しなくなります。

または、carthage copy-frameworks produces "The file couldn’t be saved." error · Issue #3056 · Carthage/Carthage · GitHubによれば、macOSのシステム設定でXcodeにフルディスクアクセス権限を付与した状態で、Run Scriptを以下のように書き換えることで発生しなくなります。

rm -rf ${TMPDIR}/TemporaryItems/*carthage*  
/usr/local/bin/carthage copy-frameworks

まとめ

以上でXcode12+CarthageでiOSアプリをビルドできるようになりました。

f:id:ch3cooh393:20201016121138p:plain

そもそもとしてcocoapods-binaryを使ってキャッシュを利用するようにしているので、Carthageを使うようにしてもCI上でのビルド時間自体はそんなに変わりませんが、ローカルPCでのデバッグ実行時のビルドが軽くなった印象はあります。

なお、FirebaseをCarthage経由でインストールする場合にはいくつか注意する点があるのであわせて関連記事もお読みください。