Part One: CodeWarrior
In the mid-nineties, I was using Metrowerks' CodeWarrior development system for Mac OS. While it was a decent example of an Integrated Development Environment, I began to chafe under its rigidity. Like any proper Macintosh application, it stored data in binary file formats, to which it provided access through dialog boxes and other windows. Aside from one's own source code, there wasn't a text file in sight.
While the project manager metaphor was ostensibly user-friendly, the opaque file containing the details of one's project didn't play nicely with version control. Not only was generating a diff impossible, but merely opening the project in the IDE would update its modification timestamp, so it always appeared to systems like CVS that it had been modified. MacCVS Pro actually included special logic to detect this situation and ignore it.
Then came target multiplication. Whereas the initial CodeWarrior developer releases shipped with each combination of language (C and Pascal) and architecture (68K and PPC) supported in a separate application, a later version of the IDE unified these, allowing the developer to have a single project file per project. To allow the same project to be built for both 68K and PPC architectures, the project data model included targets: One target would compile for 68K and link against 68K libraries, another would do the same for PPC. Targets could also be used to select an optimized build versus one for debugging. Combining both dichotomies yields four targets: 68K debug, 68K optimized, PPC debug, and PPC optimized. Then if your project involves multiple executables, like a code resource or shared library in addition to an application, you now have eight targets. Or, if you support one of, say, 68020 optimization, profiling, or a third executable, make that twelve. Or, for all of them, twenty-seven.
Even with only four targets, the process of changing an option uniformly across all four required visiting the Preferences dialog for each target in turn. Besides being tedious, this is error prone. If the project file had used a human readable text format, you could have verified by viewing the diff that (for example) the same new header search directory had been added, in the same order, to each target. Instead, you had to just do it right.
Even better, of course, would be the ability to specify information common to all targets in a single place. And avoiding altogether the need to specify anything covered by default assumptions would be better yet. I realized that if I wanted a system built to my specifications, I'd have to be the one to build it. It was time to leave behind the mass-produced branded erector set and set up a workshop.
Part Two: MPW
Inspired by the example of Matthias Neeracher, author of MacPerl, I installed MPW, Apple's Macintosh Programmer's Workshop. MPW was an IDE in the sense that a single application, MPW Shell, provided text editing, project management, and a suite of developer tools. But it also loosely mimicked Unix — it featured a plugin interface resembling standard I/O (including command-line arguments, filehandles, and environment variables) and a built-in shell scripting language.
However, very much unlike Unix, the MPW Shell had only a single thread of execution — only one program could be running at once. Not only that, but there was no way for MPW's compiled plugins (called tools) to invoke other tools or scripts — not even via system() (which blocks the calling program until the called program exits). Therefore, Make couldn't actually do anything, but only printed out the commands for the user to run manually. You could code in Perl instead of the built-in language, but then your scripts couldn't run other programs — only MPW shell scripts could do that.
I was incredulous. How could a product whose design was so obviously derived from Unix end up so far removed from it?
(Allow me to emphasize how starkly this contrasts: In Unix, compiled programs are first-class citizens — they can do anything — with scripts a very close second. A script can name in its first line an interpreter program that will be used to run it. Compiled programs (called 'binaries', since they're not text) and scripts are nearly equal in ability — the only exception being that the setuid flag is commonly ignored for scripts for security reasons. The Unix equivalent of MPW's restrictions would be an OS with a single shell scripting language interpreter compiled into the kernel and no user-level fork or exec calls.)
MPW's scripting language was strictly imperative, having loops but no means to define functions. If you needed a function, you had to provide it as an executable (and if it needed to call other programs, then it had to be an MPW shell script as well). Arguments passed this way were subject to additional evaluation (again, unlike Unix), so to properly escape them you had to know how deep they'd been passed. The build tool I eventually cobbled together was a hodgepodge of Perl, dmake fragments (since I didn't touch the built-in Make), and (naturally) MPW shell scripts, including one script whose sole function was to output its arguments doubly-escaped, so that when applied, it would cancel out the one level of evaluation that occurred just by calling it, for a net effect of escaping once. I could then apply this script to a set of arguments as many times as needed. Of course, each invocation had to read the script text from its file — yet another trip through the Device Manager, classic Mac OS' legendarily slow I/O subsystem.
The end result was that my build tool took over 30 seconds just to determine that everything was up to date. What. The. Hell.
While MPW was amazingly robust — I'd never seen it crash once — I considered it deeply flawed for its functional limitations. It was unthinkable to me how someone could be inspired by Unix's combination of simplicity, power and elegance, and then deliberately create a new system lacking it. Especially when (given enough time) even a novice like myself could surely do it.
I remained outraged until I realized the truth of my previous thought: I could do it. Sure, it would be a lot of work, and I'd have to figure out how to do system calls, concurrent processes, vfork() and execve(), signals, and a Unix-like view of the Mac file system, and fork() was possibly out of the question, but it was definitely doable (given enough time).
The choice was obvious.
Part Three: Genie and Lamp
Having had enough of MPW's limitations, I set out to create a replacement. The first thing I needed was a name.
Douglas Hofstadter had written in Metamagical Themas how Lisp's REPL (read-eval-print loop) reminded him of a genie, in that you gave it commands, and it performed them (and displayed the results). Aside from needing a Unix shell instead of Lisp, this image was precisely what I had in mind, and I adopted the name Genie for my new project. Then I realized that the name Genie, in addition to being confused with the commercial online service GEnie, conveyed very little information about the project — even if you did get the reference, Genie was far more than just a shell. My ultimate goal was nothing less than making as much of Unix as possible available in classic Mac OS. That said, it was unlikely that I would ever achieve standards-conformance, and drawing on another Hofstadter reference (to the dialogue Little Harmonic Labyrinth from Gödel, Escher, Bach, in which a genie is summoned from a lamp), as well as aping the recursive acronym GNU (GNU's Not Unix), I renamed the project to LAMP (LAMP Ain't Mac POSIX). (POSIX is a standardized specification for operating systems, and is a subset of Unix.) I soon recased LAMP to Lamp to ward off yet more confusion with the other LAMP (Linux/Apache/MySQL/Perl) until I could come up with a better name.
I began development in the latter part of 1999. Before the year ended, I had used Genie at my workplace to deal with invisible files that the Finder ignored. The next summer I demoed Genie at MacHack 15's Best Hack contest (providing a counterpoint to the development of Mac OS X). But despite the intensity of my efforts, Genie got off to a modest start. The first revision checked into SourceForge (in March 2001) was a monolithic, single-threaded application. The shell was built into the kernel, and every command was a shell builtin. There was no POSIX API yet; all filing operations used Mac OS routines. I hadn't yet learned about RAII or smart pointers; for memory management, I actually implemented NeXTstep autorelease pools in C++. In short, I really didn't know what the hell I was doing.
Part Four: MacRelix
After more than a decade of development, I finally thought of a workable name: MacRelix. It started with the prefix 'Mac', clearly marking it as a Mac application. As the name of a Unix-like system, it satisfied the convention of ending in either 'ux' or 'ix'. And the play on 'relics' was just the right amount of self-effacement, as well as a clue that MacRelix was more at home in the 1990s — a prior century (or even millennium) in the software world.
Nowadays, MacRelix has pipes, signals, system calls, TCP sockets, and more. It works on both 68K and PowerPC Mac systems and builds as Carbon to run natively in OS X. It can be used on any Mac OS version from System 7 to Mac OS X 10.6 "Snow Leopard" (after which Apple removed the Rosetta PowerPC emulator). I haven't implemented fork() yet, but I know how to do it. In addition to a Unix-like file system interface (which even handles long names by storing them in Desktop database comment fields)), MacRelix has a /proc filesystem (with human readable stack crawls) and also maps various parts of Mac OS (e.g. the ROM image in /sys/mac/rom).
It even has a virtual filesystem as a portable windowing API, called FORGE (File-Oriented, Reflective Graphical environment), which may be the most important feature of all. Eventually I'll set aside MacRelix along with the rest of classic Mac OS, but FORGE is a concept that you'll see on modern Unix-based systems in the future (and perhaps in the next issue of ZiP).