A brief look at the Xcode project format

Let’s take a quick little tour of the Xcode project file format. Anyone doing Mac OS X or iOS development in a team environment (i.e. just about everybody) has had to deal with the joy of merging project changes in version control (subversion, cvs, git, you name it).

An Xcode project, of course, is actually a package, a directory with bundled files within. A typical project might look like this:


MyProject.xcodeproj/
	danwr.mode1v3
	danwr.pbxuser
	project.pbxproj

The first two files have your username. If multiple users use the same project, you will have a set of .mode1v3 and .pbxuser files for each user. These files contain various user settings (preferences, really) that are associated with the project, for example:

  • the size and position of the project window
  • which groups are open and which are closed
  • which item in the project is selected
  • …and more

This information is not normally shared among multiple users, so it is neither necessary nor desirable to add these (.mode1v3 and .pbxuser) files to your repository. If you already have, go ahead and remove them (svn remove if you use subversion, for instance). I’ll wait.

Project.pbxproj

This leaves us with the project file itself, project.pbxproj. Oh, look how smug it is, all undecipherable and all. Or… is it? Let’s open up that bad boy in a text editor and see what it looks like. First, the overall layout:


// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 45;
	objects = {
            [[ : snip! : ]]
	};
	rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
}

We start with a comment indicating the text encoding used in the file, UTF-8. Presumably other encodings are possible, however in practice all project files use UTF-8. You will notice other comments (C-style: /* ... */) sprinkled throughout the project file. While presumably Xcode’s lexer handles multi-line comments, Xcode itself does not generate multi-line comments. If one were attempting the read or write project.pbxproj files, the parser would need to be able to handle multi-line comments, while ideally avoiding writing them (unless preserving existing comments).

A set of brackets { } enclose a record of key-value pairs. The keys are:

  • archiveVersion; It is set to 1 in all versions of Xcode that use the file format described here.
  • classes; A list of classes, usually empty.
  • objectVersion; This relates to which object types are used in this project.pbxproj, and which keys are defined. This changes based on the version of Xcode that wrote the project; it is controlled by the “Project Format” popup menu in Xcode’s “Project Info” window. The value 45 corresponds to “Xcode 3.1-compatible”.
  • objects; This is a list (actually, a record, or hash) of objects in the project. This is the meat of the file format.
  • rootObject; This identifies the root object, the object that represents the project itself.

Now let’s take a look at the objects in general. There is an object for every file, group, target, build phase, and so on. Each object is identified by a UUID. If they are universally-unique, then any number of projects from any number of original machines can be opened at the same time by a single copy of Xcode without any problems. These UUIDs are 12 bytes—24 hexadecimal digits without any separating hyphens. Each object has a set of properties, one of which, “isa” specifies the class of the object. The other properties are determined by this class.

Object classes

The object classes supported depends upon the objectVersion. Here is a partial list:

  • PBXBuildFile
  • PBXFileReference
  • PBXFrameworksBuildPhase
  • PBXGroup
  • PBXNativeTarget
  • PBXProject
  • PBXResourcesBuildPhase
  • PBXSourcesBuildPhase
  • PBXVariantGroup
  • XCBuildConfiguration
  • XCConfigurationList

The most important one here is PBXFileReference; every file referenced by the project (source files, headers, libraries, frameworks, xcconfig files, other projects…)1 is represented by a PBXFileReference.


	089C165DFE840E0CC02AAC07 /* English */ = {
		isa = PBXFileReference; 
		fileEncoding = 4; 
		lastKnownFileType = text.plist.strings; 
		name = English; 
		path = English.lproj/InfoPlist.strings; 
		sourceTree = ""; 
	};

There are two different types of files: input (e.g. source files) and output (e.g. the output application or library). lastKnownFileType is present and set for input files. The list of possible values can be found in Xcode in the file “Get Info” window. Additional values are, of course, possible.


	8D1107320486CEB800E47090 /* MyProject.app */ = {
		isa = PBXFileReference; 
		explicitFileType = wrapper.application; 
		includeInIndex = 0; 
		path = MyProject.app; 
		sourceTree = BUILT_PRODUCTS_DIR; 
	};

Output files always have the explicitFileType key, includeInIndex (typically set to 0, or false, for binaries and packages). Both input and output files have a path and sourceTree specified. Path names are not normally quoted unless necessary (for example, if the pathname includes a semicolon, space, or other special character).

In part 2, I’ll look at PBXVariantGroup, XCBuildConfiguration, and XCConfigurationList.


1 There are exceptions; two of the most well-known are Info.plist files and precompiled header source (.pch) files. These two files are always identified either by absolute pathname, or by a path relative to the .xcodeproj project.

Tags: , ,