tags:

views:

4990

answers:

8

Hello!

I would like to insert the current Subversion revision number (as reported by svnversion) into my Xcode project. I managed to insert the revision number into the Info.plist in the $PROJECT_DIR, but this is not a good solution, since the file is versioned. I tried to insert the revision into the Info.plist in the build directory, but then I get an error during the code signing phase (this is an iPhone application).

Is there a simple way to get the revision number into the application using some build files, so that the changing revision does not change versioned files? I thought maybe I could create a temporary source file that would link with the others and provide a function to get the revision number. But I don’t know how to do that.

I am not much interested in other solutions, ie. the agvtool. All I want is the revision number available to the application, without too much magic.


Update: Now this was easy. Thanks, svinto, this did not occur to me. All you have to do is create a new Run Script phase that does something like this:

REV=`svnversion -n`
echo "#define kRevisionNumber @\"$REV\"" > ${PROJECT_DIR}/revision.h

Then add the revision.h to the project, include wherever you like and tell Subversion to ignore it.

+2  A: 

See this question:

http://stackoverflow.com/questions/111436/how-can-i-get-the-svn-revision-number-in-php

svinto
That question doesn't have a definitive answer, and SVN keyword substitution is not a good solution in this case — he needs a way to update the processed property list file, not the versioned one.
Quinn Taylor
+2  A: 

this is a link to a three methods of inserting revision control numbers into an Xcode project. For Subversion, Bazaar and Git.

The Git one is mine :-)

Abizern
+5  A: 

For posterity, I did something similar to zoul for iPhone applications, by adding a revision.h to my project, then adding the following as a Run Script build phase:

REV=`/usr/bin/svnversion -nc ${PROJECT_DIR} | /usr/bin/sed -e 's/^[^:]*://;s/[A-Za-z]//'`
echo "#define kRevisionNumber @\"$REV\"" > ${PROJECT_DIR}/revision.h

I did this to grab a simple revision number, as opposed to the more detailed string that svnversion produces in zoul's solution.

For Mac applications, I based my approach on this post, and instead created a buildnumber.xcconfig file. Under the build settings for the target, I changed the Based On value in the lower-right-hand corner of the dialog to buildnumber.xcconfig. Within the Info.plist, I edited the following lines:

<key>CFBundleVersion</key>
<string>${BUILD_NUMBER}</string>
<key>CFBundleShortVersionString</key>
<string>Version 1.0</string>

So that my About dialog would display a version string similar to Version 1.0 (1234), where 1234 is the Subversion revision number. Finally, I created a Run Script build phase with the following code:

REV=`/usr/bin/svnversion -nc ${PROJECT_DIR} | /usr/bin/sed -e 's/^[^:]*://;s/[A-Za-z]//'`
echo "BUILD_NUMBER = $REV" > ${PROJECT_DIR}/buildnumber.xcconfig

This may not be the cleanest way, as it requires a clean cycle before building for the new revision to take hold in the application, but it works.

Brad Larson
+1  A: 
# 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.
#
# modified by JM Marino to change only [BUILD] motif
# into CFBundleGetInfoString key.
#
# HOW TO USE IT: just add [BUILD] motif to your Info.plist key :
#    CFBundleVersion
#
# EXAMPLE: version 1.3.0 copyright 2003-2009 by JM Marino
# with [BUILD] into CFBundleVersion key

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 $REV = `/usr/bin/svnversion -n ./`;
my $INFO = "$ENV{BUILT_PRODUCTS_DIR}/$ENV{WRAPPER_NAME}/Contents/Info.plist";

my $version = $REV;

# (Match the last group of digits without optional letter M | S):
($version =~ m/(\d+)[MS]*$/) && ($version = "" . $1);

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>.+)\[BUILD\](<\/string>)/$1$version$2/;
$info =~ s/([\t ]+<key>CFBundleVersion<\/key>\n[\t ]+<string>)\[BUILD\](<\/string>)/$1$version$2/;

open(FH, ">$INFO") or die "$0: $INFO: $!";
print FH $info;
close(FH);
This is nice, but changes the versioned files, if I am not mistaken.
zoul
+7  A: 

There's a much simpler solution: using PlistBuddy, included at /usr/libexec/PlistBuddy in Leopard. See my answer to a related SO question for details.

PlistBuddy can be used in a Run Script build phase from within Xcode, and can be used to only affect the processed plist file. Just put it after the Copy Resources phase, and you don't even have to clean the target for it to run each time. You don't even have to print the value to a header file and make SVN ignore it, either.

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion `svnversion -n`" \ 
${TARGET_BUILD_DIR}/${INFOPLIST_PATH}

Assuming you add the build phase before code signing occurs, your plist should be signed with the substituted value.

Quinn Taylor
Nice! This is very close to the solution I've come up with. The only problem with this approach is that code signing takes place only every other build. That's because Xcode seems to check dependencies **before** doing anything. It assumes nothing changed in the project (not knowing of the shell script), so there will be no need for code signing and decides early to drop it. That's why I added code to set the modification time of the resulting file to 1970. This makes Xcode see that something is out of date and it has to do the code signing.
Nikolai Ruhe
You could replace the hardcoded /path/to/Info.plist with "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}" (including the double quotes) so you can reuse the script in other projects.
Nikolai Ruhe
Definitely, using the Xcode path is a great idea. The dependency checking is an interesting problem — however, if nothing has changed to trigger a rebuild, is there a case where you can get an improperly-signed result? (For example, the CFBundleVersion in the plist changes, but the code signing doesn't?) I suppose this could happen if you do an `svn update` and the base revision increments, but none of the resources for a particular target have changed. This could always be solved by a clean and build, or manually touching the plist file, in which case the next build would work fine. Hmmm....
Quinn Taylor
Along with the build version I am putting the build date and time into the Info.plist file. So for me, every time I build, the Info.plist changes and has to be signed. I'm also calculating and storing the repository path to trace builds from switched working copies.
Nikolai Ruhe
Ah, that totally makes sense. Those are pretty cool ideas for making it much easier to track down the exact build that's causing the problem. :-)
Quinn Taylor
+4  A: 

As a new user to Stack Overflow, I can't comment on Quinn's post, but I have a small change to make his solution a bit more accurate if you're using a SVN repository that has multiple projects going on at once.

Using his approach, the svnversion number that is returned is the last check-in for the entire repository, not necessarily your code base. This tweak allows for the update to be specific to your code base.

REV=`svnversion -nc | /usr/bin/sed -e 's/^[^:]*://;s/[A-Za-z]//'`
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $REV" "${TARGET_BUILD_DIR}"/${INFOPLIST_PATH}

Using the -c flag will gather the last commit done in the active branch/tag/trunk for your codebase in the form of :, then chop off the bits you don't want to store as the Revision number.

Also, notice the double quotes around the ${TARGET_BUILD_DIR}. Those are needed for users that decide to place their project in a directory structure with spaces in the name.

Hope this helps others!

George Walters II
+3  A: 

I found this page when trying to do a similar thing for my iPhone app and thought it might be helpful to share the code I decided on. I was trying to have a base version number set in my Target Info (for example 0.9.5) but then append my SVN revision number at the end of it. I needed this stored in CFBundleVersion so that AdHoc users would be able to update via iTunes even if I didn't remember to rev the version number in my Target Info pane. That's why I couldn't use the "revision.h" method which otherwise worked beautifully. Here's the final code I settled on which I've placed as a Run Script phase just after the "Copy Bundle Resources" build phase:

BASEVERNUM=`/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${INFOPLIST_FILE}" | sed 's/,/, /g'`
REV=`svnversion -n`
SVNDATE=`LC_ALL=C svn info | awk '/^Last Changed Date:/ {print $4,$5}'`
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BASEVERNUM.$REV" "${TARGET_BUILD_DIR}"/${INFOPLIST_PATH}
/usr/libexec/PlistBuddy -c "Set :BuildDateString $SVNDATE" "${TARGET_BUILD_DIR}"/${INFOPLIST_PATH}

It should append the results of svnversion to the end of whatever is set in the base Info.plist as the version. This way you can have something like 0.9.5 in your info plist and still have the .189 revision number appended at the end, giving a final version number of 0.9.5.189

Hope this helps someone else!

Brian Robbins
This is exactly what I was wanting to do; my only minor change was to use REV=`svnversion -nc | /usr/bin/sed -e 's/^[^:]*://;s/[A-Za-z]//'` from another good answer (George Walters II).
petert
A: 

Another version, written in the Apple Script. Regexp for the previousValue could be changed, currently it supports only versions in XX.XX.XX format (major, minor, svn rev).

Run by /usr/bin/osascript

set myVersion to do shell script "svn info | grep \"^Revision:\""
set myVersion to do shell script "echo " & quoted form of myVersion & "| sed 's/Revision: \\([0-9]\\)/\\1/'" as string

set myFile to do shell script "echo ${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/"
set theOutputFolder to myFile as string
set thePListPath to POSIX path of (theOutputFolder & "Info.plist")
tell application "System Events"
   tell property list file thePListPath
      tell contents
         set previousValue to value of property list item "CFBundleVersion"

         set previousValue to do shell script "echo " & quoted form of previousValue & "| sed 's/\\([0-9]*\\.[0-9]*\\)\\(\\.[0-9]*\\)*/\\1/'" as string

         set value of property list item "CFBundleVersion" to (previousValue & "." & myVersion)
      end tell
   end tell
end tell