I am on fire about Ruby. I always hated Objective-C’s weirdness (coming from C++), and read the most awesome idea recently – given the amazing power of modern computers (now pay attention, this is the good part): writing code in anything but the highest level, easiest language is… premature optimization!!! The instant I read that statement (sorry to the author, I can’t remember where), I knew it was true. Add that you can drop down from Ruby to lower level languages where you need extra speed, and that MacRuby and HotCoca are going to make it ridiculously easy to use Cocoa via Ruby and…
That’s it – I’m hooked. To get myself up to speed in Ruby and Cocoa, I’m re-writing the examples from Aaron Hillegass’s awesome book Cocoa Programming for Mac OS X. I’ll be writing about the intricacies of BDD in Ruby using Xcode, and I’ll be posting all the code, as well as custom file and project templates for Xcode.
If your file templates are missing from Xcode (I’m using 3.2.1), run the following commands in terminal, restart Xcode, and they will be there!
sudo mkdir /Library/Application\ Support/Developer/Shared/Xcode/File\ Templates/
sudo mkdir /Library/Application\ Support/Developer/3.0/Xcode/File\ Templates/
svn co http://svn.macosforge.org/repository/ruby/MacRuby/
trunk/misc/xcode-templates/File%20Templates /Library/Application\ Support/Developer/Shared/Xcode/File\ Templates/
sudo cp -r /Library/Application\ Support/Developer/Shared/Xcode/File\ Templates/* /Library/Application\ Support/Developer/3.0/Xcode/File\ Templates/
Have you wanted to automate your Xcode workflow, but couldn’t figure out exactly how to work the AppleScript commands? Well, here’s a whole object-oriented library of handlers for ya!
(Sorry I haven’t figured out a better way to format AppleScript code in a blog; any ideas, please let me know)
on target_object(the_target)
script target_obj
property target_ref : ""
to add_source_file(the_file)
tell application "Xcode"
add the_file to (get compile sources phase of target_ref)
end tell
end add_source_file
on set_header_search_paths_for_all_configurations(the_path)
tell application "Xcode"
(* configuration is Debug or Release *)
set value of build setting "HEADER_SEARCH_PATHS" of build configurations of target_ref to the_path
end tell
end set_header_search_paths_for_all_configurations
to add_run_script_phase(the_name, the_script)
tell application "Xcode"
tell target_ref
return make new run script phase with properties {name:the_name, shell path:"/bin/sh", shell script:the_script}
end tell
end tell
end add_run_script_phase
end script
tell application "Xcode"
set target_obj's target_ref to the_target
end tell
return target_obj
end target_object
on group_object(the_group)
script group_obj
property group_ref : ""
to add_file(the_path, the_file_name)
tell application "Xcode"
tell group_ref
return make new file reference with properties {full path:the_path, name:the_file_name}
end tell
end tell
end add_file
end script
set group_obj's group_ref to the_group
return group_obj
end group_object
on active_project()
script project_obj
property project_ref : ""
to add_group(group_name)
tell application "Xcode"
tell project_ref
-- Get project directory
set project_dir to project directory
-- Create new folder for group's files
tell application "Finder"
set file_lib to load script alias "Macintosh HD:Users:sean:Library:Scripts:My Library:Files.scpt"
make new folder at (file_lib's posix_string_to_hfs_file(project_dir)) with properties {name:group_name}
end tell
-- Create new group
tell root group
return my group_object(make new group with properties {name:group_name, path type:project relative, path:group_name} at beginning)
end tell
end tell
end tell
end add_group
to make_new_shell_tool_target(target_name)
tell application "Xcode"
tell project_ref
set unit_test_template to target template "BSD/Shell Tool"
return my target_object(make new target at end of targets with data unit_test_template with properties {name:target_name})
end tell
end tell
end make_new_shell_tool_target
end script
tell application "Xcode"
set project_obj's project_ref to project of active project document
end tell
return project_obj
end active_project
You can also download the scpt file here.
And here’s a script that uses the handlers.
My workflow in Xcode is typical TDD – write a test, run the tests, write some code, run the tests… I set up a “Run Script” build phase, so that every time I build the tests, they are automatically run. The only problem is that the “Run Script” happens whether the build was successful or not. So, I try to build and fail, and then the same exact tests that I already ran, automatically run again – while I sit there and wait.
The solution turned out to be very simple. “Run Script” build phases in Xcode can have input files and output files. The phase is only run if some input files were more recently modified than some output files. Thus, if we set the input file to be the test executable, and the output files to be all the source files, then the tests will only run if the build is successful. It will look something like this when set up:

I was working on an Applescript Studio project with Objective-C mixed in, when all of a sudden, without any warning or intervention from me, the debugger ceased to load when I ran the app (in debug mode). Hmmm, little gremlins, hackers from Russia???
After searching the lists and checking off the usual culprits (debug configuration, yes; debug symbols enabled, ditto), I went to my old-standby-last-resort-when-xcode-starts-acting-crazy-for-no-reason move: I created a fresh project, confirmed that it was working normally, and then opened every setting window in both projects to see what settings are different, and so could possible be contributing to the hiccup.
When I opened the settings for the executable (I was desparate), I noticed that the “Debugging->When using…” drop-down was different. Now, to you or I, “when using” seems to say “these setting only apply when you happen to be using X.” Yet on planet Xcode, it seems to translate into “when you change X, you’re project will begin to work in a different way then usual, even if you do not change any o the settings in this window.” I selected “GDB” from the drop-down, and all my problems were solved… maybe that’s a bit dramatic.
Anyway, my breakpoints are again working (in both the Obj-C and applescript code). Yay!
I got tired of manually adding them, so here’s a little script…
set list_lib to load script alias “Path:To:List Utilities.scpt”
tell application “Xcode”
– Get file name of front-most document (via window)
set header_file_path to associated file name of window 1
– Go back and get the source file using the name
set header_file to first source document whose path is header_file_path
– Turn the file into a list of lines to
— find the insertion point for the inclusion guard
set lines_of_header to paragraphs of text of header_file
– Create the inclusion guard based on the file name
set inclusion_guard_token to first word of (get name of header_file)
& “_H_INCLUDED_”
set guard_start to “\n#ifndef “ & inclusion_guard_token
& “\n” & “#define “ & inclusion_guard_token & “\n”
– Insert the start of the guard
set lines_of_header to list_lib‘s list_insert(lines_of_header, guard_start, 9)
– Insert the end of the guard
set end of lines_of_header to “\n\n#endif”
– Set the revised contents of the file
set text of header_file to (lines_of_header as text)
end tell
List Utilities.scpt (code from AppleScript: The Definitive Guide, 2nd Edition:
on find_in_list(what, L)
repeat with i from 1 to count L
if item i of L is what then
return (a reference to item i of L)
end if
end repeat
return
end find_in_list
on list_insert(L, what, ix)
if ix = 1 then
return {what} & L
else
return {item 1 of L} & list_insert(rest of L, what, ix - 1)
end if
end list_insert
In my quest to automate all repetitive tasks, setting up a Xcode project for unit testing (using Boost.Test) is in my crosshairs. I’m in the process of adopting BDD, so you’ll see that terminology mixed with that of unit testing.
Plan
- List out all the manual steps. I started with the process described by Richard Dingwall in a very nice tutorial here.
- Implement task in Applescript
Manual process
Ok, so here’s what I do when I start a new C++ project…
- File->New Project…
- Add Group “Features” @ [project-directory]/Features
- Add Group “Specifications” @ [project-directory]/Specifications
- Add Group “Boost Test Common” @ [project-directory]/Boost Test Common
- Add XcodeLogFormatter.h (allows Boost.Test reports to be properly displayed in Xcode build results; see below) and run_tests.cpp (Boost.Test’s main function) to Boost Test Common
- Add Features Target
- Add->”New Target”
- BSD->Shell Tool
- Name: Features
- Add run_tests.cpp to “Compile Sources”
- Add “/usr/local/include/boost-1_38/” to “Header Search Paths” of Features Target
- Add->New Build Phase->New Run Script Build Phase
- Script: “${TARGET_BUILD_DIR}/${EXECUTABLE_NAME}” (this causes the Features to be run every time the target is built)
- Add Specification (i.e. unit tests) Target: repeat step 6 above for “Specifications”
- Add->Existing Files->[boost unit test library]; check Targets: “Features” and “Specifications”
Applescript
Finally done! Here’s the script and Xcode library script.
Not Doing Now
As I work, I make a list of things that would be nice to have, but are not important enough to think about right now and would take me away from my task:
- Put Boost Test Common files in a commonly accessible include folder
Source Code
XcodeLogFormatter.h (from http://richarddingwall.name/category/xcode/):
#include <boost/test/included/unit_test.hpp>
struct xcode_log_formatter :
public boost::unit_test::output::compiler_log_formatter
{
// Produces an Xcode-friendly message prefix.
void print_prefix(std::ostream& output,
boost::unit_test::const_string file_name, std::size_t line
{
output << file_name << ‘:’ << line << “: “;
}
};
run_tests.cpp (from http://richarddingwall.name/category/xcode/):
#define BOOST_AUTO_TEST_MAIN
#include <boost/test/unit_test.hpp>
#include “XcodeLogFormatter.h”
// Set up the unit test framework to use an xcode-friendly log formatter.
struct xcode_config {
xcode_config() {
boost::unit_test::unit_test_log.set_formatter( new xcode_log_formatter );
}
~xcode_config() {}
};
// Call our fixture.
BOOST_GLOBAL_FIXTURE(xcode_config);
(updated) Check out the finished AppleScripts:
1. main script @ http://seandenigris.com/blog/files/Make%20new%20C++%20project.scpt
2. Xcode handler library script @ http://seandenigris.com/blog/files/Xcode.scpt
@#$%#$^$%%$#^#$%#%!!!!!!!!
Love Boost, hate Boost. You can’t imagine how much time I spent on this.
Ok, much better. If you install the Boost libraries to an alternate directory (other than usr/local/lib), you will have to jump through hoops to link to the shared versions.
Option #1: Keep Boost in /usr/local/libs. To link to shared libs, drag them into Xcode. Done. (Tip: put a symlink in a visible directory if you can’t see /usr/local/libs)
Option #2: Keep Boost wherever you want and copy the dynamic libs you use into the executable’s directory before linking. This approach is not recommended. Your program will only work when the current directory is the directory of the executable.
Recent Comments