Wednesday, September 16, 2009

Shell Script To List XCode Target Source Files

I develop a lot of c++ code on my mac for cross-platform applications, and I've often wanted a convenient way to export a list of source files belonging to a given target in an XCode project. That way when one of my colleagues wants to create a new project on another platform (e.g. Visual Studio or makefile) I can tell them which subset of the source tree to compile for a given application. I've looked several times for a solution but never had any luck. So finally last night I did what any programmer would do and I wrote my own shell script to parse this information out of the XCode pbxproj file.

I'm sharing it here because I'm not sure where else to post it, so if anyone has any suggestions let me know. I also had to learn a bit about awk and sed and I'm not a bash programmer by trade so I'm sure there is a more efficient/elegant/obfuscated way to do this - if anyone has suggestions please do share.

The pbxproj file is coded by long hex codes - so the trick was to find the section of the file that defines the names of the targets, then work forwards from there using the hex codes to find the right lines of the file and figuring out the right awk/sed commands to parse out the relevant information.

I've only tested this with XCode 3.1.3 and with only a single project, so I don't know how robust it is. It's at least a start. Thanks to http://formatmysourcecode.blogspot.com/ for their quick source code formatter.

#!/bin/bash

# Dan Swain dan.t.swain at gmail.com 9/15/09

# Bash script to list the source files that are compiled for each target
# in an xcode project file.
# If an argument is specified, it is taken to be the name of the
# xcode project - the program looks for argument.xcodeproj/project.pbxproj
# If no argument is specified, the program searches for a .xcodeproject
# directory in the current directory. This probably won't work
# if there is more than one .xcodeproj directory.

# given the name of the project, this is the location of the project file
if [ $1 ]
then
PBXFILE=$1.xcodeproj/project.pbxproj
else
XCODEPROJ=`find . -name '*.xcodeproj'`
PBXFILE=$XCODEPROJ/project.pbxproj
fi

# finds the lines defining the names of the targets
LINES=`sed -n '/isa = PBXNativeTarget/=' $PBXFILE`

# for each target
for LINE in $LINES
do
# the target name is on the previous line and is the second field
# delimited by *'s, we also remove a leading and trailing space
TARGETNAME=`awk 'NR=='$((LINE-1)) $PBXFILE | awk -F\* '{print $2}' | sed 's/^[ ]//;s/[ ]$//'`

echo
echo Source Files for Target $TARGETNAME:
echo ====================================

# this is the hex code corresponding to the sources for this target
SOURCES=`awk 'NR=='$((LINE+4)) $PBXFILE | awk '{print $1}'`

# find the line numbers containing the hex code for the sources
SOURCESLINE=`sed -n '/'$SOURCES'/=' $PBXFILE`
# there are two of these, so this leaves us with just the last one
for SL in $SOURCESLINE
do
: # just want the last one
done

# the first file is 4 lines below this
SL=$((SL+4))
# loop over the subsequent lines until the awk command fails
OK=0
while [ "$OK" = "0" ]
do
# the source file is the third field
FILE=`awk 'NR=='$SL $PBXFILE | awk '{print $3}'`
if [ $FILE ]
then
echo $FILE
else
OK=1
fi
# next line
SL=$((SL+1))
done
done

3 comments:

Anonymous said...

Thanks for your script I just used it successfully on a project here. We use Xcode 3.2.5 as far as I seen there were not problems in parsing the project file.

Dan said...

Great! Glad it could be a help. I did dabble a little with writing Python code to do this, too. If you're interested, I'd be glad to share.

FYI though: I stopped worrying about this stuff because I started using CMake to manage project files. I set source files up front and then let it generate the XCode project (or makefiles, or Visual Studio project, etc). It's fantastic, check it out!

Tj Fallon said...

This still works amazing, 5 years later. Note, your Compile Sources Phase must be #3 in the list, but if you make sure that's the case, this works like a charm!