Bump app version and build number using Fastlane
Introduction
We’re nearing the end of our “Fastlane from zero to hero”, and in this post we’re going to automate just a little bit more of the housekeeping work it takes to release an app to the app store. Increasing the version number and build number is something that we all have to do, but it’s also something that we all forget to do. So let’s automate it!
Pre-requisites
- You should have a working Fastlane setup. If you don’t, check out the previous posts in this series.
- I’m going to go off of the assumption that you’ve got an API key, as a JSON file in your project. If this is a subject you’re not familiar with, let me know, and I can write a post about it!
Build numbers
The first thing that we’re going to do is compare the current build number with the one that we have in our project. This is important because we don’t want to bump the build number if it’s already the same as the one in the project. We’re going to be getting the latest build numbers from 3 different sources:
- The latest build number from TestFlight
- The latest build number from the App Store
- The latest build number from the local project
Getting the latest build number from TestFlight
To get the latest build number from TestFlight, we’re going to use the latest_testflight_build_number
action. This action will return the latest build number for the app in TestFlight. It’s a built-in action from Fastlane, so we don’t need to worry about installing any additional gems, or writing the complex logic ourselves.
desc 'Compare the local build number to the latest test flight on ASC'
lane :compare_build_numbers do
test_flight_version = latest_testflight_build_number(
app_identifier: 'com.example.app',
api_key_path: 'iTunesConnectKey.json'
).to_i
end
- This is assuming that you have a JSON file with your API key in the root of your project. If you don’t, you can use the
api_key
parameter to pass in the key directly.
Getting the latest build number from the App Store
To get the version from App Store, we’re going to use the same approach - using the built-in Fastlane actions.
desc 'Compare the local build number to the latest test flight on ASC'
lane :compare_build_numbers do
test_flight_version = latest_testflight_build_number(
app_identifier: 'com.example.app',
api_key_path: 'iTunesConnectKey.json'
).to_i
live_app_version = app_store_build_number(
app_identifier: 'com.example.app',
api_key_path: 'iTunesConnectKey.json'
).to_i
end
Getting the latest build number from the local project
Can you guess what we’re going to be using? That’s right - a built-in Fastlane action!
desc 'Compare the local build number to the latest test flight on ASC'
lane :compare_build_numbers do
test_flight_version = latest_testflight_build_number(
app_identifier: 'com.example.app',
api_key_path: 'iTunesConnectKey.json'
).to_i
live_app_version = app_store_build_number(
app_identifier: 'com.example.app',
api_key_path: 'iTunesConnectKey.json'
).to_i
local_build_number = get_build_number.to_i
end
Comparing and bumping
Now that we’ve got all 3, we only really need 2; the local version, and which ever one of the other build numbers is highest. So we can write a simple lane to compare the two, and bump the local version if it’s lower than either of the other two.
lane :bump_build_number_if_needed do |options|
local_build_number = options[:local_build_number]
remote_build_number = options[:remote_build_number]
if remote_build_number.to_i > local_build_number.to_i
new_build_number = remote_build_number.to_i + 1
increment_build_number(build_number: new_build_number)
elsif remote_build_number.to_i == local_build_number.to_i
increment_build_number
end
end
This might look a bit scary, but let’s break it down:
- We take in the local and remote build numbers as parameters.
- We compare the two, and if the remote build number is greater than the local build number, we increment the remote build number by 1, and set that as our build number.
- If the remote build number is equal to the local build number, we just increment the local build number by 1.
- If the remote build number is less than the local build number, we don’t do anything ( So we don’t bump when we don’t need to!).
Putting it all together
Now that we’ve got all the pieces, we can put them together in a lane.
desc 'Compare the local build number to the latest test flight on ASC'
lane :compare_build_numbers do
test_flight_version = latest_testflight_build_number(
app_identifier: 'com.example.app',
api_key_path: 'iTunesConnectKey.json'
).to_i
live_app_version = app_store_build_number(
app_identifier: 'com.example.app',
api_key_path: 'iTunesConnectKey.json'
).to_i
local_build_number = get_build_number.to_i
bump_build_number_if_needed(
local_build_number: local_build_number,
remote_build_number: test_flight_version > live_app_version ? test_flight_version : live_app_version
)
end
lane :bump_build_number_if_needed do |options|
local_build_number = options[:local_build_number]
remote_build_number = options[:remote_build_number]
if remote_build_number.to_i > local_build_number.to_i
new_build_number = remote_build_number.to_i + 1
increment_build_number(build_number: new_build_number)
elsif remote_build_number.to_i == local_build_number.to_i
increment_build_number
end
end
And that’s it! Now we can run the compare_build_numbers
lane, and it will automatically bump the build number if it’s lower than the latest build number on TestFlight or the App Store.
This is a great way to make sure that we never forget to bump the build number again, and it saves us a lot of time in the long run. Now let’s do the same, but for your version number.
Version numbers
The version number is the same approach, but is even a little bit easier, because we can use the same built in Fastlane action to get both the App Store version and the TestFlight version.
Getting remote versions
First thing we need is the remote version, which we can get using the app_store_build_number
action, which returns the version in it’s SharedValues
. Let’s see how that looks:
desc 'Compare the local version number to the live and TF ones, and bumps it if needed.'
lane :compare_version_numbers do
app_store_build_number(
live: true,
app_identifier: 'com.example.app',
api_key_path: 'iTunesConnectKey.json'
)
live_version_number = lane_context[SharedValues::LATEST_VERSION]
app_store_build_number(
live: false,
app_identifier: 'com.example.app',
api_key_path: 'iTunesConnectKey.json'
)
tf_version_number = lane_context[SharedValues::LATEST_VERSION]
tf = Gem::Version.new(tf_version_number)
live = Gem::Version.new(live_version_number)
end
- This is assuming that you have a JSON file with your API key in the root of your project. If you don’t, you can use the
api_key
parameter to pass in the key directly. Just to break it down a little bit:
- We call the
app_store_build_number
action with thelive
parameter set totrue
, which will return the live version number. - We call the
app_store_build_number
action with thelive
parameter set tofalse
, which will return the TestFlight version number. - We convert the version numbers to
Gem::Version
objects, which allows us to compare them easily.
Getting the local version
To get the local version, we can use the get_version_number
action, which will return the version number from the Info.plist
file.
lane :project_version_number do
version_number = get_version_number(
xcodeproj: "some_project.xcodeproj",
target: 'SomeTarget'
)
version_number
end
Comparing and bumping
So just like before, now we’ve got 3 versions, but we really only need 2. so we can write a lane that expects 2 versions, compare them, and bump them if we need to.
lane :bump_version_number_if_needed do |options|
local_version_str = options[:local].to_s
remote_version_str = options[:remote].to_s
local = Gem::Version.new(local_version_str)
remote = Gem::Version.new(remote_version_str)
puts "Remote version: #{remote}, local version: #{local}"
if remote > local
puts 'Remote is higher than local'
increment_version_number(version_number: options[:remote])
puts 'Bumping to remote + 1'
increment_version_number(bump_type: 'patch')
elsif remote == local
increment_version_number
elsif local > remote
puts 'Local version is higher than remote, no need to bump'
end
end
Once again, this may seem a bit scary, but let’s break it down:
- We take in the local and remote version numbers as parameters.
- We convert them to
Gem::Version
objects, which allows us to compare them easily. - We compare the two, and if the remote version number is greater than the local version number, we increment the remote version number by 1, and set that as our version number.
- If the remote version number is equal to the local version number, we just increment the local version number by 1.
- If the remote version number is less than the local version number, we don’t do anything ( So we don’t bump when we don’t need to!).
Conclusion
In this post, we’ve seen how to automate the process of bumping the build number and version number using Fastlane. This is a great way to make sure that we never forget to bump the build number again, and it saves us a lot of time in the long run. You can just pick and drop this code into your Fastfile, and it will work out of the box. If you have any questions, or if there’s anything else you’d like to see, let me know!