Using Perforce with XCode

Copyright © 2004 Dave Bayer.

This page is www.math.columbia.edu/~bayer/OSX/Perforce/


These are my notes on installing Perforce version control software for use with XCode 1.5 and OS X 10.3.5. I am no expert, but someone with similarly basic needs might save time by adapting my choices. I'm sure that in a few days, I'll forget how this all works, like that guy in Memento. I'll refresh my memory by reading these notes.

There are now three version control systems supported by XCode:

I am one developer, using one machine and a local repository. I want version control for comparing revisions of the software I write. Even for these simple needs, I wanted to avoid CVS; I like atomic commits, and I often reorganize and rename files. Following the best directions I could find for installing and using Subversion, I found it buggy; the interaction with XCode would sometimes hang, and XCode's "Compare With" command would sometimes cough up the wrong version of the file. Your mileage may vary, and this situation may improve in a future release of XCode; Subversion support was just added. Nevertheless, I settled on Perforce, which has worked well so far.

First, look over the following reading material:

Did you find that bit suggesting that CVS was "designed by hamsters on crystal meth" ? If not, you skimmed everything too fast, and what follows might not make sense; go back. Otherwise, proceed.

Be sure to test each step as you go, to make sure that everything works. You won't uncover the issues that matter to you unless you work with real projects, but you could make a major mess of things while learning Perforce, so learn on copies of real projects. When you're sure you've got the hang of Perforce, erase its database and start over with fresh copies. Expect to go through several learning cycles before establishing your working protocol. What follows is my working protocol:




Navigate from the Perforce home page to a suitable download page, such as one of:

Download p4, p4d, and associated documentation. Copy p4 and p4d to /usr/local/bin, and make them executable, using the commands

sudo cp p4 p4d /usr/local/bin
cd /usr/local/bin
sudo chmod a+x p4 p4d

Edit your shell startup file by adding the lines

export PATH=$PATH:/usr/local/bin:
export P4PORT=1666
export P4CLIENT=me
export P4ROOT=/Volumes/User/Users/me/P4ROOT

The syntax shown is for the BASH shell. Here, me could be changed to whatever you like, and /Volumes/User/Users/me/P4ROOT should be changed to the desired location of the Perforce server root directory.

Now, create the Perforce server root directory, and start the Perforce server p4d using the command

p4d &

This will populate the server root directory with the files needed by Perforce. To test the status of the Perforce server, use the command

p4 info
Should you want to stop p4d, use the command

p4 admin stop

Now, edit the Perforce Client Specification using the command

p4 client

This will open the specification in your default editor; you must save the specification and quit the editor to end the command. The critical fields to change are

Root:	/Volumes/User/Users/me/Code

View:
	//depot/Test/... //me/Test/...

The Root field should be set to a directory containing each directory to be placed under version control. Each directory to be placed under version control should then be listed in the View field. One can subtract out subdirectories such as build, but there's no need for the extra clutter here, if one is willing to tolerate an extra question mark or two in XCode.

Each project must be manually placed under version control, before XCode can take over. Create two shell scripts, p4files:

#! /bin/bash

find . -type f -print | egrep -v '^\./build/|\.DS_Store$|~\.nib' | rev | sort | rev

and p4checkin:

#! /bin/bash

p4 -x - add
p4 submit

The shell script p4files outputs a list of files to be placed under version control. The egrep command automatically excludes the build subdirectory, the .DS_Store files used by the Finder, and backup ~\.nib files. The file list is then sorted with trailing characters most significant, to group file names by extension for ease in editing.

The shell script p4checkin places the files listed in standard input under version control.

Change directories into the directory to be placed under version control. Run p4files by itself, and stare at its output. The idea here is to very carefully delete unwanted lines. You want this file list to consist exactly of those files you would want to be given, if you were requesting a fresh, stripped copy of the project. Put differently, you want to list every file that you manually edit one-by-one, but no file that can or will be automatically recreated by other tools. Later, you'll get a chance to edit each changelist that you submit, but Perforce will doggedly keep reminding you about files you promised to add at this stage, until you fix the situation using p4 revert. Don't cave in and let Perforce control files that are automatically created; one way or another, the continual bookkeeping requirements for doing so will wear you down. It is best to very carefully edit this file list now.

For simple projects, this file list will already be correct, but if you are doing some odd things it will take a combination of experience and thought to decide how to prune it. For example, I use annote to automatically generate HTML documentation files which are interspersed with my source files; I do not place these HTML files under version control.

One way to remove unwanted files from this file list is to physically delete them, and then run p4files again. When you are satisfied, pipe p4files into p4checkin, using the command

p4files | p4checkin

If you instead want to leave some files intact, but not place them under version control, then use the following commands, replacing bbedit with your editor of choice:

p4files > temp
bbedit temp
p4checkin < temp

p4checkin runs p4 submit, which brings up a Perforce Change Specification for editing. Edit the Description field, starting with the name of this project; Perforce doesn't keep the change descriptions for different projects separate, so this convention will be helpful later. Save the specification and quit the editor to end the command.

Now, open the project file bundle for editing, using the command

p4 edit Test.xcode/...

Here, Test should be changed to the current project. Open the project in Xcode. Set the SCM system to Perforce, by double-clicking on the source file group in the "Groups & Files" column, to bring up the Project Info window. Choose Perforce, click on "Edit", and fill in P4PORT and P4CLIENT to match the server settings. After these settings are correct, click the "Enable SCM" box to make Perforce active. Now, select the .pbxproj and .pbxuser files in the SCM group in the "Groups & Files" column, and choose SCM > Commit Changes.

Now, the project is set up for using the SCM menu within XCode.

If you are using nib files, you will notice an interaction problem between Perforce and XCode: Between them, they drop the ball on handling bundles and file packages. They're squabbling over the fact that we're hosed if we ever have to merge two sets of changes to a nib file, but there are more graceful ways they could have resolved this. See Working With Bundles and File Packages.

Ignore the question mark that XCode shows for the status of nib files; their contents were indeed submitted earlier to Perforce. To work on a nib file, open it for editing using the command line, as we did for the project file bundle:

p4 edit English.lproj/MainMenu.nib/...

Here, replace English.lproj/MainMenu.nib by the name of your nib file. Later, submit changes using the command line:

p4 submit

If you want to make an atomic commit that includes both nib file and other changes, do not use the XCode SCM > Commit Changes menu item. Instead, use the command line, and later, point this out to XCode using the SCM > Refresh SCM Status menu item.

Now, we test to see if the Perforce repository indeed contains everything needed to restore our project. Make sure that you have a backup copy of your project, delete the project directory from the Finder, and recreate it by changing directories to the parent directory of the project, and using the following command:
p4 sync -f Test/...

Again, Test should be changed to the current project. This form of the command avoids synchronizing your entire client workspace, a good way to get hosed if you weren't expecting this. In general, one can easily switch between projects without peril while using one server root directory, but only by learning how to focus commands on the active project.

Now, open the project again in XCode, rebuild it, and make sure that everything is ok.

After playing around for a while, bail out and set up Perforce again from scratch a few times, until everything here becomes second nature. The following command is handy for restoring write permission to everything within the current directory, in case you were overeager on the first try in yielding control to Perforce.

find . -print | xargs chmod +w



Finally, we want to set up a way to automatically launch the Perforce server p4d, when it is not already running. To add p4d to the system startup sequence, see Creating a Startup Item. For my purposes, however, it is cleaner and simpler to just launch p4d from my account.

The following is a shell script that conditionally launches the p4d server, if it is not already running:

#! /bin/bash

if [ $(ps -U me | grep -v grep | grep -c /usr/local/bin/p4d) == 0 ];
then
	/usr/local/bin/p4d -q -p 1666 -r /Volumes/User/Users/me/P4ROOT &
fi

Change me to your account name (the variant ps aux will not work in all contexts), and change /Volumes/User/Users/me/P4ROOT to the location of the Perforce server root directory. Depending on how it is used, this script may not see your environment variables, so spell everything out.

If one is only using Perforce with XCode, one can modify the XCode startup script. See Using Scripts To Customize Xcode. The file ~/Library/Application Support/Apple/Developer Tools/StartupScript can either consist of, or run, the above shell script.

To use a shell script as a login item, see Using Startup Items in Mac OS X and Technical Note TN2065: do shell script in AppleScript. A .command file will open the Terminal application on every login, so this isn't an acceptable option.

In Sicily, there is a train station legendary among travellers for being a black hole that one falls into and never leaves. In the Mac programming universe, that black hole is AppleScript. As a rule, I have had only bad experiences with AppleScript. Here, whenever any shell script in the chain of execution started by an AppleScript application executes p4d, the Perforce server launches successfully, but the AppleScript application fails to quit.

Here is how one executes a shell script from within AppleScript. Replace /Volumes/User/Users/me/bin/p4launch with the full path to your script:

do shell script "/Volumes/User/Users/me/bin/p4launch"

In a tiny fraction of the time one would waste struggling in vain with AppleScript, one can write a standalone C program that calls a shell script. From XCode, create a new project of type Cocoa Application. Throw out the nib file, and replace the contents of main.m with

int main(int argc, char *argv[])
{
	system( "/Volumes/User/Users/me/bin/p4launch" );
	return 0;
}

Replace /Volumes/User/Users/me/bin/p4launch with the full path of the script that you want to execute. Compile and test a Deployment build, and be done in under a minute. To launch this application on every login, add it to System Preferences > Accounts > Startup Items, and check the Hide box.