📮

FlutterもXcode Cloudで楽ちんCI/CD

2024/03/31に公開

はじめに

昔リリースしたアプリをアップデートしたいんだけど、iOSの証明書周りどうなってたっけ?ということありますよね。。自分はiOSの証明書周りがあまり自信がなく、毎回調べながらリリースしてるのですが、今回色々調べていたらXcode Cloudなる機能を利用することで、自動署名でビルドしてTestFlightにアップロードできるようなので、備忘録的に記事にしてみます。

今回やりたいこと

FlutterのiOSアプリをXcode Cloudを利用してCI/CDする。

Xcode Cloudとは?

Appleによって提供されているCI/CDサービスです。
Xcode 13.4.1以降とAppleDeveloperProgramのメンバーシップが必要なようです。
料金は25(コンピューティング)h/月までは無料のようです。
https://developer.apple.com/jp/xcode-cloud/get-started/

Xcode Cloudの設定

序盤の設定はぽちぽち進めてしまい詳しく覚えていないのですが、XcodeでAutomatically manage signingをオンにしてProduct Archiveをしようとしたところ、Xcode Cloudを利用するためにGitHubと連携しませんか?的なダイアログが出てきて、指示に従ってGitHub連携を済ませました。
そして、今回リリースしたいアプリのリポジトリを指定するとXcode Cloudが走ったようです。AppStoreConnectのアプリのページのXcode Cloudのタブからワークフローのログが見れます。

現状はワークフローが失敗しているので、エラーを解消しながらビルドできるところまで持っていきます。

ちなみにWorkflowの編集はXcodeのIntegrate -> Manage Workflows.. -> 対象のアプリで開けます。

Post-clone scriptの用意

ここからはFlutterの公式ドキュメントに従って進めてみます。

まず、Post-clone scriptを用意します。
公式ドキュメントのものをそのままコピペしてきました。

ci_post_clone.sh
#!/bin/sh

# Fail this script if any subcommand fails.
set -e

# The default execution directory of this script is the ci_scripts directory.
cd $CI_PRIMARY_REPOSITORY_PATH # change working directory to the root of your cloned repo.

# Install Flutter using git.
git clone https://github.com/flutter/flutter.git --depth 1 -b stable $HOME/flutter
export PATH="$PATH:$HOME/flutter/bin"

# Install Flutter artifacts for iOS (--ios), or macOS (--macos) platforms.
flutter precache --ios

# Install Flutter dependencies.
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 # run `pod install` in the `ios` directory.

exit 0

中身を見てみると、Flutterのインストール、pub-get、cocoapodsのインストールなどをしているみたいです。fvmで指定しているversionのFlutterをインストールすべきな気がしますが、ここは一旦スルーします。(今気になるという方はこちら)

ci_post_clone.shをコミットしてPushするとWorkflowが走りBuildArchiveまで成功したようです。

TestFlightへのアップロードの設定

しかしまだTestFlightには上がっておらず、TestFlightへのアップロードの設定が必要のようです。

Workflowの設定を変更したいので、Integrate -> Manage Workflows.. -> 対象のアプリ -> Archive - iOSのDeployment PreparationをNoneからTestFlight and AppStoreに変更しました。

この設定はgit管理されていないようなので、おそらくXcode Cloudにすぐ反映されて、Webのコンソールから再実行すれば良さそうな気がしますが、ビルド番号を変えてなかったことを思い出したのでpubspec.yamlからversionをインクリメントして、Commit&Push!

無事にビルドされて、TestFlightに反映されました。

ここで、コンプライアンスがありませんと指摘されてます。(日本語おかしいw)
下記の記事に従ってinfo.plistにApp Uses Non-Exempt Encryptionを追加します。
https://dev.classmethod.jp/articles/app-uses-non-exempt-encryption/

再度pubspec.yamlからversionをインクリメントして、Commit&Push!
TestFlightに再度反映されて、実際に内部テストまでできました。

Flutterのビルド設定

しかし、アプリをインストールして立ち上げてみるとスプラッシュから動きません、、

  • XcodeでArchiveする前にFlutter buildをしてないから?
  • dart-defineの設定とかをしてないから?

ちょっとどちらが原因かわかりませんが、--dart-defineを指定したいので、下記をshellscriptの最終行の手前に追加してCommit&Push!
気づいたのですが、pubspec.yamlからversionを上げなくても自動でビルドバージョンがincrementされてました。これはXcode Cloudの仕様?🤔

ci_post_clone.sh
+ flutter build ios --config-only --dart-define=flavor=prod

TestFlightに再度反映されて、インストールされたアプリはスプラッシュでフリーズせずちゃんと動くようになりました。flavorも反映されており、Firebaseのprod環境に接続していました。

秘匿情報管理

次に秘匿情報の管理についてです。
今回Admobの広告Unit-idを秘匿管理します。

まずAppStoreConnectの該当アプリのページからXcode Cloud -> ワークフローの管理 -> 該当のワークフロー -> 環境 -> 環境変数から編集をクリックしダイアログを開きます。

こんな感じでkeyとvalueを登録できて、secretにチェックを入れると見えなくなります。
この環境変数はshellscript内で${key}でアクセスできるので、
先ほどのflutter buildコマンドを下記のように変更します。

ci_post_clone.sh
- flutter build ios --config-only --dart-define=flavor=prod
+ flutter build ios --config-only --dart-define=flavor=prod --dart-define=admobIdIos="${admobIdIos}"

TestFlightからインストールすると確かに広告ユニットIdが取得できて広告が表示できました。

これで、アプリをビルドしてTestFlightに挙げるところまで完了しました。
また本番リリースの申請にもビルドファイルを選択することができました。

ここまででやりたいことはほぼ終わったのですが、ここからは細かい設定をしていきます。

Workflowの微調整

パッと思いつくのは

  • FlutterのVersionをFVMに合わせたい。
  • Workflowが完了したときに通知を飛ばしたい。
  • 毎回走らないようにreleaseブランチがpushされた場合のみWorkflowを走らせたい。

FlutterのVersionを指定

今回FVMで指定しているFlutterのVersionを指定してインストールしたいので、ci_post_clone.shを下記のように書き換える。

ci_post_clone.sh
- # Install Flutter using git.
- git clone https://github.com/flutter/flutter.git --depth 1 -b stable $HOME/flutter
+ # Extract the Flutter version from .fvmrc file
+ FLUTTER_VERSION=$(cat .fvmrc | grep "flutter" | cut -d '"' -f 4)
+ 
+ # Clone the Flutter repository with the specified version
+ git clone https://github.com/flutter/flutter.git --depth 1 -b $FLUTTER_VERSION $HOME/flutter
+ export PATH="$PATH:$HOME/flutter/bin"
+ 
+ # Print the Flutter version
+ flutter --version

Workflow完了時の通知

XcodeのIntegrate -> Manage Workflows.. -> 対象のアプリ -> Post-Actionsの+ボタンからNotifyを追加。下の画像のようになるので、今回はSlackを追加することにしました。Slackの欄の+ボタンをクリックするとSlackとの連携のためにWebBrowserが開くので、指示に従って連携したいWorkspaceと連携。Xcodeに戻って通知を流すチャンネルを指定する。

いや、この設定一番最初にやるべきじゃない?w

releaseブランチでWorkflowを走らせる。

XcodeのIntegrate -> Manage Workflows.. -> 対象のアプリから Start Conditionsを開きブランチを設定します。ワイルドカードを利用できるので、release/*のようにしておくことでrelease/から始まるブランチを指定できます。

また、Custom Conditionsから特定のファイルの変更があった場合のみトリガーさせるという設定もできるようですが、個人開発の範囲で気にするほどのことじゃないかなと思ったので、今回は指定してません。リリースブランチ切っている時点で配布したいことがほとんどだろうし🙆‍♂️

おわりに

iOSの配布やリリース周りは証明書がかなり厄介なイメージがありましたが、今回Xcode Cloudを利用することで、証明書周りにつまづかずにTestFlightへのアップロードができました。
リリース頻度にもよりますが、軽く使うくらいであれば無料枠で賄えそうなのもオススメです!

2024年は技術発信も頑張ろうと思っているので、記事が参考になった方は記事とGitHubのいいね(スター)とフォローをしていただけると励みになります!
最後まで読んでいただきありがとうございました✨

https://github.com/miyasic

https://github.com/miyasic/PokeScouter

Discussion