Close modal

Blog Post

Auto-increment semantic build numbers with fastlane.

Development
Fri 20 April 2018
0 Comments


iOS app build numbers.

The problem

You may be familiar with the CFBundleVersion settings of your Xcode target.

This can be anything you like really.

While CFBundleShortVersionString is usually X.Y.Z, there are many options for the CFBundleVersion, alot of people use auto incrementing numbers like 1 or 100, or 32504. However certain issues may arise from using just integers:

  • Do you reset the build number for every version? or does it incrementing.
  • If you use Firebase remote config you may be aware of the behaviour that is counter-intuitive where the version actually means CFBundleVersion and not CFBundleShortVersionString (meaning that you'll need to refer to that number and it may not be possible to create a predicate to target desired versions.

The solution

So what is proposed here is that the version or build string, is simply the version string, "X.Y.Z", followed by the build number - "X.Y.Z.B", so that at any given time, just by referring to the number you can discern the release version as well as the build number for test purposes (remember this number is not usually displayed anywhere except under the hood). At the very least, this gets around firebases issue of using the build number and you can match regex such as 3\.[\d]?\.[\d]? to refer to version 3.x.y.

The implementation

Like many people, we will use fastlane here and give the a practical approach to have this taken care of for you.

The latest_testflight_build_number utility will give you the latest build number on fastlane and is a good place to start, however simply going +1 here is not enough, bevause we use a "X.Y.Z.B" instead of just "B".

What we desire is to pull the latest build version down, split it into components, increment the last one and push it back. Here's some working code that I have used with success:

# Increment build number - we use syntax major.minor.patch.build
version = get_version_number(target: "<TARGET>")
last_build_number = latest_testflight_build_number(
  app_identifier: CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier),
  version: version
).to_s
if not last_build_number.include? "."
  # Does not exist yet - Append '.0' to the version number
  last_build_number = version << ".0"
end
build_components = last_build_number.split('.')
last_component = Integer(build_components[-1]) + 1 
build_components[-1] = last_component.to_s
build_version = build_components.join(".")

increment_build_number({
  build_number: build_version
})

Conclusion

As you can see, it's only a little more complicated, however it's still handled automatically. Using this same mechanism you could potentially increment the version number automatically if the app has already been released, however for most people that's probably an infrequent occurance for most people (though it is annoying when the build gets rejected because the release stream is closed after release).