WatchKit App Automated Build Numbers

I wrote previously about how I set up automated build numbers that were source-control friendly and didn't require a build server.

Unfortunately WatchKit apps (unlike WatchKit extensions) don't have build phases exposed in Xcode, so things get tricky.

After a month of trying I couldn't find a way to get automated numbering working through build scripts in the project itself. It always resulted in code signing errors as the best you could do with phases was change the number after the fact. I ended up having to fall back to using agvtool during release builds to manually bump (or at least using Fastlane, automatically bump) build numbers.

Then a friend pointed me in the right direction.

Manually editing .pbxproj for fun and profit

While the UI isn't exposed, you can still add build phases to the project file for a watchkit app.

Please back up your project first. You'll be manually editing the Xcode project file here.

In your app's folder right click the project file and show package contents. Then open project.pbxproj in your favorite text editor.

Step 1: We need to add a new build phase manually. I'd recommend you complete my previous tutorial first so you have existing phases for automating your build number that you can just duplicate.

Search the file for "CFBundleVersion", part of the script from the last tutorial. You should see it in a block like this left over from my last tutorial:

81B5AC951B0A816900E3CD52 /* Build Number */ = {
  isa = PBXShellScriptBuildPhase;
  buildActionMask = 2147483647;
  files = (
  );
  inputPaths = (
  );
  name = "Build Number";
  outputPaths = (
  );
  runOnlyForDeploymentPostprocessing = 0;
  shellPath = /bin/sh;
  shellScript = "git=`sh /etc/profile; which git`\nbranch_name=`$git symbolic-ref HEAD | sed -e 's,.*/\\\\(.*\\\\),\\\\1,'`\ngit_count=`$git rev-list $branch_name |wc -l | sed 's/^ *//;s/ *$//'`\nsimple_branch_name=`$git rev-parse --abbrev-ref HEAD`\n\nbuild_number=\"$git_count\"\n\nplist=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\ndsym_plist=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$plist\"\nif [ -f \"$DSYM_INFO_PLIST\" ] ; then\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$dsym_plist\"\nfi";
};

Copy and paste that block to duplicate it right below the first one, and then change the ID (81B5AC951B0A816900E3CD52 in my case) to something unique. I just changed the ending 2 to a 1. Do a quick search with the new ID to make sure you didn't accidentally pick an ID that's already been used elsewhere in the file.

Remember this ID you just made, we need it for step 2.

Step 2: We need to add a new build phase to the watchkit app. Do a search on the file for "com.apple.product-type.application.watchapp" to find the config used by the watchkit app. It'll look like this:

81B0136E1A6ABD5C0080C8C5 /* WatchKit App */ = {
	isa = PBXNativeTarget;
	buildConfigurationList = 81B013841A6ABD5C0080C8C5 /* Build configuration list for PBXNativeTarget "WatchKit App" */;
	buildPhases = (
		81B0136D1A6ABD5C0080C8C5 /* Resources */,
	);
	buildRules = (
	);
	dependencies = (
	);
	name = "WatchKit App";
	productName = "Slopes WatchKit App";
	productReference = 81B0136F1A6ABD5C0080C8C5 /* WatchKit App.app */;
	productType = "com.apple.product-type.application.watchapp";
};

Add a new line to the build phases using the ID of the phase you created from the last step.

buildPhases = (
  81B5AC951B0A816900E3CD51 /* Build Number */,
  81B0136D1A6ABD5C0080C8C5 /* Resources */,
);

At this point you can save and run and have automated build numbers working for your watchkit app too, satisfying Xcode 6.3's requirement that all your targets must share the same build number.

Big thanks to Conrad Kramer for getting me thinking in the right direction.