Applescripting Xcode and Qt qmake: Adding a run script build phase
So I’ve got a problem. And no, it doesn’t involve lawyers. I know, I’m surprised about that as you are.
My problem is that I want Xcode to automatically run my unit tests after each debug build. The trouble is my .xcodeproj is generated from a Qt .pro file using qmake. Unfortunately, qmake doesn’t have the ability to create a Xcode run script phase with its QMAKE_POST_LINK directive.
Seriously, even their docs say so.
And this bug.
And this one too.
That means to have a run script build phase that executes my UnitTest++ unit tests I would need expand the Target, right click, add a build phase, and copy in the shell script to run from somewhere every single time I regenerated the .xcodeproj from the .pro.
I mean, come on, that’s like at least 20 seconds worth of work. Plus I have to right click. And I have to type! What next, should I write C++ code in my own blood?
Luckily, Xcode has a great deal of Applescript support which means all I had to do was devote an evening of Applescript coding while watching a basketball game in order to save myself 20 seconds.
Anyhow, here is what you need to do to create a new run script build phase in an Xcode project using Applescript. Note that this example is pretty heavily Qt based since that was my issue but the general idea could easily be used for any other kind of post build script. (Ex: Copy the binary to a network share, send an email, make a bacon sandwich. Not sure how to do that last one, but man that would be EPIC.)
First, from the command, generate your .xcodeproj from your Qt .pro using qmake.
lammi-mbp:Unix lammig$ qmake -spec macx-xcode
In my case I was using a file named scmserver.pro. The result of qmake was then scmserver.xcodeproj.
Then, open up the new .xcodeproj in Xcode.
lammi-mbp:Unix lammig$ open scmserver.xcodeproj/
Notice how the project that was generated doesn’t have a Unit Test phase.

To get that we need to run my Applescript. (createunittestsforxcode.scpt)
lammi-mbp:Unix lammig$ osascript createunittestsforxcode.scpt
If we look back now in Xcode we can see that through the magic of Applescript there is a new build phase.

Double clicking on the new build phase shows that it has all the stuff we want. Rock. And even it works when I compile. Double rock.

Now while I was writing this script I came to the conclusion that Applescript is really hard to Google on and that examples of scripting Xcode itself are fairly few and far between. I blame the first on the Applescript language being so English-like. I blame the second on the Internet.
So to help spread the knowledge in the whole “man ain’t the Internet grand let’s all have a big group hug” kind of way here is a walkthrough of the script to show how it all works.
First we start out by saying that Xcode is the application we want to play with and that we should use currently active project.
tell application "Xcode"
tell project of active project document
Next we need to build the full path to the executable that Xcode will build. We need to do this because in order to run unit tests we run the debug binary passing the parameter UnitTest on the command line. We can get all this information generically from the Xcode project so the Applescript can be reused as-is against all our different .xcodeproj’s.
set myExecPath to path of executable 1 -- This gets the .app bundle name
set myExecName to name of executable 1 -- This get the name of executable binary
set myStartupDirectory to startup directory of executable 1 -- This gets the startup directory
-- Concate them all together with the UnitTest parameter. This is the command to run the tests
set myUnitTestCmd to myStartupDirectory & "/" & myExecPath & "/Contents/MacOS/" & myExecName & " UnitTest"
The next operations will be going against the Xcode Target for our project, so we need to tell the Applescript to operate against that.
tell target 1
Now we need to write the shell script that will run the binary with the UnitTest parameter to run our tests.
-- Write the shell script to execute during our new run script phase
set myScript to "if [ $CONFIGURATION = \"Debug\" ]; then
echo
" & myUnitTestCmd & "
fi"
All that’s left is adding our shell script as a run script build phase to our target. To do this we just need a single line. This is the line you’ll want to use if you want to run a different script of your own. Just replace the contents of the myScript variable with whatever you want to run.
-- Add new run script phase with our shell script
set myBuildPhase to make new run script phase with properties {name:"Unit Tests", shell path:"/bin/sh", shell script:myScript}
The only thing left is to wrap up our tell blocks and we’re golden..
end tell
end tell
end tell
Finally, here’s the script in full, have fun Xcode’ing and Applescripting. Oh and if you can somehow make a script that makes a bacon sandwich, GPL that sucker for the sake of all humanity.
tell application "Xcode"
tell project of active project document
set myExecPath to path of executable 1 -- This gets the .app bundle name
set myExecName to name of executable 1 -- This get the name of executable binary
set myStartupDirectory to startup directory of executable 1 -- This gets the startup directory
-- Concate them all together with the UnitTest parameter. This is the command to run the tests
set myUnitTestCmd to myStartupDirectory & "/" & myExecPath & "/Contents/MacOS/" & myExecName & " UnitTest"
tell target 1
-- Write the shell script to execute during our new run script phase
set myScript to "if [ $CONFIGURATION = \"Debug\" ]; then
echo
" & myUnitTestCmd & "
fi"
-- Add new run script phase with our shell script
set myBuildPhase to make new run script phase with properties {name:"Unit Tests", shell path:"/bin/sh", shell script:myScript}
end tell
end tell
end tell
The last even remotely challenging issue I had to debug was a formatting problem in PowerPoint. I am so jealous you cannot possibly imagine.
Extra points for “Double Rock”.
—Chip
Debug PowerPoint? Who is this guy?
Chip is far better at this stuff than I’ll ever be. It’s a crime that that is the toughest tech problem he’s had to deal with lately.