2

I recently use a script to increase my project's build number related to git commits, and i ran into an infinite loop.

I build, then the build number is changed, then i commit because the info.plist is changed, then the build number will be changed next time i build, so i have to commit it again since the info.plist is changed, then you know what is gonna happen.

is there any way to avoid this? or is there a better way to increase the build number automatically?

ps. since i worked the project with others, change the number by hand or bumps it every time i build is quite difficult to maintain the number.

Maxismo
  • 23
  • 3
  • I use something [similar to this question](https://stackoverflow.com/questions/21114376/how-to-auto-increment-the-build-number-in-xcode-5) but instead of incrementing a number, i count the number of commits since the project's start with `git rev-list HEAD --count`. – Guillaume Algis Feb 15 '16 at 10:52
  • See also http://blog.jaredsinclair.com/post/97193356620/the-best-of-all-possible-xcode-automated-build – Guillaume Algis Feb 15 '16 at 10:56
  • @GuillaumeAlgis: i do use `git rev-list HEAD --count`. :( – Maxismo Feb 15 '16 at 10:58
  • The "trick" in the article I linked is to never write the commit count in any file known to git. The count is written at the last minute into Info.plist *in the final build directory* (not your sources dir) just before creating and signing the `.ipa`. – Guillaume Algis Feb 15 '16 at 11:29
  • Does this answer your question? http://stackoverflow.com/q/35407266/2998 – Stephen Darlington Feb 15 '16 at 13:56
  • There are other ways as well. As per [my question](http://stackoverflow.com/questions/9258344/better-way-of-incrementing-build-number), where Xcode was getting upset when the `Info.plist` file was being updated behind its back, the simplest solution was to add the *bump build number* script as a separate Xcode target and make the other targets dependent upon *that* target. Then the `Info.plist` file has been updated well before Xcode needs to look at it, and that makes it happy. Last-minute-copying works, for sure, but it's not simplest solution to diagnose when things start going wrong. – trojanfoe Feb 15 '16 at 14:00
  • @StephenDarlington Did you mean to self-reference this question? – trojanfoe Feb 15 '16 at 14:18
  • @trojanfoe Yes. It's a bad infinite loop joke. – Stephen Darlington Feb 15 '16 at 14:19
  • @StephenDarlington Oh, blimey. Yeah. OK. – trojanfoe Feb 15 '16 at 14:20
  • @GuillaumeAlgis Thanks, this works. but there is another issue: when i try to archive it, the build number seems stick to info.plist. so i change the file again when i need to archive it. – Maxismo Feb 16 '16 at 02:56

3 Answers3

1

Yeah this is a classic issue. I use a similar technique but I don't relate the build number to the git commits, instead I detect changes to files in the source tree in relation to the modified time of a version file (a simple file that holds the version and build number). If there are changes then I bump the build number.

Here the python script I use. You should make it a separate target in your Xcode project and then make other targets dependent upon this target. It will update your .plist file(s) with the new version/build number as necessary. It assumes the source tree is below the location of the version file:

#!/usr/bin/env python
#
# Bump build number in Info.plist files if a source file have changed.
#
# usage: bump_buildnum.py buildnum.ver Info.plist [ ... Info.plist ]
#
# andy@trojanfoe.com, 2014.
#

import sys, os, subprocess, re

def read_verfile(name):
    version = None
    build = None
    verfile = open(name, "r")
    for line in verfile:
        match = re.match(r"^version\s+(\S+)", line)
        if match:
            version = match.group(1).rstrip()
        match = re.match(r"^build\s+(\S+)", line)
        if match:
            build = int(match.group(1).rstrip())
    verfile.close()
    return (version, build)

def write_verfile(name, version, build):
    verfile = open(name, "w")
    verfile.write("version {0}\n".format(version))
    verfile.write("build {0}\n".format(build))
    verfile.close()
    return True

def set_plist_version(plistname, version, build):
    if not os.path.exists(plistname):
        print("{0} does not exist".format(plistname))
        return False

    plistbuddy = '/usr/libexec/Plistbuddy'
    if not os.path.exists(plistbuddy):
        print("{0} does not exist".format(plistbuddy))
        return False

    cmdline = [plistbuddy,
        "-c", "Set CFBundleShortVersionString {0}".format(version),
        "-c", "Set CFBundleVersion {0}".format(build),
        plistname]
    if subprocess.call(cmdline) != 0:
        print("Failed to update {0}".format(plistname))
        return False

    print("Updated {0} with v{1} ({2})".format(plistname, version, build))
    return True

def should_bump(vername, dirname):
    verstat = os.stat(vername)
    allnames = []
    for dirname, dirnames, filenames in os.walk(dirname):
        for filename in filenames:
            allnames.append(os.path.join(dirname, filename))

    for filename in allnames:
        filestat = os.stat(filename)
        if filestat.st_mtime > verstat.st_mtime:
            print("{0} is newer than {1}".format(filename, vername))
            return True

    return False

def upver(vername):
    (version, build) = read_verfile(vername)
    if version == None or build == None:
        print("Failed to read version/build from {0}".format(vername))
        return False

    # Bump the version number if any files in the same directory as the version file
    # have changed, including sub-directories.
    srcdir = os.path.dirname(vername)
    bump = should_bump(vername, srcdir)

    if bump:
        build += 1
        print("Incremented to build {0}".format(build))
        write_verfile(vername, version, build)
        print("Written {0}".format(vername))
    else:
        print("Staying at build {0}".format(build))

    return (version, build)

if __name__ == "__main__":
    if os.environ.has_key('ACTION') and os.environ['ACTION'] == 'clean':
        print("{0}: Not running while cleaning".format(sys.argv[0]))
        sys.exit(0)

    if len(sys.argv) < 3:
        print("Usage: {0} buildnum.ver Info.plist [... Info.plist]".format(sys.argv[0]))
        sys.exit(1)
    vername = sys.argv[1]

    (version, build) = upver(vername)
    if version == None or build == None:
        sys.exit(2)

    for i in range(2, len(sys.argv)):
        plistname = sys.argv[i]
        set_plist_version(plistname, version, build)        

    sys.exit(0)
trojanfoe
  • 120,358
  • 21
  • 212
  • 242
0

The approach I use is to have a placeholder in the file that contains the version number, and then having the build server replace that placeholder with the correct version number locally before creating which the distributable package. The version of info.plist that is in the repository does not get changed; it always has the placeholder.

David Deutsch
  • 17,443
  • 4
  • 47
  • 54
0

I made a version of @trojanfoe's answer in pure bash. This assumes a file called versions placed in the root project directory.

#!/bin/bash

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }

shopt -s dotglob

versionModifiedAt=$(date -r versions +%s)
needsUpdate=false

processEntry() {
  if [[ "$1" == "versions" ]]; then return; fi
  if [[ "$1" == ".git" ]];     then return; fi
  if beginswith ".git/" "$1";  then return; fi

  if [ $(date -r "$1" +%s) -gt "$versionModifiedAt" ]; then
    needsUpdate=true
  fi
}

# Top level
for entry in *; do
  processEntry "$entry"
  if [[ $needsUpdate == true ]]; then
    break;
  fi
done

# Nested
if [[ $needsUpdate != true ]]; then
  for entry in **/*; do
    processEntry "$entry"
    if [[ $needsUpdate == true ]]; then
      break;
    fi
  done
fi

if [[ $needsUpdate != true ]]; then
  echo "No update needed"
  exit 0
fi
echo "Updating version and build info"


buildPlist=${PROJECT_DIR}/${INFOPLIST_FILE}

# Increment build number
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$buildPlist")
buildNumber=$((0x$buildNumber))
buildNumber=$(($buildNumber + 1))
buildNumber=$(printf "%04X" $buildNumber)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$buildPlist"

echo -e "${git_release_version#*v}\n$buildNumber" > versions
thislooksfun
  • 1,049
  • 10
  • 23