FlutterアプリのビルドにXcode Cloudを使ってみた
NTTレゾナントテクノロジーでiOS/Androidのアプリ開発エンジニアを担当している長谷川です。
今回は、Flutter製プロジェクトのiOSアプリビルドにXcode Cloudを使ってみました。
Xcode Cloudについて
概要
CI/CD環境を提供しているサービスとして、BitriseやCircleCI、Codemagicなどがありますが、この環境をAppleが公式で用意したものになります。私達が普段開発に使用しているIDE「Xcode」上からワークフローの設定・編集や各ビルド状態やログの確認などができます。
特徴
Apple公式で提供されているCI/CDサービスで、iOSアプリのビルドに特化している
「Xcode」を使ってワークフローの初期設定や編集、ビルド状態やログの確認など、全ての操作が可能
コード署名が自動で行われる(開発者が意識しなくて良い)
連携できるソースコード管理サービスとして、GitHub以外にGitLabやBitBucketにも対応
ほとんどがGUIのみを使って設定ができ、操作が簡単である
必須要件
主な要件として、Xcode 14.0.1以降(Get Startedのページでは 13.4.1以降 とありますが、下記の要件ページでは 14.0.1以降 となっている)を利用していること、Apple Developer Programを契約していることとなっています。ただし、現在アプリを公開しようとするとXcode14以上(2024年4月29日以降はXcode15以上)でのビルドが必須条件となっているため、利用するXcodeのバージョンについては問題ないと思います。
その他、詳細な要件については下記の公式ドキュメントをご覧ください。
この中で個人的に気をつけるべき点として「自動コード署名を有効にしていること」です。特に証明書とプロビジョニングプロファイルを受領し「Automatically manage signing」を無効にされている方は、Xcode Cloud利用時には証明書とプロビジョニングプロファイルは自動で解決される必要がありますので、この設定を有効にする必要があります。
なお、Xcode13以降では「Automatically manage signing」を有効にしておくことで、わざわざ配布用証明書を個別に管理する必要がなく、アプリやソフトウェアの配信のための署名が自動的にクラウドで行われます。詳しくは以下のドキュメントをご覧ください。
プランの違い
2024年3月25日現在、無料プランを含め4種類のプランがあります。
いずれも、1ヶ月に利用できる時間となっております。
無料:25コンピューティング時間
49.99ドル:100コンピューティング時間
99.99ドル:250コンピューティング時間
399.99ドル:1000コンピューティング時間
「コンピューティング時間」と書かれてありますが、実質「時間」と捉えて問題ないです。
まずは1ヶ月25時間分が無料で利用できるようになっておりますので、この範囲内で十分に試すことができます。
初期設定
Apple公式ドキュメントに画像付きで手順が書かれていますので、こちらを見ながら設定すればワークフローの初期設定が完了します。
基本的には上記のドキュメントを確認しつつ「Next」や「Complete」ボタンを押すだけで進めることができます。ただし、一部注意して設定すべき点がありましたので、紹介します。
①GitHubとの連携
Xcode CloudがビルドのためにGitHubなどのソースコード管理サービスと連携する部分です。GitHubを例に挙げると、各リポジトリに「GitHub Apps」としてXcode Cloudがインストールされますので、そのAppsとGitHubリポジトリとの連携許可に権限が必要となります。こちらは自身のGitHubアカウントで権限が無い場合は管理者に問い合わせをし、権限をいただくか許可してもらう必要があります。
②Xcode、macOSのバージョン指定
途中ワークフローの編集で、利用するXcodeとmacOSのバージョンを指定する部分があります。こちらは、デフォルトでは「Latest Release」が設定されておりますので、指定のバージョンを使いたい場合は変更しましょう。
カスタムビルドスクリプト
Xcode Cloudでもいくつかのイベントのタイミングで指定されたスクリプトを実行する仕組みがあります。このスクリプトは「カスタムビルドスクリプト」と呼ばれています。
GUIでは設定できず、Xcodeのプロジェクト直下に ci_scripts という名前のディレクトリを作成し、その中に決まったファイル名のスクリプトを配置することで、Xcode Cloudが自動的にそのスクリプトを実行してくれます。
主に以下の3つのタイミングでスクリプトが実行されます。
Post-clone(ci_post_clone.sh):Gitを使ってソースコードをCloneした直後
Pre-xcodebuild(ci_pre_xcodebuild.sh):依存解決をした直後、xcodebuildを使ってビルドする直前
Post-xcodebuild(ci_post_xcodebuild.sh):xcodebuildを使ってビルドした直後
なお、スクリプトファイルの拡張子が .sh となっている通り、Shell Scriptで記述する必要があります。
また、カスタムビルドスクリプトが実行される環境では、Homebrewがデフォルトでインストールされています。そのため、Homebrewでインストールできる全てのツールをこのXcode Cloud上で利用できます。
CocoaPodsやCarthageなどのパッケージ管理ツールの利用
この「カスタムビルドスクリプト」を利用することで、ビルド前にCocoaPodsやCarthageなどのパッケージ管理ツール経由で必要なライブラリをダウンロードすることも可能です。
CocoaPodsを利用したスクリプト例がApple公式のドキュメントに載っているため、そちらを参考にするとよいでしょう。
FlutterでXcode Cloudを利用する
ここからは、実際にFlutter製iOSアプリのXcode Cloudの設定時に苦労したこととその解消法を共有します。少しでも参考になれば幸いです。
Start Conditionの指定
実は、Flutterの公式ドキュメントにXcode Cloudを利用する際の設定例について記載されています。
FlutterでiOS/Androidの両OS向けアプリを開発する際、全ファイルのコミットをビルド開始のトリガーにしてしまうと、Androidにのみ影響するファイルのコミットでもビルドが始まってしまいます。そのため、上記のドキュメントに記載されている通り、「Start Condition」の「Branch Changes」でiOSアプリに関係するファイルのみを対象とするよう設定します。
Flutterパッケージの取得
iOSのアプリをビルドする前に、あらかじめFlutterで利用するパッケージをダウンロードする必要があります。そのためには、flutter ツールが必要となります。そのための設定もFlutterの公式ドキュメントに書かれています。
「カスタムビルドスクリプト」の「Post-clone」のタイミングで実行するスクリプト(ci_post_clone.sh)を用意します。その中で、最初にFlutterツールをダウンロードし、そのツールを使ってFlutter用パッケージを取得します。その後、 ios ディレクトリ配下で pod install を実行することで、一部のFlutterパッケージに必要なiOSネイティブのライブラリを取得することができます。
上記のFlutter公式ドキュメントでは、gitコマンドを使って最新版のFlutterツールを取得しています。しかし、プロダクトによっては利用するFlutterのバージョンを固定したい場合もあります。
そのため、Homebrew経由でインストールできる「FVM」というFlutterのバージョン管理ツールを使い、指定バージョンのFlutterツールをダウンロードし、Flutter用パッケージをインストールするようにしました。
#!/bin/sh
# Fail this script if any subcommand fails.
set -e
# Installed Flutter version
FLUTTER_VERSION=3.16.3
# Install Flutter using FVM
brew tap leoafarias/fvm
brew install fvm
fvm install "${FLUTTER_VERSION}"
fvm global "${FLUTTER_VERSION}"
fvm flutter --version
# Install Flutter artifacts for iOS
fvm flutter precache --ios
# Get Flutter packages
cd "${CI_PRIMARY_REPOSITORY_PATH}"
fvm flutter pub get
# Install CocoaPods using Homebrew.
HOMEBREW_NO_AUTO_UPDATE=1 # disable homebrew's automatic updates.
brew install cocoapods
# Install CocoaPods dependencies.
cd ios && pod install
CocoaPodsのエラー解消
Flutter用の設定が完了し、いざビルドを実施してみることにしました。しかし、自身の開発PCでは発生しなかった以下のエラーがXcode Cloud上で発生しました。
[!] CocoaPods could not find compatible versions for pod "Amplitude":
In snapshot (Podfile.lock):
Amplitude (= 8.16.1)
In Podfile:
amplitude_flutter (from `.symlinks/plugins/amplitude_flutter/ios`) was resolved to 0.0.1, which depends on
Amplitude (= 8.18.0)
iOSアプリ開発者であれば良く見るエラーですね...
これは、コミットされた Podfile.lock に記載されたライブラリのバージョンと、実際にインストールされたライブラリのバージョンが異なるために発生するエラーとなります。
上記の例は amplitude_flutter ですが、他にもFirebase系のパッケージでも発生しました。
ここで一度、pubspec.yaml に書かれている amplitude_flutter の依存関係の設定を確認してみます。
dependencies:
amplitude_flutter: ^3.16.1
バージョン指定として ^3.16.1 と書かれていますが、これは >=3.16.1 <4.0.0 と同じ意味となります。
つまり、環境によっては 3.16.1 ではなく 3.16.2 以上 4.0.0 未満 のAmplitudeのパッケージがインストールされる可能性があります。
私の環境では 3.16.1 がインストールされていましたが、この当時の amplitude_flutter の最新版は 3.16.2 となっていました。そのため、私の環境とXcode Cloudの環境では違うバージョンがインストールされていて、それぞれで利用されるiOSライブラリのバージョンが違うのではと思い、 amplitude_flutter の変更履歴を調べてみました。
以下が 3.16.2 リリース時のコミット差分です。
確かに、使用しているiOS用のライブラリバージョンが、 8.16.1 → 8.18.0 に変更されていました。これにより上記のエラーが起きていました。
そのため、Flutterパッケージのバージョン指定を以下のように修正しました。
dependencies:
amplitude_flutter: 3.16.1
バージョンの先頭にあった「^」を外し、確実に 3.16.1 がインストールされるよう変更しました。これで、無事このエラーも解消されました。
感想
Xcode Cloudは専門知識をそこまで有していなくても、誰でも設定できるように工夫されているなと感じました。また、コード署名に関しては本当に自動で行われるため、証明書とプロビジョニングプロファイルを受領して手動設定するしかなかったプロダクトにとってはとても便利でこれだけでも十分に利用したいと思いました。
その一方で、BitriseやCircleCIなどと比べると細かな設定やカスタマイズができず、基本的にiOSアプリをTestFlightへアップロードする目的のみに絞ったサービスになっています。このあたりは一長一短なところがあり、自身のプロダクトにあっているかを検証して検討すべきかと思いました。
最後に
NTTレゾナントテクノロジーではスマホアプリ開発エンジニアを募集しております。少しでも興味がありましたら、ぜひ以下の採用ページをご覧ください。