Making the Leap from Azure to Codemagic: Achieving a Remarkable 76.85% Improvement in iOS CI/CD

Bevan christian
IDN Engineering

--

Photo by Lala Azizli on Unsplash

Disclaimer

This article is purely informational and not sponsored by any particular company or product. The insights and experiences shared are based on genuine observations and intended to provide valuable insights into the discussed topic.

Background

Before we dive in, make sure you’re comfortable with Fastlane and have some CI/CD experience under your belt.

In this article, we’re spilling the beans on why we switched from Azure to Codemagic and showing you exactly how to supercharge your CI/CD game. We’re covering the entire spectrum — from compiling and creating release notes to testing and deployment. Let’s rewind a bit to our iOS developers’ struggles. Our CI process (think builds, tests, and coverage) was taking a chunky 40–50 minutes. Even after briefly dipping to 30 minutes, it shot back up due to new features and libraries, putting the brakes on our development speed. And guess what? CD (build and deploy) wasn’t any better, sapping 40 minutes.

But hold on, Codemagic to the rescue! The magic ingredient? M1 architecture. It turbocharged our build time, shrinking the total ordeal to just 25 minutes, down from the 80–90-minute marathon.

Build, Test and Deploy in Codemagic 25 Min
Build, Test and Deploy in Azure 1 Hour 49 Min

Witness the Marvel: A Whopping 76.85% Boost in Our CI/CD Process! 🚀

Our Script

Now, here’s a sneak peek into the goods — a snippet of YAML code for your iOS exploits, complete with testing, building, and the grand finale, deploying to Firebase Distribution.

 definitions:
- &installDependency
name: Install Dependency
script: |
gem install bundler
bundle install


- &runTest
name: Run Unit Test
script: |
bundle exec fastlane pr_tests_codemagic

- &podInstall
name: Install CocoaPods dependencies
script: |
pod install


build-to-firebase-distribution:
name: publish-to-firebase-distribution
max_build_duration: 35
instance_type: mac_mini_m1
cache:
cache_paths:
- $HOME/Library/Caches/CocoaPods
environment:
groups:
- iosVariable
- firebase_credentials
ios_signing:
provisioning_profiles:
- AdHocMobileProvisionCodemagic
certificates:
- distributionCertificate
vars:
BUNDLE_ID: "com.app.App"
XCODE_WORKSPACE: "app.xcworkspace"
XCODE_SCHEME: "release"
TARGET_NAME: "your app name"
APP_STORE_APPLE_ID: xxxx
xcode: 14.2
cocoapods: default
scripts:
- *installDependency
- *podInstall
- *runTest
- name: Generate release notes
script: |
cd $CM_BUILD_DIR
now=$(date)
currentDate="Current build on $now, This log may not be a complete log, contact developer for the complete build log"
echo $currentDate >> ./release_notes.txt
echo "XCODE_SCHEME: $XCODE_SCHEME" >> ./release_notes.txt
git log --graph --oneline --pretty=format:"%s" --since="yesterday" >> ./release_notes.txt
cat ./release_notes.txt
- name: Set up provisioning profiles settings on Xcode project
script: xcode-project use-profiles
- name: Get the latest build number and Increment build number
script: |
LATEST_BUILD_VERSION=$(firebase-app-distribution get-latest-build-version -p xxx -a 1:xxx:ios:xxx)
cd $CM_BUILD_DIR # avgtool must run in the folder where xcodeproj file is located
agvtool new-version -all $(($LATEST_BUILD_VERSION + 1))
- name: Build ipa for distribution
script: |
xcode-project build-ipa -v\
--workspace "$CM_BUILD_DIR/$XCODE_WORKSPACE" \
--clean \
--scheme "$XCODE_SCHEME" \
--archive-flags="-destination 'generic/platform=iOS'"
artifacts:
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
- $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.app
- $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.dSYM
publishing:
firebase:
firebase_service_account: $FIREBASE_SERVICE_ACCOUNT
ios:
# Add your iOS app id retrieved from Firebase console
app_id: "1:xxx:ios:xxxx"
# Add one or more groups that you wish to distribute your iOS application to.
# You can create groups in the Firebase console
groups:
- ios-tester

Fasfile

  desc "Run Pull Request tests Codemagic"
lane :pr_tests_codemagic do
scan(
workspace: "app.xcworkspace",
scheme: "UnitTesting",
device: "iPhone SE (3rd generation)",
reset_simulator: true,
configuration: "debug",
test_without_building: false,
skip_build: false,
fail_build: false,
number_of_retries: 3,
concurrent_workers: 4
)
if $?.exitstatus != 0
UI.user_error!("Tests failed. Exiting with status 1.")
exit(1)
else
UI.success("Tests passed.")
end
end

This YAML configuration is for setting up a CI/CD pipeline using CodeMagic for an iOS application. It defines a scheduled build named scheduled-build-to-firebase-distribution-RC. The build process includes several steps, such as installing dependencies, running tests, generating release notes, setting up provisioning profiles, incrementing build numbers, setting up code signing, and building the IPA for distribution.

Build Environment

The build is performed on a mac_mini_m1 instance.

Chace

cache:
cache_paths:
- $HOME/Library/Caches/CocoaPods

CocoaPods caching is used to speed up dependency installation. it will automaticly chace when you execute pod install

Environment Variables

environment:
groups:
- iosVariable
- firebase_credentials

Various environment variables are defined for the build process, including BUNDLE_ID, XCODE_WORKSPACE, XCODE_SCHEME, TARGET_NAME, and APP_STORE_APPLE_ID. all this environment variables you can set in your app setting in codemagic web

iOS code signing

Code signing is a crucial security mechanism in the iOS app development process. It involves adding a digital signature to your app’s executable and other relevant files to verify its authenticity and ensure that it hasn’t been tampered with. Code signing is essential for app distribution and installation on iOS devices.

Certificate: Developers generate a request and receive a digital certificate from Apple. This certificate confirms their identity and links their public key to their developer account.

Provisioning Profile: A setup file that defines app details, device access, and distribution method. Different types, like development and distribution profiles, are tied to the certificate.

ios_signing:
provisioning_profiles:
- AdHocMobileProvisionCodemagic
certificates:
- distributionCertificate

because i will publish this app to firebase distribution its mean it will need AdHoc Profiles, if i publish to appstore or testflight you can use App Store profiles.

to manage this certificate you can go to Team setting -> Integration

  1. Add your App Store Connect API Key

after you success add App Store Connect API Key you can proceed to Team setting -> Code Signing indentities

  1. Upload your p12 file in my case i will name it distributionCertificate

2. After that, fetch your profile — in my case, I will name it AdHocMobileProvisionCodemagic. When fetching profiles, you’ll need an API key from App Store Connect.

Scripts

Several scripts are executed during the build process:

Install Dependency

Installs the Bundler gem and installs Ruby dependencies using bundle install.

Install CocoaPods dependencies

Installs CocoaPods dependencies using pod install.

Run Unit Test

Executes unit tests using Fastlane and the specified lane pr_tests_codemagic.

Generate Release Notes

Creates release notes by appending build information, Xcode scheme, and recent commit messages to a text file.

- name: Generate release notes
script: |
cd $CM_BUILD_DIR
now=$(date)
currentDate="Current build on $now, This log may not be a complete log, contact developer for the complete build log"
echo $currentDate >> ./release_notes.txt
echo "XCODE_SCHEME: $XCODE_SCHEME" >> ./release_notes.txt
git log --graph --oneline --pretty=format:"%s" --since="yesterday" >> ./release_notes.txt
  1. cd $CM_BUILD_DIR: This changes the current directory to the $CM_BUILD_DIR, which is a predefined environment variable by codemagic representing the directory where the build is taking place.
  2. now=$(date): This captures the current date and time using the date command and assigns it to the variable now.
  3. currentDate="Current build on $now, This log may not be a complete log, contact developer for the complete build log": This creates a message indicating the current build timestamp and a note that the log might not be complete, encouraging others to contact the developer for a full log. The variable $now is substituted with the captured date and time.
  4. echo $currentDate >> ./release_notes.txt: This appends the currentDate message to a file named release_notes.txt.
  5. echo "XCODE_SCHEME: $XCODE_SCHEME" >> ./release_notes.txt: This appends the value of the XCODE_SCHEME environment variable to the release_notes.txt file. The XCODE_SCHEME is likely a scheme name used in the Xcode project.
  6. git log --graph --oneline --pretty=format:"%s" --since="yesterday" >> ./release_notes.txt: This command retrieves the commit history using git log. Commit messages from the Git history, starting from yesterday, are added to the notes file.

Set up provisioning profiles

settings on Xcode project: Configures provisioning profiles on the Xcode project.

Get the latest build number and Increment build number

script: |
LATEST_BUILD_VERSION=$(firebase-app-distribution get-latest-build-version -p xxx -a 1:xxx:ios:xxx)
cd $CM_BUILD_DIR # avgtool must run in the folder where xcodeproj file is located
agvtool new-version -all $(($LATEST_BUILD_VERSION + 1))

here’s a explanation:

  1. LATEST_BUILD_VERSION=$(firebase-app-distribution get-latest-build-version -p xxx -a 1:xxx:ios:xxx): Fetches the latest build version from Firebase.
  2. cd $CM_BUILD_DIR: Navigates to the build directory where the Xcode project file is located.
  3. agvtool new-version -all $(($LATEST_BUILD_VERSION + 1)): Increases the build version by 1 using agvtool, ensuring a unique identifier for the new build.

Set up code signing settings on Xcode project:

Configures code signing settings on the Xcode project.

Build IPA for distribution:

xcode-project build-ipa -v\
--workspace "$CM_BUILD_DIR/$XCODE_WORKSPACE" \
--clean \
--scheme "$XCODE_SCHEME" \
--archive-flags="-destination 'generic/platform=iOS'"

Builds the IPA file for distribution, specifying the workspace, scheme, and archive flags.

This script is provided by codemagic you can check the documentation in here https://github.com/codemagic-ci-cd/cli-tools/blob/master/docs/xcode-project/build-ipa.md.

Publishing

publishing:
firebase:
firebase_service_account: $FIREBASE_SERVICE_ACCOUNT
ios:
# Add your iOS app id retrieved from Firebase console
app_id: "1:xxx:ios:xxxx"
# Add one or more groups that you wish to distribute your iOS application to.
# You can create groups in the Firebase console
groups:
- ios-tester

This script is for publishing to Firebase Distribution, where `$FIREBASE_SERVICE_ACCOUNT` is an environment variable that I’ve set in the `firebase_credentials` environment variable group. The app ID can be found in the Firebase console under Project settings -> Your Apps Section. For the complete documentation, you can refer to this link: https://docs.codemagic.io/yaml-publishing/firebase-app-distribution/.

Conclusion

To sum it up, Codemagic’s M1-powered prowess is our secret sauce for a speedier CI/CD journey. This article unwraps the tech wizardry that slashed build times and ramped up our development mojo. We’re keeping things simple, showing how our YAML wizardry choreographs tests, builds, and deployments like a symphony of efficiency.

Next Article

The differences between Codemagic and Azure will be discussed in the upcoming article.

--

--

iOS Developer @IDN Media Stay up to date with the latest iOS development insights from Bevan Christian. Follow them for expert tips about things iOS dev.