Skip to content

Instantly share code, notes, and snippets.

@karlvr
Forked from nickshanks/xcode-git-version.sh
Last active October 15, 2022 17:36
Show Gist options
  • Save karlvr/c93a98d7000ecb163895 to your computer and use it in GitHub Desktop.
Save karlvr/c93a98d7000ecb163895 to your computer and use it in GitHub Desktop.
#!/bin/bash
# This script automatically sets the version and short version string of
# an Xcode project from the Git repository containing the project.
#
# To use this script in Xcode, add the script's path to a "Run Script" build
# phase for your application target.
set -o errexit
set -o nounset
# First, check for git in $PATH
hash git 2>/dev/null || { echo >&2 "Git required, not installed. Aborting build number update script."; exit 0; }
# Alternatively, we could use Xcode's copy of the Git binary,
# but old Xcodes don't have this.
#GIT=$(xcrun -find git)
# Run Script build phases that operate on product files of the target that defines them should use the value of this build setting [TARGET_BUILD_DIR]. But Run Script build phases that operate on product files of other targets should use “BUILT_PRODUCTS_DIR” instead.
INFO_PLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
# Build version (closest-tag-or-branch "-" commits-since-tag "-" short-hash dirty-flag)
BUILD_VERSION=$(git describe --tags --always --dirty=+)
# Use the latest tag for short version (expected tag format "vn[.n[.n]]")
# or if there are no tags, we make up version 0.0.<commit count>
LATEST_TAG=$(git describe --tags --match 'v*' --abbrev=0 2>/dev/null) || LATEST_TAG="HEAD"
if [ $LATEST_TAG = "HEAD" ]
then COMMIT_COUNT=$(git rev-list --count HEAD)
LATEST_TAG="0.0.$COMMIT_COUNT"
COMMIT_COUNT_SINCE_TAG=0
else
COMMIT_COUNT_SINCE_TAG=$(git rev-list --count ${LATEST_TAG}..)
LATEST_TAG=${LATEST_TAG##v} # Remove the "v" from the front of the tag
fi
if [ $COMMIT_COUNT_SINCE_TAG = 0 ]; then
SHORT_VERSION="$LATEST_TAG"
else
# increment final digit of tag and append "d" + commit-count-since-tag
# e.g. commit after 1.0 is 1.1d1, commit after 1.0.0 is 1.0.1d1
# this is the bit that requires /bin/bash
OLD_IFS=$IFS
IFS="."
VERSION_PARTS=($LATEST_TAG)
LAST_PART=$((${#VERSION_PARTS[@]}-1))
VERSION_PARTS[$LAST_PART]=$((${VERSION_PARTS[${LAST_PART}]}+1))
SHORT_VERSION="${VERSION_PARTS[*]}d${COMMIT_COUNT_SINCE_TAG}"
IFS=$OLD_IFS
fi
# Bundle version (commits-on-master[-until-branch "." commits-on-branch])
# Assumes that two release branches will not diverge from the same commit on master.
if [ $(git rev-parse --abbrev-ref HEAD) = "master" ]; then
MASTER_COMMIT_COUNT=$(git rev-list --count HEAD)
BRANCH_COMMIT_COUNT=0
BUNDLE_VERSION="$MASTER_COMMIT_COUNT"
else
MASTER_COMMIT_COUNT=$(git rev-list --count $(git rev-list master.. | tail -n 1)^)
BRANCH_COMMIT_COUNT=$(git rev-list --count master..)
if [ $BRANCH_COMMIT_COUNT = 0 ]
then BUNDLE_VERSION="$MASTER_COMMIT_COUNT"
else BUNDLE_VERSION="${MASTER_COMMIT_COUNT}.${BRANCH_COMMIT_COUNT}"
fi
fi
# For debugging:
echo "BUILD VERSION: $BUILD_VERSION"
echo "LATEST_TAG: $LATEST_TAG"
echo "COMMIT_COUNT_SINCE_TAG: $COMMIT_COUNT_SINCE_TAG"
echo "SHORT VERSION: $SHORT_VERSION"
echo "MASTER_COMMIT_COUNT: $MASTER_COMMIT_COUNT"
echo "BRANCH_COMMIT_COUNT: $BRANCH_COMMIT_COUNT"
echo "BUNDLE_VERSION: $BUNDLE_VERSION"
/usr/libexec/PlistBuddy -c "Add :CFBundleBuildVersion string $BUILD_VERSION" "$INFO_PLIST" 2>/dev/null || /usr/libexec/PlistBuddy -c "Set :CFBundleBuildVersion $BUILD_VERSION" "$INFO_PLIST"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $SHORT_VERSION" "$INFO_PLIST"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUNDLE_VERSION" "$INFO_PLIST"
@karlvr
Copy link
Author

karlvr commented May 8, 2014

Changes from original:

  • Use PlistBuddy instead of default write, as suggested in the comments - but note that we don't want to change the Info.plist in the project itself, as that will cause merge nightmares.
  • Support / require SemVer style tag versions
  • Support git repositories that do not yet have tags; assume 0.0. version style

@wvdvegt
Copy link

wvdvegt commented May 12, 2014

Hi,
First many thanks for your excellent script on getting version numbers compiled into an iOS app.

Got a small addition. I did not like the fall-back version string if tags where missing so added a few lines to insert the projects version number from Xcode (the one you have to update manually) and append the build number to that instead of using 0.0 as a base.

I added to the start of your script:

PLIST_PATH=$(find . -type f -iname *-Info.plist -d 2)
PROJECT_TAG=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" $PLIST_PATH)

to the start of the project (to find the main plist).

Finally I changed the line inside the:

if [ $LATEST_TAG = "HEAD" ]

then branch to read:

LATEST_TAG="$PROJECT_TAG.$COMMIT_COUNT"

Now the version number reads something like 1.0.130 instead of 0.0.130.

Please note my scripting knowledge is minimal so improvements are possible.

regards
wvdvegt

@couchdeveloper
Copy link

First off: Thank you very much for sharing this useful script! :)

There seems to be an issue, when building on a non-master branch which has not yet at least 1 commit.

For example, on master $ git checkout -d test1
Then building the project will fail in line:

58 MASTER_COMMIT_COUNT=$(git rev-list --count $(git rev-list master.. | tail -n 1)^)

because (test1)$ git rev-list master.. | tail -n 1 returns an empty string, and thus

(test1)$ git rev-list --count ^

returns an error:

fatal: bad revision '^'

On the other hand, when there is at least one commit, e.g. on branch develop:

(develop)$ git rev-list master.. | tail -n 1

will return a list of hashes, e.g.:

a54af49fdf0777af681818832a3ca0454456d27f

and thus

(develop)$ git rev-list --count a54af49fdf0777af681818832a3ca0454456d27f^

returns the number of commits, and the build succeeds.

(git version: 1.9.2)

@johnboiles
Copy link

I hit the same thing as @couchdeveloper. I'll post here if I get time to build a fix.

@knox
Copy link

knox commented Apr 27, 2015

I found a solution for the above problem at https://github.com/uranusjr/macdown/blob/master/Tools/utils.sh

if [ $(git rev-list --count master..) = 0 ]; then   # The branch is attached to master. Just count master.
        MASTER_COMMIT_COUNT=$(git rev-list --count HEAD)
    else
        MASTER_COMMIT_COUNT=$(git rev-list --count $(git rev-list master.. | tail -n 1)^)
    fi

@Bobisback
Copy link

has anyone tried this script in an xamarin forms application? I am looking for a solution like this for ios and android (if possible) for an xamarin forms project.

P.S and to clarify this is for a ios project. I am not sure if this for-fills the requirements for the ios build numbers and testflight.

@chetstone
Copy link

I've been using a variant of this script for years but it stopped working with the xcode 10 new build system. The values created by this script get overwritten by the generic project plist. The solution here is to add the plist file as an input to the run script phase.

@NSGod
Copy link

NSGod commented Oct 15, 2022

Just thought I'd add that while CFBundleShortVersionString and CFBundleVersion are actual keys, there is no such thing as CFBundleBuildVersion. It's not an official key as far as I can tell. A google search returns a measly 3 results. I'm not really sure why it's being set....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment