Apple’s Macintosh Programmer’s Workshop (MPW) is the most powerful development environment available on the Macintosh. It is more than just an environment for writing programs: it also offers powerful file-manipulation and task-automation functions.
Newcomers to MPW often describe it as being “UNIX-like”. While many features of MPW are indeed modelled on UNIX, its approach is fundamentally different from the conventional UNIX command line—and quite different from any other Macintosh development environment, for that matter.
People familiar with other Mac-based development environments tend to consider MPW to be complex and unfriendly. But then, MPW can do many things that those other environments cannot. MPW is admittedly “un-Mac-like” in many ways, but then, regardless of what else you may be familiar with, MPW is like nothing else you’ve ever seen!
MPW is available for downloading from Apple. Or you can read more info...
MPW has some UNIX-like features:
with MPW
Note the use of less cryptic command names: “Files” for “ls”, “StreamEdit” for “sed” and so on, as well as the use of Macintosh-specific characters for wildcards and other special purposes. Also commands are case-insensitive, like the MacOS filesystem itself.
It also has features like nothing available in UNIX:
These markers get saved (along with your font and tab settings, current selection, window position etc) in “MPSR” resources in the file’s resource fork, thus they are completely transparent to compilers and any other tools that might read the text, while at the same time they are persistent—they reappear for use the next time you open that file in MPW.
For example, imagine I want to expand tabs to spaces in a selected part of the contents of a window. A command like the following will not work:
because the output will be opened by the Shell before the Entab command executes, which means its input will be truncated to zero-length. Instead, you must do this:
The interesting thing is, while the second form works under MPW, the equivalent construct doesn’t seem to work under most UNIX shells. It works under bash, but not under csh, tcsh or sh.
For example, you might generate a listing of files in the current directory with a Files command. Then you might generate a count of the number of lines in certain of those files (not all of them) by inserting the command “Count -l” in front of each one you’re interested in and pressing Enter. Finally, you might copy and paste some of those counts (not all of them) into an “Evaluate” command to total them up. No need to try to figure out, in advance, a single command sequence to do it all! (Though you can do it that way if you want to...)
For example, the Set command lets you set new values for shell variables, as well as display the current values:
Set TheVar 'The Value'
sets the variable TheVar to the value 'The Value'. You can then query the value of this variable with the command:
Set TheVar
which produces this output:
Set TheVar 'The Value'
which is exactly what you would type to set the variable to that value!
The point with this is that the Make utility can use default rules to automatically decide which compiler to invoke to generate which object file.
where the Link command generates the executable code for MyProg (in “CODE” resources), while the Rez command includes other resources, such as message strings, menu definitions, icons and so on.
But there is a pitfall.
Thus, the command
Make `Make -r`
will generate any needed build commands for out-of-date parts of all root targets. It is easier to use this command than to require every Makefile to begin with a dummy “Everything” default target that depends on every other target, as is commonly found in UNIX Makefiles.
Indeed, in MPW, any valid regular expression may be used as a wildcard filename specification.
Find /regexp/
searches forward from the current position for a match for regexp, while
Find \regexp\
searches backward from the current position. So far, perhaps, not much more than you can do with normal UNIX regular expressions. Until you come to constructs like
Find selection1:selection2
which selects everything from a match for selection1 to a match for selection2, inclusive (either or both may involve forwards or backwards searches). Another valid form is
Find markername
where markername is the name of one of the markers previously defined for this file.
In MPW, there is (thankfully) a single universal convention: an option begins with a single hyphen. Multiple options must be specified separately. If an option takes a value, it is separated from the option by whitespace.
The MPW linkers, on the other hand, do not have the concept of “default” or “system” libraries: everything that goes into the link must be specified explicitly. Not only that, but MPW introduces the concept of “stub” libraries: these are cut-down versions of shared libraries, which contain nothing but the exported names from the full libraries, plus version information. The linker uses these in exactly the same way it would use the full library—that is, to record, in the executable, the name of the library and its version against the corresponding symbols imported from that library. When the program actually runs, the system loader will still expect to find the full library with the correct version in the usual way.
This allows you, for example, to build your program with a stub library that specifies an older version than the actual shared library on your system. Or you can include a stub for a shared library that doesn’t actually exist on your system (if you’re cross-compiling for another system), or conversely, omit a stub for a shared library that is installed, in case you don’t want to rely on some feature that is only available on newer systems. And you can do all this in the confidence that it will work—that the linker isn’t “accidentally” doing something extra behind your back that you weren’t expecting.
An alternative approach, used for example under Microsoft Windows, is to write the program code to manually load the library and search for the appropriate symbols within it. But this is a lot of extra work, when weak-linking makes things so much easier.
There are some UNIX features lacking in MPW:
One consequence of this is that the Make tool cannot actually invokes the commands it generates: it merely writes them to standard output, where it is easy enough to capture them in a window, and select and execute them. Alternatively, you can write a simple wrapper script to capture the commands to a temporary file and execute them there (in fact, Apple provides one). But I prefer the manual approach, because
Project-oriented development environments like Metrowerks CodeWarrior are very popular. They are built around a pretty simple model: take a bunch of source files, compile them all, and link them into a single executable. They probably deal nicely with 90% of programming needs (though your choice of problems to solve is often subtly influenced by the tools you have available to solve them!). But they do tend to fall down in a number of areas:
In MPW, by contrast, you can set up the build rules to generate the fat executable directly, without producing any intermediate executable targets.
The way MPW deals with this is to have separate Make variables, COptions and PPCCOptions which specify options for the 68K and PowerPC C compilers respectively. However, it is easy enough to define these variables partly in terms of another, custom variable, which provides common language settings, e g
CommonCOptions = -proto strict
COptions = {CommonCOptions} -mc68020 -mc68881
PPCCOptions = {CommonCOptions} -vector on
so changing the common options is done in one place, just by updating the definition of CommonCOptions.
In MPW, on the other hand, the Makefile format and build commands have remained largely stable for a long time. If you do happen to use newer features that might not be available in the version of MPW that someone else is using, the odds are that they will be able to get around it by making small changes to the build files, rather than having to rewrite them from scratch.
CodeWarrior Pro 5 and later tried to get around this by introducing the feature of exporting a project file to XML format, as well as being able to reconstruct a project file by reimporting the XML form. Of course, this feature is of no use to those still stuck with earlier versions of CodeWarrior.
It’s hard to conceive of MPW Makefiles, which are just text files, becoming corrupted in any comparable fashion.
Trouble is, the CodeWarrior displays cannot be saved to disk in any fashion that keeps their “liveness”. Imagine I want to save the error list to disk so I can come back to it and continue fixing the errors later: I can save the list to disk all right, but when I try to reopen it, it’s just a text file, and I can no longer double-click entries to go to them! And the Compare Files display cannot be saved to disk at all: If I want to come back to the comparison later, I have to rerun it.
In MPW, compilers and other tools report all their messages in the form of ho-hum plain text. But there is an interesting twist. All the messages that refer to parts of files take a form that looks like this:
File "CheeseList"; Line 234 # Expected Gorgonzola, but got Cheddar instead
Remember what I said above about MPW being, not a command line, but a command window? It just so happens that, if you click within the above line and press Enter, MPW will open the specified file and take you right to the line mentioned in the message! (This works because “File” and “Line” are defined as commands, and the human-readable message is put after the “#” comment delimiter, where MPW pays no attention to it.)
It may only be plain text, but it’s “live” plain text, and it stays live even after saving it to a file and reopening it, or copying it to another application and back again, or whatever.
Now, I admit this is not a big issue with any realistic sizes for program sources on present-day machines. But there are other kinds of files you might want to operate on: for example, Web server logs can easily reach many hundreds of megabytes in size. I guess this difference reflects the fact that MPW is so much more than just a program-development environment.
With an MPW makefile, sharing of compiled code is the default behaviour: source file A will normally be compiled only once, and the result linked into both targets S and T. But if you really want a separate copy of the compiled code for each target, perhaps with different build settings, it is easy enough to set this up, by specifying different names for the two object files, with different rules for generating them from the same source file. As with most things in MPW, the choice is yours.
The closest CodeWarrior comes to this facility is that of project “stationery”, where you can use a single existing project file as the starting point for constructing a new one. Project files cannot be edited with the CodeWarrior editor in the same way that ordinary source files can.
Then the client reported that the application would no longer run on older Macs without USB, even if you weren’t trying to use the functions provided by this hardware. I discovered that the driver library included an implicit import of USBServicesLib, which is a MacOS shared library that only loads if the machine has a USB interface, which my machine did. I had not included this library in my project, yet it was automatically being brought in by CodeWarrior during the build, as a “strong” import requirement. The result was that the application could not be launched on a system where this library was not present.
The solution, of course, was to explicitly include USBServicesLib in the project, such that it came before the hardware driver library in the link order. Then I could specify that USBServicesLib was to be weak-imported, which meant that the application was to be allowed to run even if this library wasn’t present. Of course, my code had to explicitly check whether the library was present before attempting to use the functions of the USB hardware.
This kind of trickiness is all very well, but where in the project do you document it? Otherwise, some future programmer who inherits my code may not appreciate the significance of this aspect of the project settings, and introduce a bug by inadvertently changing it. Having a separate “Read Me” file introduces a separate project component that I have to remember to keep up to date. But the CodeWarrior project file doesn’t provide any place for me to insert my own comments!
Contrast this with an MPW Makefile. There is no such thing as implicit import of libraries, so I would have had to explicitly include USBServicesLib in the build from the beginning—the link order is not important. Also, it would be easy enough to stick a comment next to the -weaklib directive that weakly imports this library, explaining that this is to allow the program to run on machines without USB hardware.
It wasn’t always this way. Back in the early days of project-oriented environments, it was MPW that was big and slow, and the project-based systems that were small and fast. What’s happened is that the project environments have become larger, slower and more complicated, as they try to fill in all the major gaps that keep becoming apparent in their functionality, that MPW was able to do all along. Trouble is, I don’t think their original architectures were designed to cope with all of this. Thus, whether you like it or not, the MPW architecture turns out to be the one that is better at standing the test of time.
For a long while, one of the ways I used to customize my MPW installation
was to remove the -Q
keyboard shortcut for the “Quit” menu
item. This is because it was so close to the
-W shortcut for
closing windows that I would occasionally hit it by accident and quit the
environment when I just wanted to close a window, and it used to be a
pain when it took several seconds to start MPW up again.
These days, I do this same customization with CodeWarrior, but I don’t bother doing it with MPW any more—MPW is so fast to start up now that it no longer matters.