25

Since version 11, Xcode sets my CFBundleVersion value to $(CURRENT_PROJECT_VERSION) and my CFBundleShortVersionString to value $(MARKETING_VERSION) whenever I enter Version or Build values in the target settings (tab "General").

The actual version and build values that I enter are now stored in the project.pbxproj file. I do not want or like this behaviour, as I use shell scripts to modify the values at buildtime.

I can manually set the correct values in the Info.plist file, but as soon as I change Version or Build numbers in the target settings, the Info.plist file gets changed again by Xcode.

How do I stop Xcode 11 from doing this?

When I modify my build script to change the project file itself, Xcode will immediately cancel the build as soon as the project file is changed.

hotdogsoup.nl
  • 1,078
  • 1
  • 9
  • 22
  • Why would you want Xcode 11 to stop doing this, instead of modifying your shell script to retrieve the value? – Manuel Oct 14 '19 at 23:19
  • 3
    @Manuel I think modifying a plist using `plistbuddy` is nice and clean, whereas modifying the project file is much more messy, unreliable and prone to unexpectetd changes in the file format. – hotdogsoup.nl Oct 16 '19 at 08:48
  • 1
    Manipulating the project.pbxproj file is not messy when you understand the file format. It’s just a Next style plist that is well documented. You can even modify the file with plistbuddy, it is compatible with this format. – Manuel Oct 16 '19 at 12:46
  • I updated my answer with a suggestion for your use case. – Manuel Oct 16 '19 at 12:57

1 Answers1

7

The road so far

My use case was that:

  1. I'm synchronizing the version and build numbers across several targets.
  2. I'm synchronizing the version and build numbers with the target's Settigns.bundle
  3. 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.

enter image description here

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.

enter image description here

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:

  1. Go to the directory where the sync script is located in order to execute it
  2. Allows a log to be written during the pre-action, otherwise any output is silenced by default
  3. Execute the sync script by providing the MARKETING_VERSION and CURRENT_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.

KoCMoHaBTa
  • 1,519
  • 15
  • 16