Insert Subversion revision number in Xcode
Asked Answered
E

7

25

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.

Erst answered 16/12, 2008 at 18:31 Comment(3)
See this question: https://mcmap.net/q/112305/-how-can-i-get-the-subversion-revision-number-in-phpVincevincelette
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.Decretory
You don't need to create a revision.h file and ignore it, you can modify the processed plist directly using a simple Apple tool. See my answer below.Decretory
D
37

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.

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

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

Decretory answered 30/6, 2009 at 5:38 Comment(9)
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.Jacquettajacquette
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.Jacquettajacquette
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....Decretory
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.Jacquettajacquette
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. :-)Decretory
Should ${TARGET_BUILD_DIR}/${INFOPLIST_PATH} be quoted?Syllepsis
I had to modify the svnversion call slightly to account for mixed-revision or modified repositories: svnversion | cut -f 2 -d : | grep -o \"[0-9]*\" the cut splits the reva:revb (taking b) and the grep removes any non-digitsMurillo
Sorry for not understanding fully, but are we supposed to copy and paste that script into a Build Phase Run Script, with the Shell being /usr/libexec/PlistBuddy? If so, I get a build error :(Conducive
Mine broke with svnversion saying 'svn: E200030: sqlite: callback requested query abort' when I upgraded to Xcode 5. Fixed with svn cleanup.Glutenous
O
10

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.

Outherod answered 5/1, 2009 at 22:50 Comment(1)
FYI - the PROJECT_DIR define didn't work on my project, but this code does: REV=`svnversion -nc | /usr/bin/sed -e 's/^[^:]*://;s/[A-Za-z]//'` echo "#define kRevisionNumber @\"$REV\"" > ${PROJECT_DIR}/revision.hDornick
W
8

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!

Wench answered 7/12, 2009 at 22:16 Comment(0)
D
8

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!

Dygall answered 13/1, 2010 at 0:35 Comment(1)
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).Jaggy
E
2
# 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);
Examinee answered 31/1, 2009 at 11:21 Comment(1)
This is nice, but changes the versioned files, if I am not mistaken.Erst
P
2

Since I can't comment on Quinn's answer:

if you're using the MacPorts svn client, it may be necessary to include the full path of the svnversion command, /opt/local/bin/svnversion in my case. I also added the -c option to svnversion.

Also, if you're only interested in the second version number of mixed revisions, add a 'cut' command to the invocation, like this:

echo -n "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}" \
| xargs -0 /usr/libexec/PlistBuddy -c "Set :CFBundleVersion `/opt/local/bin/svnversion -nc | cut -f2 -d:`"
Plasticine answered 20/12, 2012 at 13:48 Comment(0)
S
0

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
Stratus answered 14/1, 2010 at 10:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.