It’s April 1, and that means it’s both April Fools’ Day and the anniversary of the founding of Apple Inc. While this year is a sober one due to current events, I think a lot of people still appreciate what people are creating and sharing to keep spirits up, whether that be music or art or…impractical programming projects. And while pranks on April Fools’ seem less and less fun1, obvious jokes and whimsy, not at anyone’s expense, are still something I believe in…and even better if they actually work.
Last year I implemented the world’s best code visualizer. This year I decided to seriously attempt something that I’d thought about in the past: getting a Swift program to run on Mac OS 9.
What’s a Mac OS 9?
Twenty (!) years ago, before the macOS2 we know today, there was another operating system known as “Mac OS”.3 It was one of the first OSs to use a GUI at all, something that we pretty much take for granted these days. It also dates from the days when only one program could run at a time; because of that, even the latest version uses cooperative multitasking to run multiple programs—that is, a program has to yield its time to let others run.4 If a program crashed or overwrote memory it wasn’t supposed to, there was a good chance you’d have to restart the whole system.
Mac OS 9 ran on PowerPC processors, which were also used in the GameCube, PS3, and Xbox 360; earlier versions of the OS had started on Motorola’s 68k CPU series. Its successor Mac OS X5 also ran on PowerPC when it first launched; it wasn’t until 10.4 that Apple began to switch to Intel processors instead, and 10.6 when PowerPC was finally dropped.
Mac OS X was a huge step forward from Mac OS 9 in a number of ways, including preemptive multitasking so that you could actually run multiple things at once. But Apple didn’t want to just leave OS 9 programs behind, so they did two things:
-
The Classic environment set up a sandbox that looked enough like Mac OS 9 to run Classic Mac OS programs directly in Mac OS X. Because the Classic environment was itself an app, all the programs that ran inside it were protected from interfering with other Mac OS X programs and vice versa. It really was quite effective, and actually survived longer than booting into Mac OS 9 (which never received support for newer PowerPC processors). But its life ended with the switch to Intel-powered Macs—Classic was built on running the instructions in the original apps directly, only having to provide compatibility shims for libraries. Think of it like Wine / CrossOver rather than VirtualBox / Parallels.6
-
Carbon was a packaged-up version of the old Mac OS Toolbox APIs so that you could write Mac OS X apps the same way you always had. You basically just recompiled your app and added an extra annotation saying you were “Carbonized”. (Sound familiar? 7) Fun aside: This is the reason (one of the reasons?) Core Foundation exists—to provide a common interface between Carbon and Cocoa. (h/t Marshall Elfstrand for the video link.)
Classic ended with the switch to Intel processors back in the 2000s, but Carbon worked all the way up to last year, macOS Mojave. Apple never released a 64-bit version of Carbon, presumably to encourage developers to move to Cocoa, and with last year’s macOS Catalina, support for 32-bit apps was dropped entirely with very few exceptions.8
What’s the goal?
Since I learned to program on Classic Mac OS, and years later spent a good chunk of my career working on Swift, I’ve had the tantalizing thought that I’d like to write a program in Swift and run it on Mac OS 9. That is,
- I write Swift source code that calls Carbon / Toolbox APIs.
- I compile it for PowerPC with (a version of) the Swift compiler.
- I package it up as necessary for Mac OS 9.
- Profit!
Is this useful? No! Absolutely not! But neither was ROSE-8, and yet I still learned a lot doing it.
As you probably guessed, I managed to accomplish this, or I wouldn’t be writing this blog post. So, without further ado, here’s a picture of a Swift Toolbox app running on Mac OS 9.2, on my friend Nadine’s Power Mac G4. (Check out that blazing fast 400MHz processor!)
I assume a good number of people reading this would like to know how to do it too!
-
If you want to build your own PPC-capable Swift compiler, check out the following repositories:
git clone https://belkadan.com/source/ppc-swift-project cd ppc-swift-project git clone -b ppc-swift https://belkadan.com/source/swift git clone -b ppc-swift https://belkadan.com/source/llvm-project git clone https://github.com/apple/swift-cmark cmark make # quick start to build swiftc and the stripped-down stdlib
Note the directory and branch names of the sub-repos, and note that they should be nested inside the ppc-swift-project repo. You will also need the
mpw
emulator and a copy of the Macintosh Programmer’s Workshop tools9 to build an actual app using modern macOS. -
If you want a prebuilt PPC-capable Swift toolchain, here’s one: ppc-swift-toolchain. Note that while I’ve put a built Swift.o in this toolchain, you’ll probably only have success with optimized code that doesn’t actually have any remaining links to the stdlib (i.e. everything is inlined away). You will also need the
mpw
emulator and a copy of the Macintosh Programmer’s Workshop tools.9You may still want to check out the example in the ppc-swift-project repo. The required flags for
swiftc
,PPCLink
, andRez
can be a little finicky. (And note that theSIZE
andcarb
resources are required for any Carbon app, so you can’t just skip the Rez part if you actually want to run your app.) -
If you just want to try a built version of BitPaint, here’s one: BitPaint-swift.hqx.
I’d like to hear about anything you make with these tools! Meanwhile, if you’d like to hear how I made this work, read on.
Gathering materials
The last time I was building Classic Mac OS apps, I was using CodeWarrior. Actually, calling that “building Classic Mac OS apps” was a stretch; I was learning C and using CodeWarrior’s terminal I/O library to get a stdin/stdout interface that Classic Mac OS didn’t have natively. (Remember, no command line!) I could try to get some version of CodeWarrior running again, but that didn’t seem like the most convenient thing. I didn’t think I’d be able to get the Swift compiler running on Classic, so I’d be shuttling object files back and forth between OSs to get anything done.
Fortunately for me, I’m not the only one interested in building Classic apps on modern macOS. At some point I found about the mpw
project: an emulator specifically for running Apple’s Macintosh Programmer’s Workshop tools. And I knew it was going to work, too, because Steve Troughton-Smith, (in)famous in the Apple community for finding undocumented and prerelease features in Apple’s OSs, had written up his experiences building an app with mpw
that ran on System 1 all the way up to modern Mac OS X, just by building with the appropriate compiler and against the appropriate libraries.
If you’re interested in all this, I highly recommend checking out his blog post. Not only was this the reference I used to get started, but the app you see running in the above picture, BitPaint, is Troughton-Smith’s test app, ported to Swift. (I did ask him ahead of time if it was okay to use his app for a hobbyist project.) Longtime Mac developer Gwynne Raskind also gave a two-part high-level tour of the Toolbox APIs on Mike Ash’s blo