Details on our Universal Binary port

Thursday, March 30, 2006

This is for the Cocoa developers out there, or anyone who might be interested in what I had to change to make Curio a Universal Binary.

First, some background: Curio is about 250,000 lines of code. It's broken into about 13 separate Xcode projects. For example, our global app-independent framework, our Curio core framework, a project for all the Inspector plugins, a project for all the Preference plugins, etc. We end up creating or bundling 9 frameworks, 21 plugins, a Spotlight plugin, and the Curio executable itself.

Some key points before I get started:
  1. We wanted to maintain compatibility with 10.3.9 Panther customers. We couldn't just be Tiger and above.

  2. That said, we do make some Tiger-only calls but we check the OS version before making those calls so they are done on-the-fly. The important thing here is that we can't build against the 10.3.9 SDK since that would result in compile errors for those Tiger-only calls we make.
For all of our projects, I had to make some changes to each project's build properties. To access these properties simply open the project in Xcode and double-click on the icon on the very top of the Groups and Files list. The advantage here is that all the changes you make will be inherited by all the Targets of this project. For example, all of our Inspectors are in one project but there is a Target for each Inspector. So I can change just the project's build settings and all 13 targets for each Inspector automatically inherit those changes.

So, in the project's build properties:
  1. Click the General tab, choose "Mac OS X 10.4 (Universal)" for the Cross Develop Using Target SDK item. This will set the SDK Path for all configurations. It will also set the Architectures to $(NATIVE_BUILD) so it only builds for your machine's architecture.

  2. Click the Build tab, choose " for the Configuration item and "Customized Settings" for the Collection item. Now add two custom items by clicking the + button. The first is MACOSX_DEPLOYMENT_TARGET_i386 and set it to 10.4, then the second is MACOSX_DEPLOYMENT_TARGET_ppc and set it to 10.3. This tells the application that it can be run on PowerPC Macs on Panther or above, but on Intel it requires Tiger and above.

  3. Next change the Configuration dropdown to "Deployment/Release". Change the Architectures setting to "ppc i386" (without the quotes). If you switch Configuration to "Development/Debug" you'll see that Architectures is still $(NATIVE_BUILD). This means when doing debug builds you'll only compile for the machine you're using (and not spend time building for the other architecture). But when you do your release build then you'll build for both architectures.
That's it. Do those same 3 steps for all the projects you have and you're basically done. For most everyone out there, that'll all you have to do to become Universal and still work on Panther.

Except, in the case of Curio, we have an additional wrinkle. One of our frameworks uses the system BZip2 library for compressing a number of things. We also use the system Crypto library for encrypting some things.

So, I had to edit the build settings for that particular target and add some custom link flags.
OTHER_LDFLAGS_i386 -lcrypto.0.9 -lbz2
OTHER_LDFLAGS_ppc -lcrypto.0.9 -L$(HOME)/Library/lib -lggbz2
Ew, what's all of that?

-lcrypto.0.9 means to link against libcrypto.0.9.dylib. So that's the same for both Intel and PowerPC builds.

But the BZip2 was tricky. Apple changed that library from a static library (a .a file) to a dynamic library (a .dylib file) when they released Tiger. So, when compiling against the 10.4 SDK we can't simply link against "-lbz2" because that'll find the dylib and thus not embed the static library in Curio, and, therefore, running on Panther will die since it won't find the dylib out there.

Here's how we fix it: on Intel we're safe just linking against the dylib since Intel is obviously Tiger and above only, so the dylib will definitely exist.

But on PowerPC, since we support Panther, we link against a copy of the static library and embed that in Curio. So, even though, technically, the dylib does exist in Tiger, we're ignoring it since we will just use the same static library found in Panther (which is just fine with us).

Where do we get the static library? From the 10.3.9 SDK!

We have a build script that builds all of Curio's projects for all 4 editions of Curio (Pro, K12, Home, and Basic). Just building Curio Pro takes 13 minutes on a dual 1GHz G4, or an amazing 4 and a half minutes on our new MacBook Pro.

That script does some "prep work" for us to get the build environment ready. One of the additional items it now handles is putting a copy of the static library in a special spot:
cp /Developer/SDKs/MacOSX10.3.9.sdk/usr/lib/libbz2.a ~/Library/lib/libggbz2.a
ranlib ~/Library/lib/libggbz2.a
Now our link will find that static library for our PowerPC builds and all is happy. (I was unable to have the linker link directly against the static library in the 10.3.9 SDK directory unfortunately, so this copying trick is a workaround.)

Lastly, Curio had a handful of places that needed some special code changes:
  1. GetKeys. We use the Carbon GetKeys call to find out the state of the keyboard on the fly. We had to make a tweak to use a KeyMapByteArray unioned with a KeyMap to get past a compiler error. Pretty straightforward code change:
     union {
    KeyMap keymap;
    KeyMapByteArray bytes;
    } keymap;

    GetKeys(keymap.keymap);

    // Now use keymap.bytes to look at the 16 array values
  2. Byte swapping. We use memmove to pack 4-byte and 2-byte integers into an array of bytes. Because of the Endian change between Intel and PowerPC, I had to make use of CFSwapInt32HostToBig and CFSwapInt16HostToBig before packing, and CFSwapInt32BigToHost and CFSwapInt16BigToHost after unpacking. This was very simple, just 6 places in the code had to change.
So that's it. The magic of Xcode's support of building universal binaries took care of the rest. Two days after I unpacked our MacBook Pro I had Curio running as a Universal Binary!

Curio is now a Universal Binary!

Wednesday, March 29, 2006


We're happy to announce that Curio 3.1 has just been released as a Universal Binary!

That's right: we dance like a native on Intel Macs, while still maintaining happy compatibility with Mac OS X 10.3.9 and above on PowerPC. In a future posting, I will detail the changes we had to make for you programmers listening out there.

Check out the release notes for the full details.

One of the other cool features added to this release is hoisting support in the Organizer. So, if you're working on a huge project and want to concentrate on a specific subtree then use hoist to hide everything else. Very slick!

Another, hugely popular request which made it in this release is search text highlighting. Long overdue, when you search for some text using our amazing Search Bar, that text itself is highlighted in yellow (you can change that color in the Prefs) in all the found figures so it stands out.

A few more features are in there as well, plus lots of fixes of course, so please be sure to check out the release notes.

Enjoy!

Curio 3.0.2 update with minor fixes

Wednesday, March 01, 2006

CurioWe just released a minor 3.0.2 update for all you 3.0.x users out there.

Lots of little fixes plus some minor features as well. For instance, via Curio's preferences you now can specify the monitor to present the slideshow. Also, by default, Curio will present only the "minimum bounds" of your idea spaces. So empty space is automatically cropped away before presenting, which allows for much nicer looking slides since we don't have to scale as much. You can enable or disable this feature through Curio's preferences as well.

Plus a slick change to the Project Center so if we can't find a project, like it has been moved to another volume, you can now locate it for Curio and we'll remember the new location.

We hope everyone enjoys the new release and please, as always, send us any feedback or suggestions. You can download the update here.