May 17, 2007
Look, Ma. No framework!
When I first sat down to set up the project files for Sandvox, I knew that we wanted to support plugins. These are little bundles of Cocoa code and related resources that can be loaded into an application at runtime to extend its funcationality. A lot of Cocoa apps work this way. Watson did. So do Mail, iPhoto, iDVD, even Calculator. If an app keeps its plugins in the standard location (Contents/PlugIns) you can even browse them and activate/deactivate, add or remove them via Finder.
If, in Finder, you do Get Info on Sandvox you'll see the list of plugins that are included in the .app.

Though you can activate and deactive them using Finder, I don't recommend doing it, for any app. Apple seems to have implemented half of a good idea here, but with no documentation or expectation as to how it should be used. Activating/deactivating plugins this way may have unfortunate results. So don't do it. But it's still an interesting way to browse and see if an app supports plugins, at least in the standard way.
Nevertheless, what's important is that plugins allow you to modularize and build out an application's functionality over time, including after the bulk of your app has already shipped. Not only can you extend your application with new plugins, but so can third parties. That's a big bonus and well worth supporting in your application if it makes sense.
The typical way that you encourage plugin development for your app is to move the general code required to support a plugin to its own framework that you then bundle inside your .app. Another developer comes along, learns (via SDK documentation, if they're lucky) that your app supports plugins, and that the way to do that is by linking to one or more frameworks within your app's main bundle.
Sandvox works this way. In fact, Sandvox supports six different types of plugins: Pages, Pagelets, Elements, DataSources, Indexes, and Designs. In all shipping versions of Sandvox up to the current, 1.2, we've made available a framework called KTComponents that you need to link your plugin against so it knows how to load itself into Sandvox, talk to Core Data, etc.
Wanting to be as open with our code as is reasonably possible, we kept moving more and more functionality from Sandvox over to KTComponents so that plugins could use this code, too. This included things like HTML parsing, UI classes, value transformers, and a bunch of different categories extending not only Foundation and AppKit, but Core Data, Core Image, and WebKit. Over a hundred classes or categories in all. A lot of code.
And, frankly, it's been a big pain maintaining it. Having all this code in a separate framework slows down the build. Makes the build target more complicated. Makes it harder to figure out where to set breakpoints. Makes it harder to locate resources. Makes maintaining localized strings more work that it should be. And did I mention that it really slows down the build?
Well, no more. When the next major update to Sandvox ships, KTComponents will be no more. All the code is going into the app. And plugins will work just fine. In fact, getting new plugins to build and link will be easier than I ever thought possible.
How can this be?
About a year and a half ago Wil Shipley posted a piece called Frameworks are Teh Suck, Err wherein he makes many fine points about where framworks came from, what they're good for, what their limitations are, and why you shouldn't use them.
At the time I thought "Well, that's nice, Wil. But we have to support plugins and we need a framework for that." Dan had posted in the comments to Wil's piece asking about this, but no one offered up a solution. I didn't think much about it at the time; just resigned myself to the annoying code split and build headaches.
About a year later, Wincent Colaiuta posted some thoughts about Cocoa development and the things he wished he'd done a little differently, had he only known, including where and when to use frameworks. Included in his post is a little piece of linker trivia that I had never heard of before. A flag for ld called bundle_loader.
Wincent didn't include any links to any information about bundle_loader and it only appears once in Apple's documentation, under Dead Code Stripping. I didn't think much about it at the time: just filed it away until I had some time to explore. This week, I explored...
Here's what 'man ld' has to say about bundle_loader:
-bundle_loader executable (32-bit only)
This specifies the executable that will be loading the bundle
output file being linked. Undefined symbols from the bundle are
checked against the specified executable like it was one of the
dynamic libraries the bundle was linked with.
I had no idea. This is basically how you get a plugin to link against an app without the linker going belly up. It works and it works well. (The 32-bit only comment is a slight concern. I don't think it will be an issue for the foreseeable future for Sandvox. I intend to confirm this at WWDC.)
If you've made it this far, rather than bore you with all the things I tried to make the linker happy before I stumbled onto bundle_loader, let me just share with you how we're going to be building plugins from here on out.
Rather that having to know about and locate KTComponents in the Sandvox appwrapper, somehow adding that to your plugin project, getting your framework search paths right, worrying about framework version numbers, if you're so inclined, and all that other junk, you now just add these two settings to your plugin's target's build configurations.
USER_HEADER_SEARCH_PATHS = /path/to/Sandvox.app/Contents/Headers
BUNDLE_LOADER = /path/to/Sandvox.app/Contents/MacOS/Sandvox
That's it! Really, that's it. That's all you have to do. Your plugin doesn't have to know anything about Sandvox except those two settings. Unbelievable. What was I doing messing around with frameworks for all these years?
You can put these in the Build tab of your target's inspector or you can put them into xcconfig files.
Suddenly, now, life just got a whole lot simpler. Plugins are trivial to get up and running. And we now have all the Sandvox code under one roof. (ConnectionKit, iMediaBrowser, and WebKit notwithstanding.) Suddently refactoring Sandvox becomes doable, since all the code is in the same project now. Very happy news. And, oh yeah, did I mention builds are a lot faster?
One last word of advice: to make this work, you do have to pay attention to what symbols you're exporting. Typically when you make an install build, the executable is stripped of many of its symbols: symbols that plugins are going to need to link against. Basically you can either leave your app unstripped (though still strip debugging symbols) or pass flags to strip that are more akin to those used with frameworks than with apps. E.g.,
STRIPFLAGS="-S"
And if you have things marked "extern", like string constants and functions that you want accessible across files, you need to tell gcc not to make those symbols private or plugins won't link for that reason as well. E.g.
GCC_SYMBOLS_PRIVATE_EXTERN=NO
So that's the big secret. No more splitting projects into app+framwork(s). I wish I had known about this years ago.
bundle_loader is the way to go.
Posted by ttalbot at 3:06 PM | Comments (2)
May 16, 2007
Fail Faster
The default Cocoa Application project in Xcode includes a target with a set of build phases. Here's what it looks like:

Notice that Copy Bundle Resources happens before Compile Sources and Link Binary With Libraries. This means that every time you build, before the compiler or the linker see anything, Xcode spends time copying things like nib files and images into the built product.
If you have a large app, this list of resources to copy can be quite long. Everytime you make a source change and hit command-Y to compile and relaunch your app, it has to run through Copy Bundle Resources again, and then it tries to compile. That's fine. It needs to do that. What's the big deal?
The big deal is that if you're writing a bunch of new code or manually refactoring some older code and you have syntax errors, say, a missing semicolon, a typo in a class name, or a missing #import, the compiler is going to complain and halt your build. If you forget to add a reference to a framework or a library, the linker will complain and halt your build. But it's going to halt your build after copying bundle resources, things that the compiler and linker pretty much don't care about.
In Sandvox's case, there are nearly 200 different resources to copy during a build, and that's not even counting the plugins and frameworks that ship with the app. Doing this over and over again during the course of a day adds up. That's time we can reclaim.
Whenever I make a new project now, I open up the target(s) and move Copy Bundle Resources below Link Binary With Libraries.

This makes sure that the first thing Xcode does is build my code. If it builds, then it can go on and flesh out the rest of the .app. If it's not going to build, I want to know right away so I can spot the problem, fix it, and get back to work.
So I say, move that build phase!, and save yourself some time.
Posted by ttalbot at 4:25 PM | Comments (2) | TrackBack