Building a Workflow to Upload a Flutter iOS App to Firebase App Distribution Using CircleCI (Non-AutoSigning)
Introduction
Recently, I built a workflow to upload a Flutter iOS app to Firebase App Distribution using GitHub Actions.
However, running it on macos
was quickly draining the free tier minutes. While I plan to switch to a self-hosted runner to avoid this, for the time being, I decided to run it on CircleCI as a temporary workaround.
The Workflow I Built
Here is the final workflow I ended up with. Since this is a temporary workflow that will be discarded once it’s no longer needed, the processing is hardcoded and not organized for long-term use. Please keep that in mind.
version: 2.1
orbs:
flutter: circleci/flutter@2
firebase-app-distribution: nnsnodnb/firebase-app-distribution@0
executors:
macos-executor:
macos:
xcode: 15.4.0
resource_class: macos.m1.medium.gen1
jobs:
ios_deploy_app_distribution:
executor: macos-executor
parameters:
firebase_app_id:
type: string
app_distribution_service_account:
type: string
ios_bundle_id:
type: string
ios_provisioning_profile_name:
type: string
ios_adhoc_provision_profile_base64:
type: string
runner_keychain_password:
type: string
steps:
- checkout
- flutter/install_sdk_and_pub:
version: 3.19.5
- run:
name: Update cocoapods
command: gem update cocoapods
- flutter/install_ios_pod
- run:
name: Install Apple certificate and provisioning profile
environment:
IOS_ADHOC_PROVISION_PROFILE_BASE64: $IOS_ADHOC_PROVISION_PROFILE_BASE64
RUNNER_KEYCHAIN_PASSWORD: $RUNNER_KEYCHAIN_PASSWORD
command: |
CERTIFICATE_PATH=$CIRCLE_WORKING_DIRECTORY/build_certificate.p12
PP_PATH=$CIRCLE_WORKING_DIRECTORY/build_pp.mobileprovision
KEYCHAIN_PATH=$CIRCLE_WORKING_DIRECTORY/app-signing.keychain-db
mkdir -p $(dirname "$CERTIFICATE_PATH")
mkdir -p $(dirname "$PP_PATH")
echo -n "$IOS_CERTIFICATE_P12_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n << parameters.ios_adhoc_provision_profile_base64 >> | base64 --decode -o $PP_PATH
security create-keychain -p << parameters.runner_keychain_password >> $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p << parameters.runner_keychain_password >> $KEYCHAIN_PATH
security import $CERTIFICATE_PATH -P "$IOS_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k << parameters.runner_keychain_password >> $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- run:
name: Generate ExportOptions.plist
command: |
export EXPORT_METHOD=release-testing
export EXPORT_DESTINATION=export
export BUNDLE_ID=<< parameters.ios_bundle_id >>
export PROVISIONING_PROFILE=<< parameters.ios_provisioning_profile_name >>
envsubst < ios/Runner/ExportOptions-Template.plist > ios/Runner/ExportOptions.plist
- run:
name: Install Rosetta 2
command: sudo softwareupdate --install-rosetta --agree-to-license
- run:
name: Build ipa
command: |
flutter build ipa \
--release \
--flavor << parameters.environment >> \
--export-options-plist=ios/Runner/ExportOptions.plist
- run:
name: Set up Firebase App Distribution Service Credentials
command: echo << parameters.app_distribution_service_account >> > /tmp/firebase-app-distribution-service-credentials.json
- firebase-app-distribution/deploy:
app: << parameters.firebase_app_id >>
binary_path: build/ios/ipa/app-<< parameters.environment >>-release.ipa
groups: ios_testers
release_note: << pipeline.git.revision >>
service_credentials_file: /tmp/firebase-app-distribution-service-credentials.json
workflows:
ios_deploy_app_distribution:
jobs:
- ios_deploy_app_distribution:
firebase_app_id: $FIREBASE_IOS_APP_ID
app_distribution_service_account: $APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON
ios_bundle_id: $IOS_BUNDLE_ID
ios_provisioning_profile_name: $IOS_PROVISIONING_PROFILE_NAME
ios_adhoc_provision_profile_base64: $IOS_ADHOC_PROVISION_PROFILE_BASE64
runner_keychain_password: $RUNNER_KEYCHAIN_PASSWORD
filters:
branches:
only: develop
Since the steps aren’t significantly different from what I built with GitHub Actions, I’ll skip the detailed explanations.
Also, it appears that CircleCI provides an Orb for Flutter, making workflow construction more straightforward. There are several jobs and commands you can use with this Orb, so please check it out if necessary.
Conclusion
I couldn’t simply copy-paste the workflow from GitHub Actions, as CircleCI has its own setup and syntax that I needed to account for. So, even though this is a throwaway workflow, it still took a bit of time.
I hope to quickly set up a self-hosted runner and free myself from the anxiety of macos build times.