14 July 2008

Adding svn:externals to Your Project

Subversion provides an easy way to add references to other projects. These projects might provide frameworks or other resources that your project needs. Rather than copying these resources into your own repository and trying to manage revisions yourself, Subversion allows you to specify a link to the external project. This is done with the svn:externals directory property.

To add an external project to your own, first decide where you would like it to live. One option is to keep a directory outside of the trunk directory called externals or third-party (the name is up to you) and then add tagged versions of the external code to the directory as needed. Another option is to place the externals directory inside the trunk and then when you branch or the trunk, the correct version of the external project will travel to the branches directory. In any event, you first have to decide where you want the external project to reside within your own.

Next, use the svn propedit svn:externals <dir_name> on a directory that is versioned. The format of the property is destination_dir [-rversion] source_url. Since svn:externals can be a multiline property, it is best to use the svn propedit command.

Then, do an svn update and your done!

Example

I use two (so far) external projects in the Sidelight Project. I use the Xcode configuration files from the Google Toolbox for Mac and the Google Testing Framework called googletest for unit testing the C++ frameworks. I put these external frameworks in a directory called "externals" just under the "trunk" directory in the repository. An svn propget svn:externals on trunk reveals:

computer:sidelight username$ svn propget svn:externals trunk
externals/src/google-toolbox-for-mac/1.5/XcodeConfig  http://google-toolbox-for-mac.googlecode.com/svn/tags/google-toolbox-for-mac-1.5.1/XcodeConfig
externals/src/googletest/1.0 http://googletest.googlecode.com/svn/tags/release-1.0.1

In this case, I have put tagged versions of the external projects into externals since I want them to remain stable until I update them. Alternatively you could check out a particular version of the project's trunk by specifying the version in the property.

Adding links to external projects to your own project is a great alternative to copying the resources directly and trying to keep synchronized with the external project. Additionally, other developers can share the same properties without a need to configure their own machines. Subversion makes this easy with the svn:externals property.

09 July 2008

Frameworks Are Great (Part 3)

The Solution

It turns out that the problem I wrote about in part 1 and part 2 can be solved. In Mac OS 10.5 (Leopard), new features have been added to ld, the linker used at compile time, and dyld, the dynamic linker used at run time, to add arbitrary freedom in specifying the installed path of dynamically linked libraries.

@rpath

The solution is fairly straightforward. Instead of requiring a framework or dylib to have an install location relative to @loader_path, the path of the code requiring the framework to be loaded, or @executable_path, the path of the main executable, they can now have an install location relative to @rpath, the "run path" specified in the code requiring the framework to be loaded. This is explained pretty well in the dyld Release Notes.

The process of using @rpath requires two steps.

  1. The framework (or dylib) specifies its install directory to be relative to the "run path" (the "Dynamic Library Install Name" build setting in Xcode or the -install_name command line flag in ld). For example, the framework example below uses the name @rpath/LevelOne.framework/Versions/A/LevelOne as its "Dynamic Library Install Name." Xcode makes this a little easier by allowing you to specify the "Install Name" as "@rpath" and then it figures out the rest.
  2. The application (or plugin, or another framework) that loads the framework must specify its "run path." Specifying the "run path" is done in Xcode by specifying the "Runpath search paths" build setting or the -rpath option if using ld from the command line.

And that's it. It's pretty easy and almost limitless in flexibility.

Example

Everything is simpler with an example. Here's the scenario: I want to write a screen saver plugin called RpathSaver.saver that uses a framework called LevelTwo.framework. The plugin and the framework require loading a second framework called LevelOne.framework. I can't use an install path relative to the @executable_path, since that would require installing the screen saver plugin is in a known location (i.e. /System/Library/Screen Savers/. Bad form. I can't use an install path relative to the @loader_path since both the plugin and the framework are loaders of LevelOne.framework. As discussed in part 2 I have three bad options.

  1. Pull the executable code out of the two frameworks and put it in RpathSaver.plugin/Contents/MacOS with the rest of the executable code and specify their install path to be @loader_path.
  2. Put the frameworks in a global location like System/Library/Application Support.
  3. Statically link all of the source together.

But in Mac OS X 10.5, I can use the @rpath in the install path, and everything sorts itself out. Here's the solution:

  1. Set the runpath of the RpathSaver.saver plugin to be @loader_path/../Frameworks. That will let dyld search in the frameworks directory of the plugin for loadable code.
  2. Set the runpath of the LevelTwo.framework to be @loader_path/../../... That will let dyld search in the frameworks directory of the plugin for loadable code.
  3. Set the install directory of LevelTwo.framework to be @rpath/LevelTwo.framework/Versions/A/LevelTwo. At run time, dyld will replace the @rpath portion with the runpath specified in the loading code (in this case the plugin) and will find the framework in @loader_path/../Frameworks + LevelTwo.framework/Versions/A/LevelTwo.
  4. Set the install directory of LevelOne.framework to be @rpath/LevelOne.framework/Versions/A/LevelOne. At runtime, the @rpath will be replaced by the runpath of the loading code. In the case of the RpathSaver.saver plugin, it will load from this path: @loader_path/../Frameworks + LevelOne.framework/Versions/A/LevelOne. In the case of LevelTwo.framework, in will load from this path: @loader_path/../../../Frameworks + LevelOne.framework/Versions/A/LevelOne.

I'm enclosing the sample code here. It is the screen saver code that I discuss above. It simply displays some text from LevelOne.framework and LevelTwo.framework, which in turn uses LevelOne.framework to generate its own text. I hope you find it useful.

[Update 12 Aug 2008]

An alternative solution, for Tiger is explained by Jeff Johnson at Lap Cat Software. The post can be found here. The solution involves using install_name_tool.