How to stop Xcode 11 from changing CFBundleVersion and CFBundleShortVersionString to $(CURRENT_PROJECT_VERSION) and $(MARKETING_VERSION)?
The road so far
My use case was that:
- I'm synchronizing the version and build numbers across several targets.
- I'm synchronizing the version and build numbers with the target's
Settigns.bundle
- I'm reading and modifying the the build number from a CI server.
I used to execute point 1 and 2 as a target build script and point 3 as a custom script on the CI itself.
The new way of storing the version and build within the Xcode build settings were causing issues with the scripts, because they were no longer able to effectively modify the values. At least reading was possible.
Unfortunately i was not able to discover a legit way of preventing Xcode from storing the version and build numbers into the project build settings, however i've managed to create a workaround.
It turns out that when a build or an archive is made, the value written in the Info.plist
is used. This means that the value is substituted during build time, which does not allow us to modify it during the same build time.
I've also tried to modify the project using xcodeproj
cli, however any changes to the project were causing any builds to stop, so this solution was not working.
Eventually, after a lot of different approaches that i tried, i've finally managed find a compromise that was not violating the Xcode's new behavior.
Short Answer:
As a target pre-action, a script is executed which writes the respective values to CFBundleShortVersionString
and CFBundleVersion
to the target's Info.plist
As a source of truth, i use the Xcode build settings to read the values of MARKETING_VERSION
and CURRENT_PROJECT_VERSION
of the desired target.
This way, when you modify the values from the project settings - upon the next build/archive - they will be written to the Info.plist
, allowing any if your existing scripting logic to continue to work.
Detailed Answer
The only way to modify a resource upon a build action is using a pre-action
script. If you try doing it from a build script - the changes will not take effect immediately and will not be present at the end of the build/archive.
In order to add a pre-build action - go to edit scheme.
Then expand the Build and Archive sections.
Under Pre-action
, click the Provide build and settings from
dropdown and select the source of truth target from which you wish to read the values.
Add the following script:
# 1)
cd ${PROJECT_DIR}
# 2)
exec > Pruvit-Int.prebuild.sync_project_version_and_build_with_info_plists.log 2>&1
# 3)
./sync_project_version_and_build_with_info_plists.sh $MARKETING_VERSION $CURRENT_PROJECT_VERSION
The scrip lines do the following:
- Go to the directory where the sync script is located in order to execute it
- Allows a log to be written during the pre-action, otherwise any output is silenced by default
- Execute the sync script by providing the
MARKETING_VERSION
andCURRENT_PROJECT_VERSION
The final step is to write your own sync script that reads the values of the provided MARKETING_VERSION
and CURRENT_PROJECT_VERSION
to the respective target/s and whenever else you want.
In my case the script is the following:
#!/bin/bash
#IMPORTANT - this script must run as pre-action of each target's Build and Archive actions
version_number=$1
build_number=$2
echo "version_number is $version_number"
echo "build_number is $build_number"
#update Pruvit/Info.plist
pruvitInfoPlist="Pruvit/Info.plist"
/usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString $version_number" $pruvitInfoPlist
/usr/libexec/PlistBuddy -c "Set CFBundleVersion $build_number" $pruvitInfoPlist
#update Pruvit/Settings.bundle
settingsPlist="Pruvit/Settings.bundle/Root.plist"
/usr/libexec/PlistBuddy -c "Set PreferenceSpecifiers:0:DefaultValue $version_number" $settingsPlist
/usr/libexec/PlistBuddy -c "Set PreferenceSpecifiers:1:DefaultValue $build_number" $settingsPlist
#update BadgeCounter/Info.plist
badgeCounterInfoPlist="BadgeCounter/Info.plist"
/usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString $version_number" $badgeCounterInfoPlist
/usr/libexec/PlistBuddy -c "Set CFBundleVersion $build_number" $badgeCounterInfoPlist
I use shared Info.plist
and Settings.bundle
between both of my app targets, so i have to update this once.
Also i use a notification service extension BadgeCounter
, which has to have the exact same version and build as the target into which it is embedded. So i update this as well.
Don't.
Presumably there is a reason why this behavior changed. If later Xcode features build on this behavior, things get more and more "constructed" down the line.
Instead of trying to bend Xcode, change how the build script retrieves these values:
How to read current app version in Xcode 11 with script
If you need to manipulate the project.pbxproj
file, it is a Next style plist that is well documented. You can use plistbuddy
which is compatible with this old format. You can also use awk
with more scripting if you have more complex manipulations.
If I understand your use case, you could write a script that gets the highest version numbers with awk
and then updates all lower version numbers it can find in the file with sed
.