I have released quite a few apps already, mostly at work but also personally. When releasing, I was facing issues with signing, Xcode configurations, CI/CD solutions or the AppStore Review. I assumed I have seen quite a lot of issues. Recently I was facing an interesting case I never encountered before, and I was very surprised something like this even exists.
I use an SDK, which internally reads the
CFBundleVersion values from its bundled
Info.plist. I noticed strange behaviour on TestFlight builds. The values got modified and were overridden with the values from the iOS target of my app in a release build. This caused some issues since the SDK was relying on these values. I was a little bit baffled at first that there seemed to be some setting which was altering my files without me implementing something like that. But then was able to find out why this was happening. In the following, I want to show you what caused this issue and how it could be fixed.
I am only using CI/CD solutions to export apps, so either
Xcode Cloud or
Bitrise and not Xcode directly. So I searched a little bit online but could not find something that matched my issue. Then I went ahead and tried to export and upload my app the good old manual way with Xcode directly. And then I found it! When exporting and uploading your app via Xcode to AppStore Connect you come across the following screen.
And here it says:
Manage version and build number
This will change the version and build number of all content in your app to ExecutableTarget.CFBundleVersionShortString (ExecutableTarget.CFBundleVersion)
It was exactly the issue I was facing. Now knowing the correct "term" I was able to find out more. This is an option that was introduced with Xcode 13.0 and per default is true. It is also mentioned in the official Apple Documentation for Distribution. As soon as the checkbox is unchecked all of
Info.plist files stayed the same and nothing was overridden any longer.
To disable this behaviour programmatically, e.g. for the usage in a CI/CD environment you need to add the
manageAppVersionAndBuildNumber key in your
ExportOptions.plist and set its value to
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>manageAppVersionAndBuildNumber</key> <false/> </dict>
With that in place, you opt out of the default behaviour and the
Info.plist files of all of your frameworks are untouched.
❗ Be aware that if you disable this option and you implement a watchOS companion app or include app extensions in your executable, you have to manually make sure that
CFBundleVersion do match the main executable.
After all of the issues has been resolved I was asking myself, why Apple introduced this option and made it true by default. I couldn't find an official answer to this, so I can only guess. I think the primary use case for this option is that it also causes the
Info.plist files of the extension to be aligned with the main executable target, which on iOS is a requirement e.g. for a Widget extension. So it seems to be rather a safeguard for developers in case they forgot to align these values.
While I do like good defaults, in that case, it caused more issues for me than it helped. As soon as I had some "official name" for the feature I found a lot of entries of other developers facing the same issue. There were a lot of cases where 3rd party SDKs relied on reading the
CFBundleVersion values from its bundled
Info.plist and then behaved weirdly because the values did not match their expectations.
So I think we need to be careful with this option and consider the following points:
- As an SDK or library developer it's not safe to rely on
CFBundleVersionvalues set in the
Info.plistfile. They can be intentionally or accidentally overridden by the application the artefacts are included in. So it's safer to use custom keys for the version and build number if the code relies on those. Or if there is a reason to not use custom keys, communicate to consumers that these values must not be altered.
- As an app developer we need to ensure that the option is set in a way that does not break embedded internal or external code.
I hope I was able to highlight this rather hidden option within the AppStore/Xcode ecosystem and what issues can arise from using it incorrectly. As soon as you know what's going on, the issue can get fixed with small changes, but you need to know where to look at.
If you find any mistake or would like to reach out to me please feel free to do so.
See you next time! 👋