32

The version and build number (or version and short version) of a Watchkit app and extension have to be set to the same value as the containing app.

I use environment variables to set the apps version in the Info.plist dynamically at build time. That also works fine for the Watchkit extension, but not for the Watchkit app.

The environment variables I use have to be provided in the plist for the main app and extension without ${} (for variable ${VERSION} I set VERSION).
if I do the same for the Watchkit app, it is taking the string itself, not the value. If I provide it with dollar & brackets there is no data in the variable.

Any idea how to set the variables for the Watchkit app?

dogsgod
  • 6,267
  • 6
  • 25
  • 53
  • Similar Question: [How to auto-increment Bundle Version in Xcode 4?](http://stackoverflow.com/q/6286937/642706) – Basil Bourque Oct 11 '16 at 00:12
  • “I use environment variables to set the apps version in the Info.plist dynamically at build time.” — I'm here to find out how you do that. – Martin Jul 02 '20 at 13:58
  • It's 2022 and this is now very simple - see my answer below https://stackoverflow.com/a/72790925/7107094 – Trev14 Aug 04 '22 at 19:09

10 Answers10

23

I use this to update all the targets:

#!/bin/bash
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$INFOPLIST_FILE")
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$INFOPLIST_FILE"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/Great WatchKit App/Info.plist"
Thomas Kekeisen
  • 4,355
  • 4
  • 35
  • 54
17

My CFBundleVersion is the number of commits on my master branch on the git repo.

On my main app target, in Build Phases > + New Run Script Phase I've added this script:

# Set the build number to the count of Git commits
buildNumber=$(git rev-list --count HEAD)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/app WatchKit Extension/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/app WatchKit App/Info.plist"

From app WatchKit App the app should be the name of your app, but check the exact path.

Lucien
  • 8,263
  • 4
  • 30
  • 30
  • 1
    At which position in your Build Phases did you add this? Before 'Compile Sources'? – stk May 04 '15 at 15:02
  • 1
    After, it's the last one. I've marked `Run script only when installing` so it will increment only on archives. Didn't like to pollute every commit with a change on 3 plists. – Lucien May 05 '15 at 01:59
  • 1
    I had to add this run script just after the **Copy Bundle Resources**, otherwise I got a failed build with complaining on non matching version numbers (however after the build the version numbers where all identical). – Magnus Apr 04 '16 at 20:24
  • For my configuration, I needed to replace `"${PROJECT_DIR}/${INFOPLIST_FILE}"` with `"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"`. – blwinters Dec 30 '17 at 13:52
  • 1
    If you change the build number for the plist files within `BUILT_PRODUCTS_DIR`, then they won't be tracked by Git in your main project directory, as explained [here](http://blog.jaredsinclair.com/post/97193356620/the-best-of-all-possible-xcode-automated-build). For example: iOS App: `"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"`, Watch app: `"${BUILT_PRODUCTS_DIR}/../${CONFIGURATION}-watchos/MyApp_Watch.app/Info.plist"` and Watch extension: `"${BUILT_PRODUCTS_DIR}/../${CONFIGURATION}-watchos/MyApp_Watch_Extension.appex/Info.plist"`. – blwinters Dec 30 '17 at 14:39
  • Should I add this script to my extension or also to may main app? If I add only to my extension is it going to work? Since in my main app has many script... – Steven Jun 19 '20 at 17:34
9

You can update the build version of all your targets without a build script. (You can also use this to update the marketing / short build version; in this case ignore changes to CFBundleVersion).

Open your project settings and set CURRENT_PROJECT_VERSION (Current Project Version) to the desired version number. In all targets make sure CURRENT_PROJECT_VERSION is empty (so that its value is inherited from the project). Then in all Info.plist files set CFBundleShortVersionString (Bundle versions string, short) and CFBundleVersion (Bundle version / build version) to $(CURRENT_PROJECT_VERSION).

Optionally, you can distinguish between the build version and the "marketing" version (this is useful if the build version is generated automatically). To do so, in your project settings, set MARKETING_VERSION (Marketing Version) to the desired version number. As before, make sure all targets inherit the value from the project. Finally, in all Info.plist files set CFBundleShortVersionString (Bundle versions string, short) to $(MARKETING_VERSION).

If you want to increment your CFBundleVersion on each build (or to have it reflect your git SHA). Use agvtool as described by dogsgod or see https://developer.apple.com/library/ios/qa/qa1827/_index.html.

davidisdk
  • 3,358
  • 23
  • 12
  • 1
    for me this is the correct answer, as all scripting is not needed and it seems this is what Apple thought it should be like. – Christian Aug 26 '16 at 06:24
  • 1
    For the CFBundleShortVersionString (Bundle versions string, short) I would use $(MARKETING_VERSION) – Dmitry Aug 27 '21 at 20:03
  • I agree this should be the accepted answer. Scripting shouldn't be necessary & creates more complexity than we need to deal with. Thanks for the simple solution. – Trev14 Jun 28 '22 at 16:51
6

stk's answer is right, but I wanted to add my findings as well.

One way to solve the problem is by using agvtools:

Create a new target in OSX > Other > External Build Sytem Add a run script similar to this one:

#!/bin/bash

#read vesion number from version.txt in project root
VERSION=$(head -n 1 version.txt)
BUILD=`git rev-list $(git rev-parse --abbrev-ref HEAD) | wc -l | awk '{ print $1 }'`

echo "${VERSION} (${BUILD})"

agvtool new-marketing-version ${VERSION}
agvtool new-version -all ${BUILD}

exit 0

I have a version.txt file with only my version number in it (marketing version or short bundle version) that can be easily adjusted by any CI system and use the number of my git SHAs as build number (bundle version) Adjust the sources for VERSION and BUILD to fit your requirements

Run the scheme that has been created for the new target before your build/archiving.

In case you need to have this as a dependency for your main target - this will fail, as it will stop the execution of the following targets (if somebody knows how to prevent that, I would be thankful for a hint)

But you can still achieve that with a script like the following executed for each of your plists (similar to what stk provided):

#!/bin/sh
#
# usage:
# set-version-in-plist.sh LIST VERSION BUILD
# LIST:      Info.plist path & name
# VERSION:   version number xxx.xxx.xxx
# BUILD:     build number xxxxx
#

# Location of PlistBuddy
PLISTBUDDY="/usr/libexec/PlistBuddy"

${PLISTBUDDY} -c "Set :CFBundleShortVersionString $2" "$1";
${PLISTBUDDY} -c "Set :CFBundleVersion $3" "$1";

Save this script as a file, make it executable (chmod +x SCRIPTNAME) Then execute it with the mentioned parameter for all your plists

This solution is not so convenient as the agvtools solution, but it should not stop your build when used in a dependency ...

dogsgod
  • 6,267
  • 6
  • 25
  • 53
  • Instead of using this as a build phase, you could put it in the hooks directory of your git/svn repo. – Shaolo Sep 19 '15 at 09:07
  • if you have that level of access, that might work - but I don't – dogsgod Aug 04 '16 at 05:40
  • Hi @dogsgod, I use svn and in my main app I use script which get the version number from svn. I see in ur example that used for git, is there away just to use in my extension that can inherit directly from main app plist, the version . – Steven Jun 19 '20 at 17:41
6

I have a Run Script that I attach to my main app target. It will propagate the WatchKit Extension and the WatchKit app upon building the app.

It is completely reusable. Enjoy!

buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${PROJECT_DIR}/${INFOPLIST_FILE}")

buildNumberDec=$(($buildNumber + 1))

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumberDec" "${PROJECT_DIR}/${INFOPLIST_FILE}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumberDec" "$SRCROOT/${PRODUCT_NAME} WatchKit Extension/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumberDec" "$SRCROOT/${PRODUCT_NAME} WatchKit App/Info.plist"
Sam Bantner
  • 375
  • 1
  • 3
  • 9
  • 1
    This is nearly an exact duplicate of Lucien. – stk May 04 '15 at 15:02
  • Is anything original anymore?!? :) It's just pulling the Product Name to allow for a more re-usable experience. – Sam Bantner May 13 '15 at 20:45
  • It's not only a duplicate of other answers, it's also highly redundant and does not add anything new – dogsgod May 14 '15 at 14:34
  • I had to add this run script just after the **Copy Bundle Resources**, otherwise I got a failed build with complaining on non matching version numbers (however after the build the version numbers where all identical). – Magnus Apr 04 '16 at 20:25
4

Well, if it doesn´t work like this, do it with a Run Script Build Phase. Do something like this:

#!/bin/sh
INFOPLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
echo "writing to $INFOPLIST"
PLISTCMD="Set :CFBundleVersion $(git rev-list --all|wc -l)"
echo -n "$INFOPLIST" | xargs -0 /usr/libexec/PlistBuddy -c "$PLISTCMD"

I don´t have the right paths for your WatchKit App, so you will have to change that yourself.

stk
  • 6,311
  • 11
  • 42
  • 58
  • 2
    Already found that solution, but I prefer agvtool - it sets all plists in one go – dogsgod Apr 04 '15 at 09:58
  • 1
    Nice! Can you share the solution? – stk Apr 04 '15 at 10:03
  • planned to do so - actually that's what you commented on yesterday, when I misunderstood some question ... – dogsgod Apr 04 '15 at 10:05
  • 4
    That won't work as you can't (as of Xcode 6.3) add any kind of build phases to a watchkit app, only the watchkit extension can have build phases. – Parrots May 01 '15 at 01:13
  • 1
    That´s right, my answer was related to Xcode 6.2, where the WatchKit App didn´t neither had the Build Settings tab nor had the check for same CFBundleVersion. – stk May 04 '15 at 14:38
  • See my answer for a working solution. It's working great with all versions including 6.3.1 – dogsgod May 04 '15 at 19:15
  • Yeah, agvtool is good, but I don´t want to use a second target for the deployment process. And for the manual setting with PListBuddy, I´m struggling with the timing and which-file-is-read-or-written-before-what-else. – stk May 05 '15 at 10:29
  • I´ve managed to get it working now, will update my answer in the next days. – stk May 14 '15 at 09:51
  • Hi @stk, I use Svn to commit my version and not git, how can I use ur script for my extension? Thanks – Steven Jun 19 '20 at 17:27
3

If it would be useful to supplement other answers with my own personal experience. These centred around build failure caused by ValidateEmbeddedBinary.

ValidateEmbeddedBinary will fail if the CFBundleVersion is not the same in the embedded WatchKit app and the parent app.

The error looks something like:

(null): error: The value of CFBundleVersion in your WatchKit app's Info.plist (1234) does not match the value in your companion app's Info.plist (7931). These values are required to match.

Working in XCode 7.3, the following will first update the parent app's plist. Then it updates the Debug or Release WatchKit app before PBXCp executes to copy it to the parent app directory:

#!/bin/sh

git=`sh /etc/profile; which git`
appBuild=`"$git" rev-list HEAD --count`

appPlistPath="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
watchKitPlistPath="${BUILT_PRODUCTS_DIR}/../${CONFIGURATION}-watchos/${PRODUCT_NAME} WatchKit App.app/Info.plist"

echo "Setting App CFBundleVersion $appBuild at info plist path at ${appPlistPath}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $appBuild" "${appPlistPath}"

echo "Setting WatchKit App CFBundleVersion $appBuild at ${watchKitPlistPath}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $appBuild" "${watchKitPlistPath}"

The above uses git's commit count as CFBundleVersion.

Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
  • 1
    Thanks for this. It's helpful to know how to define the `watchKitPlistPath` within `${BUILT_PRODUCTS_DIR}`, since that is not tracked by Git. – blwinters Dec 30 '17 at 14:23
3

Simple, Easy, No Scripting Needed

Tested with Xcode 13.4.1

This is by far the easiest way to sync build numbers and marketing versions across your targets.

Step 1

Make sure that the project's Current Project Version and Marketing Version are set. This will be the single source of truth for your build number and marketing version. Find these settings by clicking the project in the navigator, clicking the project (above the targets), and typing "versioning" into the filter.

Set project build number and marketing version

Step 2

Sync Build Number / Project Version (e.g. 123)

In your targets, go to the Build Settings tab and set Current Project Version to $(CURRENT_PROJECT_VERSION).

In your .plist files set Bundle version (same as CFBundleVersion) to $(CURRENT_PROJECT_VERSION).

Sync Marketing Version (e.g. 1.2.3)

In your targets, go to the Build Settings tab and set Marketing Version to $(MARKETING_VERSION)

In your .plist files set Bundle version string (short) (same as CFBundleShortVersionString) to $(MARKETING_VERSION).

Here's a link to my blog post on the topic with more helpful screenshots.

Trev14
  • 3,626
  • 2
  • 31
  • 40
  • This solution works. Do you have any suggestion how to increase the build number in the project file correctly? – gklka Sep 07 '22 at 15:27
1

To extend this thread, if someone encounters similar problem as me, hopefully script below will help.

For instance, I have watch target, watch extension, and an app share extension in my project.

I used run phase to update project's plist as suggested, and it works if I build the project, the .plist files are all updated as expected.

However the problem is when you archiving app(let's say all targets have different build number), the info plist in the archived projects was not updated. After a few times of try out, I found extension's plist files were copied before this run phase, and then the run phase script(updating project's plist) won't help with the archived plist. So I eventually changed the script to update compiled target's plist, and it works as I expected, I have same build number for all the targets in the application. Here is how I did it: add this script to each target's build phase:

infoPlistPath="${TARGET_BUILD_DIR}/${EXECUTABLE_FOLDER_PATH}/Info.plist"
PLISTBUDDY="/usr/libexec/PlistBuddy"
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')
$PLISTBUDDY -c "Set :CFBundleVersion $buildNumber" "${infoPlistPath}"

For different target, this EXECUTABLE_FOLDER_PATH was different, and it will update the compiled target's info plist, instead of the project's info plist. Just a note I checked "Run script only when installing" as well since I only need this to be run for archiving

Abhishek Bedi
  • 5,205
  • 2
  • 36
  • 62
air_bob
  • 1,317
  • 14
  • 26
0

You can also simply add the same Build Phase to EACH of your targets.

#Update build number with number of git commits if in release mode
if [ ${CONFIGURATION} == "Release" ]; then
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"
fi;

So if you have a Watch App, a Watch App Extension, a Widget Extension, or whatever else extension, go to each of their Build Phases, add a New Run Script Phase and paste the above.

Arnaud
  • 17,268
  • 9
  • 65
  • 83