How to stop Xcode 11 from changing CFBundleVersion and CFBundleShortVersionString to $(CURRENT_PROJECT_VERSION) and $(MARKETING_VERSION)? - xcode11

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.

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 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.

Related

Xcode appears to be modifying paths inside bash scripts during run script build phase

I have a simple script that looks like this:
#!/bin/sh
set -eux
install_folder="${HOME}/Library/MobileDevice/Provisioning Profiles"
mkdir -p "${install_folder}"
if [[ $? != 0 ]]; then
echo "Unable to create destination directory: ${install_folder}"
exit 1
fi
If I run this script from the command line by doing ./my_script.sh everything works as expected. Things go wrong though when I call from Xcode as part of a run script build phase. I currently call it by having "${SRCROOT}/path/to/my_script.sh" in the run script build phase, but the same issue occurs even if I copy and paste the code above in directly.
So what's the issue? Well, it seems Xcode is causing the wrong folder to be created. When I run from the command line, I get a folder named Provisioning Profiles inside ~/Library/MobileDevice/ as expected. When I run from Xcode, the folder is named Provisioning\ Profiles (that \ is literally part of the name).
But it gets weirder. If I change the mkdir line to mkdir -p $install_folder then I'd expect to get a folder called Provisioning inside the MobileDevice folder and a folder called Profiles wherever I ran the command. That's what happens when I run from the command line. If I run from Xcode however, I get a folder Profiles inside MobileDevice but I also get a folder called Provisioning\ Profiles.
I cannot explain this behavior at all. It seems totally counter to everything I (thought) knew about shell scripts.
How is Xcode influencing this? How do I make it stop?
The trick, as always, was realising that there was more to this than I had considered. Xcode wasn't just running this script in the phase, it was doing it with a list of output files set. It was then creating the path for those after it ran the script. The script was behaving exactly as it should have, it was just the extra stuff which made it appear to be broken.
Lesson learned: Xcode will create a folder for output files if it doesn't exist.

How to define Setup File Name at build time in InstallShield

We are launching our installer build using IsCmdBld.exe and would like to define at buildtime the name of the Setup File Name .exe
I have tried doing this by passing a command similar to this:
IsCmdBld.exe -p c:\project_path\installer_project.ism -c "My Custom Configuration" -z "SetupFileName=My App 1.0"
Unfortunately, it seems SetupFileName is ignored.
Anybody got ideas?
This may not be the exact answer you are looking for, but if you want to change the name within the project for each build, go to the Media / Releases menu. Click on the project name under "Releases", and in the General information is the Setup File Name. Enter your desired name here without the '.exe' extension.
Using Installshield for VS 2015 this is the updated answer:
Typically you would want your setup.exe to stay the same name every time you build. Especially the MSI filename. The version of the file can be set on the command line.
So for our setup, we specify the desired name of the setup.exe and msi filename in the product/release configuration of the ISM. Then on the command line, we always call the ISM with the same command line, which gives us repeatability/reliability.
"C:\Program Files (x86)\InstallShield\2012Spring SAB\System\IsCmdBld.exe" -p "E:\Path\to\the.ism" -y 3.6.356.2 -a "PRODUCT_CONFIGURATION" -o "E:\local\mergemodules"
-y sets the version (ProductVersion) which also sets the resulting EXE version
-a specifies the product configuration
-o specifies a folder for merge modules (we limit the merge modules we consume)

Update CFBundleShortVersionString in dSYM at build?

I'm using this as a build phase in my project:
export PLISTBUDDY="/usr/libexec/PlistBuddy"
export INFO="${CODESIGNING_FOLDER_PATH}/Info.plist"
export RXREVISION=`git describe --tags | perl -pi -E "s/[^-]+\-([^-]+).*/\1/"`
export RXVERSION=`git describe --tags | perl -pi -E "s/([^-]+)\-[^-]+.*/\1/"`
$PLISTBUDDY $INFO -c "add CFBundleVersion string $RXREVISION"
$PLISTBUDDY $INFO -c "set :CFBundleVersion $RXREVISION"
$PLISTBUDDY $INFO -c "add CFBundleShortVersionString string $RXVERSION"
$PLISTBUDDY $INFO -c "set :CFBundleShortVersionString $RXVERSION"
This works perfectly for updating the build revision in the built application's Info.plist, and it doesn't mutate my source tree.
I have tried putting my Update Version script both at the end and also before the link phase. Either way, it's able to affect changes to the built application, but the dSYM is built from the original source tree.
This mismatch of version numbers between the built app and the built dSYM is a problem. (HockeyApp throws an error here.)
If I update the Info.plist in my source tree, I need to deal with it changing. Which leaves me two options that I can see:
Put Info.plist under source control, and have the script edit it directly. But this means I'll need to check in the changes after each build.
Ignore Info.plist, and have the script edit it directly. But there's plenty of other things in Info.plist that I do want under source control.
How can I get the version in the dSYM updating automatically, too, without having to deal with the Info.plist in my source tree changing every build?
Also, tangentially: How do I see the version in the dSYM?
The accepted answer is correct, but doesn't give the actual details on how to modify the plist for the dsym. Adding the following lines to the build script modifies the plist in the dsym:
cd "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app.dSYM/Contents"
$PLISTBUDDY -c "Set CFBundleVersion $RXREVISION" Info.plist
Here’s the full script for the “Set CFBundleVersion from git” build phase I’m using:
APP_INFO_PLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
DSYM_INFO_PLIST="${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist"
BUILD_NUMBER=`git rev-list HEAD --count`
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$APP_INFO_PLIST"
if [ -f "$DSYM_INFO_PLIST" ] ; then
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$DSYM_INFO_PLIST"
fi
(Note it must run after “Copy Bundle Resources” in Xcode 6+. In Xcode 10's new build system, make sure to specify the Info.plist file as part of its input files.)
The dSYM package also has an info.plist in the root folder with similar values. You could probably modify that too in a similar way.
Another option is to define the version number in an extra .xcconfig file and include the version number from the info.plist into it, see here.
You could then also decide which kind of versioning you want or release or test builds, by using multiple independent info.plist files and referencing to different .xcconfig files in the build settings and don't include the one for test builds under source control.
In general I would suggest to do commits for every version change, since this makes the code really reproducible and also uniquely identify it in the source control system.
Apart from that, Apple specifies to set CFBundleVersion to a "monotonically increased string, comprised of one or more period-separated integers", see here. CFBundleShortVersionString is your marketing version, e.g. your goal is to work on version 3.0.
So how about doing the following for a release build: Update CFBundleVersion with the new build number and CFBundleShortVersion with the new marketing version like 3.0 Beta 1 and commit both of them and then tag that commit for release. Version numbers are part of your source like any other meta data, especially if the version number are also used to trigger specific code for compatibility, database upgrades or other things (which is often done).
I created a script that I can drop into any Xcode project folder and call from a Run Script which will update the Info.plist for the app and the dSYM so that the build number matches. It can then be uploaded to services like HockeyApp and iTunes Connect for TestFlight and the App Store.
I prefer to manage the script outside of Xcode's Run Script because I can edit it more easily and keep the contents of the project file much smaller. I can also version control the script independently of the project file.
The Build Number in this script is set with just the current date. There are other ways to generate a unique build number. One approach is covered on Jared Sinclair's blog which uses the Git hash for the latest commit. The script I am using uses a timestamp which goes down to a minute. I find it useful to know when a build was created and having the build number double as a timestamp I can see the date immediately. And for my purposes it is unique enough.
http://blog.jaredsinclair.com/post/97193356620/the-best-of-all-possible-xcode-automated-build
#!/bin/sh
set -e
# Purpose: Updates Info.plist for app and dSYM to a unique value for each build.
# Usage:
# Add as a Run Script in Xcode Build Phases
# UPDATE_SCRIPT=${PROJECT_DIR}/update_build_number.sh
# if [ -f ${UPDATE_SCRIPT} ]; then
# sh ${UPDATE_SCRIPT}
# fi
BUILD_NUMBER=`date "+%Y.%m.%d.%H%M"`
APP_INFO_PLIST=${TARGET_BUILD_DIR}/${INFOPLIST_PATH}
DSYM_INFO_PLIST=${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist
if [ -f ${APP_INFO_PLIST} ]; then
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "${APP_INFO_PLIST}"
echo "Updated ${APP_INFO_PLIST}"
else
echo "Could not find ${APP_INFO_PLIST}"
fi
# Only the Release Configuration creates the dSYM
if [ "${CONFIGURATION}" = 'Release' ]; then
if [ -f ${DSYM_INFO_PLIST} ]; then
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "${DSYM_INFO_PLIST}"
echo "Updated ${DSYM_INFO_PLIST}"
else
echo "Could not find ${DSYM_INFO_PLIST}"
fi
fi

XCode Project: how to easily find all the XIBs that have used a specific image (say, abc.png)

XCode Project:
How to easily find all the XIBs that have used a specific image (say, abc.png) ?
Add a pre-build script action to your current scema using the /bin/sh shell and project environment variables enabled, and in the first line put:
grep -Ri "abc.png" ${PROJECT_DIR}/*.xib > ~/Desktop/SearchResults.log
Then build the project, and on your desktop will be a file containing the file names and lines that contain "abc.png"
simply use your text editor's search function or grep. development xibs are xml.

Add files to an Xcode project from a script?

Right now I'm using a few scripts to generate files that I'm including as resources in Xcode. The thing is I'm running the script, then deleting from the project, then adding back into the project. There must be a way to automate this last step, so that the script can generate the files and automatically add them into the xcode project for me.
I'm using bash but any language examples would help.
Thanks,
Andrew
I had a similar need as Andrew. I needed to be able to include resources from a script without knowing those resources ahead of time. Here's the solutions I came up with:
Add a new Run Script build phase after “Copy Bundle Resource” that contains the following command:
find -L ${SRCROOT}/SomeDerivedResources \
-type f -not -name ".*" \
-not -name "`basename ${INFOPLIST_FILE}`" \
| xargs -t -I {} \
cp {} ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/
Looks scary, but let’s break it down:
find -L ${SRCROOT}/SomeDerivedResources
This crawls the directory SomeDerivedResources in our source root (-L tells it to follow symbolic links)
-type f
Only include regular files
-not -name ".*"
Ignore files starting with a dot
-not -name "`basename ${INFOPLIST_FILE}`"
In my case, my Info plists live in my SomeDerivedResources directory so we need to exclude that file from being copied to our product
| xargs -t -I {}
Pipe the results of find into xargs with -t (echo resulting commands to stderr so they show up in our build log), -I (run the command once for each input file) and use {} as our argument placeholder
cp {} ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/
Lastly, copy each found file (denoted by {}) to our product’s resource directory.
I realized when typing this that using an rsync setup instead of cp could prevent us from copying resources each time you build. If your resources are very large it might be worth looking in to.
(Also, a folder reference wouldn’t work for my need for a few reasons. One, my icons are in my DerivedResources directory and having them in a subdirectory in the bundle seems not to work. Also, I ideally wanted to be able to use [UIImage imageNamed:#"MyAwesomeHappyImage.png"] and -pathForResource:ofType: (and some of my files are nested further inside my DerivedResources directory). If your needs don’t contain those restraints, I highly suggest you go the folder reference route.)
This can be done by adding a new build phase to your application.
In your Xcode project browser, find the target for your application, and expand it to show all of the build phases.
Add a new "run script" build phase to your target. The easiest way is to right-click on the target and choose "Add/New Build Phase/New Run Script Build Phase"
Adding the new build phase should bring up an inspector window. In this window, you can enter the entire shell script, or simply a command line to run the script.
Here's the gold: At the bottom of the inspector window you can specify input files and output files. Specifying input files sets up dependencies automatically (the shell script will only be executed if some of the input files have been modified). Specifying output files automatically propagates the dependencies to those files. When your shell script is run, Xcode knows that it needs to deal with those files that the shell script has modified.
Be sure to drag your new build phase up to the top of the list of phases as shown in the screenshot below. The order will be important if you need those resource files to be included in the bundle.
Save, build, commit to the repository, ask for a raise, get some fresh air and have a nice day! :)
For those with large number of files, to avoid having to recopy (or recheck) each file, as suggested by #Ben Cochran (thanks a lot for the great script), this is how to do it with rsync:
Basically, the files just need to be copied into the main bundle
In that case just add a folder reference to the project (Create a folder in your project folder and then drag it into your projects "Resources" group (in the "Files & Groups" list; then in the sheet that appears select the "Create Folder References for any added Folder" radio button) and Xcode will copy the folder and all of its contents into the target bundle at build time.
Just an additional note: If you use this method to add image subfolders you'll have to prefix the image name with the subfolder name to use '[UIImage imageNamed:]'. For example if you have an image named "Rendezvous.png" in a subfolder named "MyImages":
`
// this won't work
UIImage * image = [UIImage imageNamed:#"Rendezvous"];
if (image) {
NSLog(#"Found Rendezvous!");
} else {
NSLog(#"Didn't find Rendezvous.");
}
// but this will!
image = [UIImage imageNamed:#"MyImages/Rendezvous"];
if (image) {
NSLog(#"Found MyImages/Rendezvous!");
} else {
NSLog(#"Didn't find MyImages/Rendezvous.");
}
`
If you already have the files somewhere on your system, relative to your source root, you can always add a "Copy Files" phase. This will allow you to specify a directory where your resources should be copied from.
You can combine this with the Build Script phase answer provided to you already. For instance, run a script to check out your assets from Subversion into a subdirectory of your project, and then follow that up with a Copy Files phase that copies from "$(SRCROOT)/Assets".
I know it's a bit late, but I just came across this article explaining how to do something that sounds like what you're looking for.
I found myself with a similar situation using Ionic Capacitor. What I was expecting was to include files on the "Copy Bundle Resources" bundle phase. What I found is that Ionic already packs you some inclusions and if you slip your files along this folders you get it included as well.
Do you see the App folder inclusion? It our entry point.
To include on it I add a script that do something like this:
cp -Rf ./includes/yourfolder/ ./ios/App/App/
I managed to solve the issue
"Code object is not signed at all"
that can be encountered during build upload to iTunes Connect in this way:
I didnot include the script to Bundle resources.
So the script (in this case Python file) is executed during build, (it does what it has to do) but it is not included in the bundle of the app.
How to do?
Open Build Phases, go to Copy Bundle Resources section, select the file and remove it with (-).

Resources