September 95 - MPW Tips and Tricks: Customizing Source Control With SourceServer
MPW Tips and Tricks: Customizing Source Control With SourceServer
Tim Maroney
When two engineers on a team edit the same source file at the same time, the
resulting chaos can be terrible to behold. Source control was invented to
mitigate the problem. Most Macintosh programmers are familiar with the MPW
Shell's Check In and Check Out dialogs, and with its Projector commands. The
next frontier of custom source control involves SourceServer, a nearly faceless
application that implements most of the Projector commands. MPW scripts are
easy to write, but they're no match for the power, speed, and friendliness of
compiled software. SourceServer exports Projector commands as Apple events,
allowing source control from compiled software without launching the MPW Shell
in all its pomp and splendor.
Popular third-party development environments often send Apple events to
SourceServer for integrated source control. You can also use SourceServer to
customize Projector beyond what you might have thought possible. For instance,
you can drag source control, kicking and screaming, into the modern world of
user experience with drop-on applications. In this column, I'll show you how to
check a file in or out with a simple drag and drop, and how to use SourceServer
for other things as well. The sample code is provided on this issue's CD;
SourceServer is distributed, with documentation, on the MPW Pro and E.T.O. CDs
(available from Apple Developer Catalog) and with third-party development systems.
Apple events have many faces, but they're primarily a way of communicating
between different applications.
Each Apple event encapsulates a message as a command with any number of input
parameters; the receiver of the message may return any number of result
parameters to the sender. The most basic unit of data is the Apple event
descriptor, which consists of a type code and a data handle. Apple events are
built out of descriptors and are themselves special kinds of complex
descriptors.
For an excellent introduction to Apple events, see "Scripting the Finder From
Your Application" by Greg Anderson
in develop Issue 20.*
SourceServer's commands are represented as descriptor lists. Its Apple events
are exact duplicates of the MPW Shell's Projector commands, but to avoid the
overhead of a full command parser, both the command name and each argument are
descriptors in the descriptor list. This saves you the trouble of putting
quotes and escapes into arguments that might contain spaces or other special
characters. The downside is that you have to expand arguments yourself: you
can't pass in MPW wildcard characters, backquoted commands for expansion, or
other special constructs.
Creating descriptor lists may sound harder than writing MPW scripts, but that's
only because it is. I've provided some utility routines to ease the way,
though. Listing 1 shows the utilities and illustrates how to make a command to
check out a file for modification. As illustrated in the CheckOut routine in
this listing, you call the CreateCommand routine first and then use the AddXArg
routines to add arguments.
Listing 1. Creating SourceServer commands
OSErr CreateCommand(AEDesc *command, CString commandText)
// Begin a new SourceServer command; name of command is in
// commandText.
{
OSErr err = AECreateList(NULL, 0, false, command);
if (err != noErr) return err;
err = AddCStringArg(command, commandText);
if (err != noErr) (void) AEDisposeDesc(command);
return err;
}
OSErr AddCommentArg(AEDesc *command, StringPtr comment)
/* Add a "-cs comment" argument to a SourceServer command. */
{
OSErr err;
if (comment[0] == 0) return noErr;
err = AddCStringArg(command, "-cs");
if (err != noErr) return err;
err = AddPStringArg(command, comment);
return err;
}
/* Other SourceServer argument utilities */
OSErr AddDirArg(AEDesc *command, short vRefNum, long folderID);
OSErr AddProjectArg(AEDesc *command, StringPtr projectName);
OSErr AddUserArg(AEDesc *command, StringPtr userName);
OSErr AddFullNameArg(AEDesc *command, FSSpec *file);
OSErr AddPStringArg(AEDesc *command, StringPtr string);
OSErr AddCStringArg(AEDesc *command, CString string);
OSErr CheckOut(FSSpec *file, StringPtr userName,
StringPtr projectName, StringPtr comment)
/* Create a "Check Out Modifiable" command for SourceServer: */
/* CheckOut -m -cs <comment> -d <dir> -project < */
/* project> -u <user> <file> */
{
OSErr err;
AEDesc command;
CStringHandle output = NULL, diagnostic = NULL;
err = CreateCommand(&command, "CheckOut");
if (err != noErr) return err;
err = AddCStringArg(&command, "-m");
if (err == noErr) err = AddCommentArg(&command, comment);
if (err == noErr)
err = AddDirArg(&command, file->vRefNum, file->parID);
if (err == noErr) err = AddProjectArg(&command, projectName);
if (err == noErr) err = AddUserArg(&command, userName);
if (err == noErr) err = AddPStringArg(&command, file->name);
if (err == noErr)
err = SourceServerCommand(&command, &output, &diagnostic);
(void) AEDisposeDesc(&command);
/* Display output or diagnostic text as desired. */
if (output != NULL) DisposeHandle((Handle) output);
if (diagnostic != NULL) DisposeHandle((Handle) diagnostic);
return err;
}
Some
of the utilities take Pascal strings, while others take C strings, which could
well be considered bad programming practice. I chose this dubious method not
because I'm on drugs, but because Pascal strings and C strings are used in
different ways. SourceServer's text descriptors are C strings; when passed to
these utilities as string constants, they shouldn't be converted from Pascal
format in place, since some compilers put constants in read-only areas. If
you're internationally savvy, you may have another objection: string constants
themselves are bad practice. However, for better or worse, MPW scripts and
tools are not internationalized. Just like aliens in Star Trek, all MPW
programmers are assumed to speak English.
While on the subject of programming practice, I must gently reprimand
SourceServer for its approach to Apple events, in which script commands are
simulated through a single 'cmnd' event. SourceServer's idiosyncratic
convention dates from the earliest days of Apple events, and modern guidelines
discourage this type of design. An application implementing its own Apple
events should designate a different command code for each operation, treating
arguments as keyword parameters.
Listing 2 shows how to send an Apple event to SourceServer. It's first
necessary to find and perhaps launch the SourceServer application. The snippet
called SignatureToApp (by Jens Alfke) on this issue's CD accomplishes this with
a single function call. Simply pass in the creator code of SourceServer, which
is 'MPSP'.
Listing 2. Sending commands to SourceServer
OSErr SourceServerCommand(AEDesc *command, CStringHandle *output,
CStringHandle *diagnostic)
{
AppleEvent aeEvent;
AERecord aeReply;
AEDesc sourceServerAddress, paramDesc;
ProcessSerialNumber sourceServerProcess;
/* SignatureToApp requires this due to a minor bug */
FSSpec appSpec;
long theLong, theSize;
DescType theType;
OSErr err;
*output = *diagnostic = NULL; /* default replies */
/* Find the SourceServer process and make a descriptor for its */
/* process ID. */
err = SignatureToApp('MPSP', NULL, &sourceServerProcess,
&appSpec, NULL, Sig2App_LaunchApplication,
launchContinue + launchDontSwitch);
if (err != noErr) return err;
err = AECreateDesc(typeProcessSerialNumber,
(Ptr) &sourceServerProcess,
sizeof(ProcessSerialNumber),
&sourceServerAddress);
if (err != noErr) return err;
/* Create and send the SourceServer Apple event. */
err = AECreateAppleEvent('MPSP', 'cmnd', &sourceServerAddress,
kAutoGenerateReturnID, kAnyTransactionID,
&aeEvent);
/* done with the address descriptor */
(void) AEDisposeDesc(&sourceServerAddress);
if (err != noErr) return err;
/* add the command */
err = AEPutParamDesc(&aeEvent, keyDirectObject, command);
if (err != noErr) { (void) AEDisposeDesc(&aeEvent); return err; }
err = AESend(&aeEvent, &aeReply,
kAEWaitReply + kAENeverInteract, kAENormalPriority,
kNoTimeOut, NULL, NULL);
(void) AEDisposeDesc(&aeEvent); /* done with the Apple event */
if (err != noErr) return err;
/* Check for an error return in the keyErrorNumber parameter. */
err = AEGetParamPtr(&aeReply, keyErrorNumber, typeInteger,
&theType, &theLong, sizeof(long),
&theSize);
if (err == noErr && (err = theLong) == noErr) {
/* Get the standard output from the keyDirectObject parameter. */
err = AEGetParamDesc(&aeReply, keyDirectObject, typeChar,
¶mDesc);
if (err == noErr)
*output = (CStringHandle) paramDesc.dataHandle;
/* Get the diagnostic output from the 'diag' parameter. */
err = AEGetParamDesc (&aeReply, 'diag', typeChar,
¶mDesc);
if (err == noErr)
*diagnostic = (CStringHandle) paramDesc.dataHandle;
/* Get the MPW status from the 'stat' parameter -- it */
/* becomes our error return. */
err = AEGetParamPtr(&aeReply, 'stat', typeInteger,
&theType, &theLong,
sizeof(long), &theSize);
if (err == noErr) err = theLong;
}
/* done with the reply descriptor */
(void) AEDisposeDesc(&aeReply);
return err;
}
The
event must be created before it can be sent. For SourceServer, there's a single
parameter, named keyDirectObject, which is the descriptor list containing the
command. After sending the event, you must extract the results. The results of
an Apple event are returned as keyword parameters in a reply descriptor. First
there's the standard keyErrorNumber parameter, which returns an error code if
delivery failed. SourceServer returns three other parameters: The 'stat'
parameter contains a second error code; if it's nonzero, SourceServer tried to
execute the command and failed. When there's an error, there will be diagnostic
output in the 'diag' parameter, a handle containing text from the MPW
diagnostic (error) channel. Finally, there's standard output -- a handle
specified by keyDirectObject -- which contains explanatory text.
The Macintosh has always had a drag and drop user experience, but the true
power and generality of dragging has been widely recognized only recently. The
drag paradigm can even be used for source control. To turn Projector into a
drag-savvy system, I've written a set of utilities called ProjectDrag (source
code and documentation are provided on this issue's CD). You simply drag and
drop icons onto the following miniapplications that make up ProjectDrag, and
the corresponding function is performed:
- Check In and Check Out, for checking files in and out
- ModifyReadOnly, for editing a file without checking it out
- Update, for bringing a file or folder up to date, as well as canceling
checkouts and modify-read-only changes
- ProjectDrag Setup, for configuring the system
These utilities are
based on a drop-on application framework called DropShell (written by Leonard
Rosenthol and Stephan Somogyi), also on the CD. When a file is dropped onto an
application, the application receives an Open Documents ('odoc') event.
DropShell takes care of the rigmarole of receiving this and other required
Apple events. The ProjectDrag miniapplications pull the file specifications out
of 'odoc' events and create SourceServer commands that operate on the files and
folders that were dropped on their icons.
DropShell is also available on the Internet at
ftp://ftp.hawaii.edu/pub/mac/info-mac/Development/src/ and at other Info-Mac
mirror sites.*
Some setup is required. ProjectDrag needs to know the locations of Projector
databases. It maps between project names and Projector database files by
keeping aliases to database folders in its Preferences folder. To start using a
project, simply drag its ProjectorDB file or the enclosing folder onto
ProjectDrag Setup. Projector also needs to know your user name, and your
initials or a nickname are used in change comments at the start of files. These
are stored in a text file in the Preferences folder. ProjectDrag asks you for
this information if it can't find it, or you can launch ProjectDrag Setup and
give the Set User Name command.
ProjectDrag is scriptable, unlike SourceServer and the MPW Shell. The
miniapplications have an Apple event terminology resource ('aete') to advertise
their events to scripting systems. This allows you to add source control
commands to any application that lets you add AppleScript scripts to its
menus.
ProjectDrag is able to run remotely over a network. This circumvents a
limitation of SourceServer, which can only be driven locally. ProjectDrag can
receive remote Apple events and then drive a copy of SourceServer that's local
to it. Among other uses, this could support an accelerator for Apple Remote
Access. Checking a file in or out over ARA takes a few minutes, which is fine,
especially for those who find tedium particularly enjoyable. Copying files is
faster. With local AppleScript front ends for remote ProjectDrag
miniapplications, you could copy files to and from a remote "shadow folder" and
initiate SourceServer commands at the remote location, where they would execute
over a fast network such as Ethernet.
I like to think that I can solve user interface problems in my sleep. When I
was writing ProjectDrag, I had a dream of a better user experience. Instead of
miniapplications, ProjectDrag would be a magical system extension that would
put a single small icon at some convenient place on the screen. When you
dragged a file onto this icon, it would pop open into a temporary window and
show you icons for the various options. Dreams are great for creativity, but
it's easier to weigh alternatives when you're awake. After I woke up, I
realized that miniapplications will be able to do the same thing.
Here's how: In Copland, the next generation of the Mac OS, the Finder will
spring-load folders so that they open automatically when you drag onto them. It
will also let you stash commonly used folders at the bottom of the screen,
where they appear as short title bars. Drag the ProjectDrag folder to the
bottom of the screen and you're set! Since the Finder will be providing my
dream interface, there's no point in a lot of trap patching and extensibility
infrastructure to accomplish the same thing.
Copland will bring another user experience benefit to ProjectDrag: it's planned
that document windows will have a draggable file icon in their title bar, so
you'll be able to use ProjectDrag on an open document by dragging the icon from
its window.
You can create programs that use SourceServer for many other tasks. On
cross-platform projects, Projector is sometimes used to control both platforms'
source folders. This can lead to baroque and error-prone processes. With
SourceServer, you can create front ends that do the right thing. They could
copy to remote folders over a network, or lock read-only files since the other
platform doesn't see Projector's 'ckid' resources.
Quality is an interesting area for source control applications. A quality tool
could query Projector databases for the frequency and scope of changes at
various stages of the project, correlating them with bug tracking to develop
project metrics. Along similar lines, a tool could measure the change rate of
various files to assist in what the quality gods refer to as root-cause
analysis.
SourceServer is much more than a way for development systems to provide
integrated source control. It's great for structuring your internal development
process as well!
TIM MARONEY wrote TOPS Terminal and BackDrop, and has been a major contributor
to TOPS for Macintosh, FaxPro, and Cachet. He has also contributed to Fiery,
the Disney Screen Saver, Ofoto, Colortron, and the Usenet Mac Programmer's
Guide. Tim learned computer networking while working on the Andrew and MacIP
projects at Carnegie Mellon and studied compiler design in graduate school at
Chapel Hill. He has written for all three major operating systems and a few
minor ones. On the Macintosh, Tim's code has included applications, INITs,
control panels, HyperCard stacks, XCMDs, shared libraries, trap patches,
plug-ins, scripts, and things more difficult to characterize. Tim is currently
doing contract work at Apple, and is available for parties and special events
at a nominal cost.*
Thanks to Greg Anderson, Arno Gourdol, and B. Winston Hendrickson for reviewing
this column.*
Special thanks to Jens Alfke, Jon Pugh, Leonard Rosenthol, and Stephan
Somogyi.*