iOS — How to handle Semantic Auto-Versioning on CI

Automate deployment with auto-versioning and save 25% of your time.
Jun 22 2022 · 4 min read

Background

In the previous article, we learned how to auto-deploy the latest build on the commit of the master branch.
However, auto-deployment requires auto-increment of version as AppStore requires a new version code and version name for each build.

If you haven’t read the previous article, I highly recommend reviewing it once.

Today, we will learn how we can manage auto-versioning.

Let’s get started.

Strategy

We will follow semantic versioning.

Basically, the version name will be created with a combination of major, minor, and patch numbers.

For example, for app version 1.4.0 —

1 - major number
4 - minor number
0 - patch number

If we want to auto-deploy apps on every commit, we can keep major and minor versions static and make the patch number dynamic. For patch number, we can use the pipeline ID which changes with every commit, thus every build with have a unique version name and number.

To conclude —

  1. We will update major and minor numbers manually as it will depend on our requirements. i.e whenever there’s a major new feature, we will update the major number and for minor changes, we will update the minor number.
  2. We will update the patch number automatically by using the pipeline ID of CI.

We will need to make 3 changes in our project to support auto-versioning.

Let’s get started!

Add VERSION file

To store version numbers we will add one file in our project which will only have the build name.

Consider a scenario where we want to push the app with the 1.4.0 version. Here, we should add the file with the name VERSION .

1.4.0

Note: Add this file to the root directory of your project so that it will be easy to access for further use.

Update deployment script

Now we have to add a script for versioning in the deployment script of CI in the .gitlab-ci.yml file.

As mentioned before, we will use pipeline ID as our patch number which will be incremented with every commit.

This pipeline ID is available as an environment variable CI_PIPELINE_IID by default in GitLab CI.

Let’s first see what our script will look like after adding the auto-versioning changes to the existing deployment script.

deploy_testflight:
  stage: deploy
  script:
    - chmod +x install_certificate.sh && ./install_certificate.sh
    - chmod +x install_profile.sh && ./install_profile.sh
    - pod install --repo-update
    - file='ProjectName/VERSION'
    - fileData=`cat $file`
    - IFS='.'
    - read -a versionValue <<< "$fileData"
    - versionNumber=$(expr `expr ${versionValue[0]} \* 1000000` + `expr ${versionValue[1]} \* 10000` + ${CI_PIPELINE_IID})
    - IFS=''
    - buildName="${versionValue[0]}.${versionValue[1]}.$CI_PIPELINE_IID"
    - echo "Uploading build $buildName"
    - ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/ProjectName/${CI_COMMIT_SHA}/${CI_JOB_ID}.xcarchive"
    - EXPORT_PATH="$HOME/Library/Developer/Xcode/Archives/ProjectName/${CI_COMMIT_SHA}/${CI_JOB_ID}/"
    - xcodebuild app_version_code=${versionNumber} app_version_name=${buildName} -workspace ProjectName.xcworkspace -scheme "ProjectName" clean archive -sdk iphoneos -archivePath $ARCHIVE_PATH PROVISIONING_PROFILE_SPECIFIER="${DISTRIBUTION_PROVISION_UUID}" CODE_SIGN_STYLE=Manual CODE_SIGN_IDENTITY="${CODE_SIGN_IDENTITY}" | xcpretty --c
    - xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportOptionsPlist ExportOptionsAppStore.plist -exportPath $EXPORT_PATH PROVISIONING_PROFILE_SPECIFIER="${DISTRIBUTION_PROVISION_UUID}" CODE_SIGN_STYLE=Manual CODE_SIGN_IDENTITY="${CODE_SIGN_IDENTITY}"
    - echo "Collecting artifacts.."
    - cp -R "${ARCHIVE_PATH}/dSYMs" .
    - IPA="${EXPORT_PATH}ProjectName.ipa"
    - echo $IPA
    - echo "Uploading app to iTC..."
    - xcrun altool --upload-app -t ios -f $IPA -u $ITC_USER_NAME -p $ITC_USER_PASSWORD

  artifacts:
    paths:
      - dSYMs
  only:
    - main
  tags:
    - runner-tag-name

Now, Let’s understand this whole script in detail.

We start by reading the VERSION file we created earlier and storing the value in a fileData variable. Here replace ProjectName/VERSION with your actual path of the file.

- file='ProjectName/VERSION'
- fileData=`cat $file`

Now let's separate major, minor, and patch numbers from the full string.

- IFS='.'
- read -a versionValue <<< "$fileData"

Now, now we will create versionNumber and buildName by using the major and minor numbers from VERSION file but replacing the patch number with CI’s pipeline number.

- versionNumber=$(expr `expr ${versionValue[0]} \* 1000000` + `expr ${versionValue[1]} \* 10000` + ${CI_PIPELINE_IID})
- IFS=''
- buildName="${versionValue[0]}.${versionValue[1]}.$CI_PIPELINE_IID"

That’s it for the version generation script!

Now, let’s pass both versionNumber and buildName to xcodebuild command, so they will be replaced in the archive that gets generated as we will see in the next step.

- xcodebuild app_version_code=${versionNumber} app_version_name=${buildName} -workspace ProjectName.xcworkspace -scheme "ProjectName" clean archive -sdk iphoneos -archivePath $ARCHIVE_PATH  PROVISIONING_PROFILE_SPECIFIER="${DISTRIBUTION_PROVISION_UUID}" CODE_SIGN_STYLE=Manual CODE_SIGN_IDENTITY="${CODE_SIGN_IDENTITY}" | xcpretty --c

You may be wondering about app_version_code and app_version_name variables, they are user-defined variables.

Let’s explore them!

Update Project Files

To pass the version code and name from the Xcode command-line tool while building the project, we will have to add app_version_code and app_version_name variables in Xcode at two places:

  1. In the Info.plist :
    - CFBundleVersion ${app_version_code}
    - CFBundleShortVersionString ${app_version_name}
  2. Project target => Build Setting => + => Add User Defined Setting
    - app_version_code = 1040000
    - app_version_name = 1.4.0

Here, we are asking Xcode to use user-defined variables for version name and number and then we change them dynamically during the build on CI.

Tadda… That’s it !!!

Conclusion

Now, if you have to push any new feature or significant update and want to bump the version with the major or minor number then you just have to update the version number in the VERSION file and push it to GitLab. Along with merging it to master, it’ll automatically update the version in iTunes Connect.

Once done, you will never have to worry about pushing build on CI. Commit your changes and you will have a build ready on Testflight within a few minutes.

Doesn’t that sound amazing?

Obviously yes! During my initial journey as an iOS developer, I wasted so many hours a week just building, testing, and deploying iOS builds. Getting rid of all those responsibilities and automating it gives me a big relief and saves around 25% of my time!


amisha-i image
Amisha Italiya
iOS developer @canopas | Sharing knowledge of iOS development


amisha-i image
Amisha Italiya
iOS developer @canopas | Sharing knowledge of iOS development


Talk to an expert
get intouch
Our team is happy to answer your questions. Fill out the form and we’ll get back to you as soon as possible
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.