Automatic Build Sub-Versioning in Xcode

August 14th, 2005
Editorial note: This entry describes at length the process I went through to achieve the “perfect” script for determining and inserting the Subversion revision number into my builds. If you’d rather skip the details and jump to the copy/pasteable script, click here.

Around the time I first tried Subversion, I noticed an interesting mailing list post from Axel Andersson, which gave great script for using Subversion’s “revision number” as the “build number” for an Xcode project. (Note the script in the above link is unusable, because the mailing list archive stripped out some meaningful variable expansions).

For those of you aren’t familiar with Subversion, it uses a simple system of versioning, in which every commit to the repository advances the “revision number” by one. So you start off at 1 and every change you make bumps the digit. (Coming from a CVS mindset, this makes me feel a little oogy, because I’m used to committing files one at a time, even though they’re all part of a single “intent,” but I’m getting over it).

The Subversion revision number is a great candidate for the “build number” that goes next to your application’s version in the standard about box. Instead of an arbitrary number that confirms to you and users that one version is indeed later than another, this number allows you to jump back with fair certainty to the exact sources that constituted a user’s build, even if you were too lazy to go out of your way to introduce a tag corresponding to that instant.

Axel’s technique is fantastic because it takes all the work and worry out of it. Every time you build, grab the latest revision number from Subversion and inject it into the Info.plist file for the target. It took me a while to notice a subtle problem with his script, however. The number that is generated using his example (on my machine, anyway) is not reliably advanced. It doesn’t pick up the “latest” revision, it only picks up the revision of the root directory. In my current repository, the latest revision is “10,” yet the method described by Axel fails to detect this:

% svn info | grep “^Revision:”
Revision: 1
%

I can’t figure out when or if this root directory’s revision ID will change, but it certainly doesn’t change on every commit. What we want to know is, what was the revision number caused by the last checkin?

After tinkering with svn info, I discovered to my dismay that there is no option for simply spitting out the latest revision number. It depends on receiving a specific entity to report information about. In fact, if you point it at the repository itself (not the checked out copy), then it will in fact report, among other things, the latest revision number. So once you know the repository URL, you can get the latest revision number by doing something like this:

% svn info file:///Users/daniel/Sources/SVNROOT | grep “^Revision”
Revision: 10
%

But argh!, this will require that I dynamically figure out the repository address at compile time. I don’t want to hardcode it. I will have to use svn info on the root directory to obtain the repository, and then svn info again on the repository URL to obtain the revision. Too many steps! Poking around the web a little bit, I discover a hopeful lead with an example that uses the svn log command. It involves passing the –revision parameter and special revision tag “HEAD”. I tinker with this a bit and come up with something workable:

% svn log -q –incremental –revision HEAD | tail -n 1
r10 | daniel | 2005-08-14 12:25:56 -0400 (Sun, 14 Aug 2005)
%

I can get that “r10″ at the beginning and parse it! Too bad it’s not the same format as Axel’s original post, though. Wait a minute – what happens if I pass the –revision parameter to svn info?

% svn info –revision HEAD | grep “^Revision: ”
Revision: 10
%

Success! Well, I thought so. Several reader comments below have convinced me to switch to svnversion. I was especially swayed by Axel’s observation that my technique won’t work as expected for repositories checked out to a specific revision number. To test this theory, I check out my project ot revision 5, and try my technique:

% svn info –revision HEAD | grep “^Revision: ”
Revision: 10
% svnversion ./
5
%

OK! I’m ready to play the svnversion game. But svnversion gets tricky when there’s any activity at all in the checked out repository. Going back to my working repository, I get output like this:

% svnversion ./
1:10M
%

Because some files in my directory have not been modified since revision 1, some were modified as late as revision 10, and some are modified but not yet checked in, I get this complex shorthand. I’m really happy with just using the right-most number, as Axel suggested in the comments area, but I decided that I might like to have the “M” too. It’s not usual to ship products with letters in the build version field, but then again, it’s not (shouldn’t be) usual to ship products with locally modified sources. I figure this might be a nice reminder and reality check. If somebody does get their hands on a version that has an “M” in it, I’ll know that it was a pre-release or one-off and I shouldn’t be too concerned about bugs that might only occur in that version.

I decide to use a Perl regular expression to parse out the desired information. I’m a regular expression “long time newbie” so I always have to pull up some kind of reference while I work with them. I’m sure I don’t do things the best way, but I get the job done and call it a day. This is what I’ve come up with as a pattern for stripping the right-most digit, as well as the “M” (or “S”) tag that might also be appended to the digits:

% svnversion . | perl -p -e “s/([\d]*:)(\d+[M|S]*).*/\$2/”
10M
%


Looks good. Now I can just plug that minor adjustment into Axel’s original script. For anybody interested in applying this to their own Subversion/Xcode project, the modified script is as follows:

# Xcode auto-versioning script for Subversion
# by Axel Andersson, modified by Daniel Jalkut to add
# "--revision HEAD" to the svn info line, which allows
# the latest revision to always be used.
        
use strict;
        
die "$0: Must be run from Xcode" unless $ENV{"BUILT_PRODUCTS_DIR"};
        
# Get the current subversion revision number and use it to set the CFBundleVersion value
my $REV = `/usr/local/bin/svnversion -n ./`;
my $INFO = "$ENV{BUILT_PRODUCTS_DIR}/$ENV{WRAPPER_NAME}/Contents/Info.plist";
        
my $version = $REV;

# (Match the last group of digits and optional letter M/S):

# ugly yet functional (barely) regex by Daniel Jalkut:
#$version =~ s/([\d]*:)(\d+[M|S]*).*/$2/;

# better yet still functional regex via Kevin "Regex Nerd" Ballard
($version =~ m/\d+[MS]*$/) && ($version = $&);

die "$0: No Subversion revision found" unless $version;
        
open(FH, "$INFO") or die "$0: $INFO: $!";
my $info = join("", <FH>);
close(FH);
        
$info =~ s/([\t ]+<key>CFBundleVersion<\/key>\n[\t ]+<string>).*?(<\/string>)/$1$version$2/;
        
open(FH, ">$INFO") or die "$0: $INFO: $!";
print FH $info;
close(FH);

To add this script to your build process, you simply create a new “Shell Script Phase” at the end of your target’s build phases, and copy & paste the contents from above into that phase. The script will edit your freshly-copied Info.plist file in place to reflect your latest Subversion revision.

Make sure you specify Perl as the shell for the script, I use “/usr/bin/perl -w” on my machine.

If any Subversion nerds stumble upon this entry and can suggest a better way of obtaining the latest revision for the project, please let me know!

38 Responses to “Automatic Build Sub-Versioning in Xcode”

  1. Steve Says:

    Have a look at svnversion:
    http://svnbook.red-bean.com/en/1.1/re57.html

    This grabs the latest revision number in your working folder, not the server, so you’ll need to make sure you run ‘svn update’ before the build.

    Also as an FYI take a look at:
    http://subversion.tigris.org/servlets/ReadMsg?list=users&msgNo=34387

    I’m not sure I agree with Ben, but it is worth considering.

  2. August Mueller Says:

    There’s also “svnversion -n” if you didn’t want to mess around with the greps n’ stuff.

  3. Daniel Jalkut Says:

    Thanks – I didn’t know about this command. The output format seems a little harder to predict and parse, though. I can’t figure out how to make svnversion spit out an equivalent to “last committed version,” without the complex “local modified” information also encoded.

    I appreciate Ben’s linked concerns,but I don’t see much in that snippet to really back up what his position is. If he’s just warning that “r3999″ might be a GM 1.0 product while r4000 might be a risky alpha, then I agree. I wouldn’t suggest using revision numbers as a replacement for a versioning convention.

  4. Axel Andersson Says:

    Thanks for the article, Daniel. I hadn’t noted this issue myself, because I build nightlies and releases from a script that always checks out a clean copy, and then svn info produces the correct revision number.

    A problem with using –revision HEAD for svn info is that it disregards the actual version number of the working copy. This will then fail if an earlier revision is checked out, for instance via a tag or a branch. It will also consult the remote repository, which may be a problem if it’s on a network.

    I haven’t tested it, but using svnversion and stripping everything but the right-most revision number sounds like the way to go.

  5. Daniel Jalkut Says:

    Thanks, Axel. I didn’t think of the possibility of wanting to build from a non-HEAD. I will update the script to use svnversion and parse out the right-most value!

  6. Pete Yandell Says:

    This is a really useful little script. Thanks for saving me the trouble of working out how to do it for myself!

  7. Ross Patterson Says:

    “I appreciate Ben’s linked concerns,but I don’t see much in that snippet to really back up what his position is. If he’s just warning that “r3999″ might be a GM 1.0 product while r4000 might be a risky alpha, then I agree. I wouldn’t suggest using revision numbers as a replacement for a versioning convention.”

    That’s his point entirely – that revision numbers are not a replacement for version numbers, because in Subversion the entire repository has one revision number, including all the branches. Likewise that revision numbers are not a proxy for “code maturity”, because “release 2.4″ might be revision 2783 and “alpha-test pre-release 3.6″ might be revision 4207.

  8. Ross Patterson Says:

    “The output format seems a little harder to predict and parse, though. I can’t figure out how to make svnversion spit out an equivalent to “last committed version,” without the complex “local modified” information also encoded.”

    The svnversion output format is quite simple and easy to predict and parse if you know the rules (or have read subversion/svnversion/main.c). In BNF, it is ‘ [ ":" ] [ "M" ] [ "S" ]‘ The “:max” part is omitted if the minimum and maximum revision numbers are the same. The “M” indicates that there are modified (i.e. uncommitted) files, and the “S” indicates that there are switched files (although I don’t understand why that’s interesting).

    You really shouldn’t be parsing the svnversion output, though, but rather should be using the entire string. All 5 components (well, maybe not “S”?) are important parts of identifying a “build number”. Think about it – is there a difference between revision 35 and a mix of code from revisions 24, 27 and 35? Sure there is, and the difference is just as important as the difference between revision 34 and revision 35. Likewise the difference between revision 35 and revision 35 with uncommitted changes.

    Anyway, thanks for the article. We’re doing something similar using Ant and Subversion and substituting the “svnversion -n .” output into some Java source as our build number.

  9. Ross Patterson Says:

    “In BNF, it is ‘ [ “:” ] [ “M” ] [ “S” ]’”

    OK, the blog software chewed that up. What I tried to write was “… `minimum_revision [ ":" maximum_revision ] [ "M" ] [ "S" ]‘”.

  10. Ben Says:

    I really like this and I’ve made a tiny modification to make it work with a system similar to one Wil Shipley posted on his blog a while back.

    I’ve added the string (v0000) to my CFBundleVersion string and changed the line:
    #$info =~ s/([\t ]+CFBundleVersion\n[\t ]+).*?()/$1$version$2/;

    to:
    $info =~ s/v0000/v$version/;

    so that my version is reported as:

    major.minor (revision)
    1.3 (v2345)

    which gives the best of both worlds!

  11. Red Sweater Blog » Blog Archive » Destroy Xcode Tedium Says:

    [...] Update Now that you’ve got your marketing version in order, why not integrate Subversion’s revision number into your bundle version? [...]

  12. Jean-Francois Roy Says:

    You may want to use $INFOPLIST_PATH insead of hardcoding the Info.plist file’s path. As it is, the scrpt will fail to work for frameworks.

    So instead of doing

    my $INFO = “$ENV{BUILT_PRODUCTS_DIR}/$ENV{WRAPPER_NAME}/Contents/Info.plist”;

    you need to do

    my $INFO = “$ENV{BUILT_PRODUCTS_DIR}/$ENV{INFOPLIST_PATH}”;

  13. Daniel Jalkut Says:

    Great idea, Jean-Francois! I didn’t know about that particular variable. My knowledge of such things has been slow in coming :)

  14. Jean-Francois Roy Says:

    So is mine, but there’s an easy way to list what’s available to you. In some project, add a shell script phase with a dummy script (or use an existing project that has one) and build the project.

    Then, display the Build Results window (Command-Shift-B by default) and enable the build transcript pane (square button with what looks like lines of text).

    You’ll see that prior to executing a shell script, Xcode will dump all the environment variables it sets. You can copy and paste that to some text document and look over them for example. This is great because not only are you getting a listing, but also sample values, which gives you a much better idea what those environment variables stand for.

    Alternatively, you can have a script do printenv, which will echo all existing environment variables within the context of that shell script.

  15. Daniel Jalkut Says:

    That’s a good tip, thanks. The only caveat I’d add is that Apple may not endorse all of those variables, so it seems that using any that you can’t also find in Apple’s documentation could be risky if you hope for scripts to keep working in future Xcode releases.

  16. Per von Zweigbergk Says:

    Seems your blog software ate some \ characters in the actual script, so I’m getting errors when running it.

    With a lot of help from my friends (I don’t quite know perl or regexes properly), I managed to get it running after a few modifications were made.

    These are the three modified lines:

    my $REV = `/sw/bin/svnversion -n ./`;
    my $INFO = “$ENV{BUILT_PRODUCTS_DIR}/$ENV{WRAPPER_NAME}/Contents/Info.plist”;

    $info =~ s/([\t ]+CFBundleVersion\n[\t ]+).*?()/$1$version$2/;

    I just hope the comments board doesn’t eat the backslashes. :D

  17. Per von Zweigbergk Says:

    Oops. The my $INFO… line remains the same. However, the following line needs changing:

    $version =~ s/([\d]*:)(\d+[M|S]*).*/$2/;

  18. Daniel Jalkut Says:

    Thanks for catching this, Per! I know it worked right when I first posted this entry, but maybe those backslashes were a casuality of the WP 2.0 upgrade (!?). Anyway they’re back now!

  19. Per von Zweigbergk Says:

    Nope, Daniel, you missed the backslashes in the $info =~ line.

  20. tacow » Subversion on Mac OS X Says:

    [...] This is based on a post at the Red Sweater Blog. With it, you can automatically use the Subversion revision number as your project’s build number. [...]

  21. dave glasser Says:

    Yeah, if you do have a mixed-revision working copy, that’s probably information worth knowing rather than hiding! If you have a mixed-revision working copy, just do an “svn up” or “svn up -r1234″ at the top to get everything back to one revision.

  22. Red Sweater Blog - Best. Template. Ever. Says:

    [...] Automatically sets the CFBundleVersion to the subversion build number through a script build phase. [...]

  23. Michael Says:

    Hi all, i try the script and it works great but the result is the revision number with a “M” behind! How to edit out this?

  24. Daniel Jalkut Says:

    Hi Michael – it’s just a question of tweaking the regular expression part of the script to filter out anything non-numeric from the end. But since I wrote this article some issues have been raised about whether it’s a good idea to use Subversion for the CFBundleVersion. Check out the comments from rentzsch in my article here. I pretty much agree with that analysis and will probably move away from this particular solution.

  25. taybin.com » I love it when a plan comes together Says:

    [...] some help from Daniel Jalkut, Dave Dribin, Chris Hanson, and of course Andy Matuschak’s Sparkle and Reinvented [...]

  26. JM Marino Says:

    Good work.

    I propose a little variant here

  27. Daniel Jalkut Says:

    Hi JM – that URL didn’t work for me … if you post a working one I’ll fix it and it will be like it never happened :)

  28. Taybin Says:

    JM: Does yours do something different or is it just cleaned up? I’m not a perl guy.

    Daniel: you can just take the slash off the end of his link to fix it.

  29. Daniel Jalkut Says:

    Ah thanks. Fixed.

    Taybin: it looks like he’s added facility to template the injection of the number in the Info.plist. So instead of completely replacing the CFBundleVersion, it just injects the number into the part that is labeled “[BUILD]” … so you can have CFBundleVersion set to “123[BUILD]” and if your svn version is 45 it will come out in the resulting app as “12345″.

    By the way I realized I’m assuming JM is a male. Apologies if this is incorrect.

  30. Taybin Says:

    Oh, 123[BUILD] is much better than my setup. [yoink]

  31. Taybin Says:

    Possibly an even better way to get the SVN revision number: http://www.cocoabuilder.com/archive/message/cocoa/2005/8/3/143542

  32. Taybin Says:

    Here’s the system I’ve ended up using:

    My Info.plist contains this:

    CFBundleVersion
    ${CURRENT_PROJECT_VERSION}.$Revision$
    CFBundleGetInfoString
    ${CURRENT_PROJECT_VERSION} (build: $Revision$)
    CFBundleShortVersionString
    ${CURRENT_PROJECT_VERSION}

    My Update Version script is essentially reduced to:

    $info =~ s/\$Revision: (.*) \$/$1/g;

    The idea is that the revision number is considered a submicro version.

    This fixes the problem of stable branches because the macro version number will have priority. And the CFBundleShortVersionString has the marketing number which ignores the build version entirely.

    Any comments?

  33. JM Marino Says:

    Sorry, I was a bug in my last perl script !!

    Try this

  34. Kevin LaCoste Says:

    How are you handling this now? After reading through this and experimenting on my own it seems that Subversion revision numbers aren’t the answer after all. Now that you’ve got a handful of serious applications to speak for, have they forced you to come up with a better solution?

  35. Daniel Jalkut Says:

    For the time being I am still using SVN versions or some slight variation on that theme. For instance when I acquired MarsEdit, it was using a different scheme, but one that kept the number quite low. It was I believe the marketing version as a single number, so since I bought MarsEdit at 1.1.2, the bundle version was 112. So from here on the bundle version in MarsEdit is (112 + My Subversion Revision Number).

    The issues that have been raised with using the revision number have to do with complications that might arise from using the same repository to generate different versions of the app. For instance if I branch for 1.1.8 after releasing 1.2 of MarsEdit, then it will have a higher bundle version and therefore be perceived as “more up to date” than the 1.2.

    I have considered moving to a more robust scheme that takes things like this into consideration, but I haven’t bothered yet. Simply knowing about the limitation has been enough for me.

  36. Kevin LaCoste Says:

    I really like the idea of tying build numbers to something concrete/meaningful rather than just making an arbitrary decision to bump the number up now and then. I also believe that the Subversion link is a really good way to go.

    My current thinking is to have a script that runs a log command using a range. The script could then count the number of checkins between the last release and the current release and use that value to increment the build number. If you commit changes to the repository every time you fix a bug or add a new feature then you end up with a reasonably accurate build number for each new release. That Subversion command would be something like:

    svn log -r38:HEAD

    As an example, I have a new project in my repository and I can see that the revision number is at 40 while the actual number of checkins for the project is only 16.

    Any thoughts on this approach? Or better yet, any takers for whipping together the necessary scripts?

  37. Daniel Jalkut Says:

    It sounds like you’re using a single subversion repository for multiple projects, and trying to script it so that you get roughly what you would see if you had separate repos for all the projects.

    I personally use separate repos, though I’ve often considered consolidating them.

    Your post gave me an idea though, that would address the problem I alluded to with the “branches more up to date than later revisions” problem. You just have to build in “buffer space” in the versioning scheme between major releases, to accommodate any ongoing branch work that might happen.

    For instance if I ship 1.2 with svn revision #138, then when I go to work on 2.0, I might start the CFBundleVersion at “200 + svn”. Then I would have the flexibiliity to make a reasonably large number of releases on the branch, and change the versioning strategy for the branch to no longer use the subversion number.

    It loses a lot of elegance, but at least it does work around the problem. If branched releases are relatively uncommon, this seems like a reasonable workaround.

  38. mkbuild, an iPhone project build script – Whatever happened to Benjamin Ragheb? Says:

    [...] tags and automatically determines the next available build number. I briefly experimented with using the repository revision number, but decided it would be better to decouple the build numbering from the revision control system [...]

Comments are closed.

Follow the Conversation

Stay up-to-date by subscribing to the Comments RSS Feed for this entry.