Starting with Xcode 4 and through at least Xcode 6, the internals of the Xcode project have been consistent. The only evolution has been to add new object types to the xcodeproj file.
There are two objects - the workspace (.xcworkspace) and the project (.xcodeproject). As of Xcode 4, the workspace can now be a visible separate object.
An Xcode workspace always exists, and may be external to an .xcodeproj, or embedded within one. We will defer talking about embedded .xcworkspace directories for a bit.
Xcode workspaces are directories with the .xcworkspace extension that the Mac OS X desktop presents as packages. In the package directory, the important file is contents.xcworkspacedata. A workspace is just a list of contained projects; there is no other metadata.
The simplest possible .xcworkspace has an on-disk structure like this:
contents.xcworkspacedata file, containing the actual workspace data, looks like this:
This workspace has nothing in it, but Xcode will open it up. Of course, once you open it up, Xcode
will likely generate other metadata like an
xcshareddata/ directory containing source control
information, and an
xcuserdata/ directory with user interface settings (window positions and
so on). That metadata isn’t used for building, so we’ll ignore it here.
A better example is from cppget, which has an Xcode workspace (albeit currently generated with Premake and not hand-made). The current directory structure for this package is as follows:
This is the contents of the workspace file,
The document root object is
<Workspace>. The root object has a
(always observed to be 1.0, indicating that the workspace file format hasn’t been changing),
and then an array of child nodes of type
FileRef node has a single attribute, which is the location of the contained item.
The value of the attribute is a string with either a “self:” prefix or a “group:” prefix (the
self prefix is used when the .xcworkspace is embedded, see below). In the
case of the “group” prefix, this auto-creates a group containing just this item in the workspace.
Groups can contain Groups or FileRefs, but FileRefs are just leaf nodes.
In the example above, the source code structure looks like this:
explaining the relative paths (getting to
I say “item” because you can put anything in an .xcworkspace. Normally, you put projects into the workspace, but you can also put individual files, or directories. These will only be used for browsing and search; to build, you need an .xcodeproj.
There is no idea of “main” project file. In this case, there is a main project file (cppget.xcodeproj) that builds an executable, and then 6 additional projects that build libraries (each in the form of a C/C++ package). When you open this workspace in Xcode, it in turn reads information from each project referenced in the workspace file. All the data displayed in the GUI comes from each .xcodeproj, the workspace itself contains no metadata.
There are other files in a typical .xcworkspace bundle. Usually, these are user-specific files that hold settings. We’ll defer covering those to some other time, as none of them alter the meaning of the project files themselves.
Adding a project to a workspace
Because a workspace just collects paths to projects, adding a project to a workspace is very simple - just add a FileEntry node pointing to the project.
From C++, we would:
- read the XML with our preferred XML parser
- look to see if there is already a FileRef referring to our project
- if yes, done
- if not, add a new node
Note that we have no “update” ability; either we find the exact path to our new project already in the workspace, or we add it. This means you need to take some care to have canonical paths.
The node we are adding follows the form
e.g. the only data is the value for the
location attribute, and the attribute value is prefixed
So, for example, assuming you were using RapidXML, your code might look like this. I have parsed the workspace into a data structure that has pulled FileRefs out into a vector for easy manipulation, but then the XML itself has to be manipulated to add a new FileRef.
Perhaps a different XML library would have cleaner syntax. It’s a tradeoff.
Like the workspace, an Xcode project is a bundle containing one or more files. The most important
and only required file is the
The simplest possible project looks like this
project.pbxproj file is an Xcode-specific file format using the text plist format, which came
from NeXt and is now only used by legacy programs - and Xcode. Or so the internet claims.
Also interestingly, Xcode by default is reading and writing the ASCII
plist format, but can also read the XML plist format. But everyone seems to be creating the text
version, so we’ll stick with that (because the XML plist format is pretty hard to read).
For a bigger example, let’s look at the .xcodeproj from cppget:
As with .xcworkspace directories, Xcode puts configuration data inside the project directory, which we will ignore, because it doesn’t bear directly on workspaces and projects at the build level.
The root of an Xcode project file is a dictionary. Looking at
we see this:
archiveVersion has always been 1. And
classes is usually empty (always?).
objectVersion indicates the project format::
- 39: something really old, when the project files were called .xcode
- 42: Xcode 2.4
- 44: Xcode 3.0
- 45: Xcode 3.1 compatible
- 46: Xcode 3.2 compatible
- 47: Xcode 6.3 compatible
The most important key is
rootObject, which tells us which object represents the project. The entire
project file is a tree, with
rootObject at the top. The value of rootObject is an Xcode object
identifier that is an index into the
objects dictionary, which is a PBXProject object.
Xcode object identifiers
Objects have a 12-byte or 24-byte identifier, written as 24 or 48 hexadecimal characters; we’ll call them GUIDs here, although this does not imply similarity to other things people call GUIDs. Object GUIDs need to be unique inside a project file, and also need to be unique among the set of project files that are opened at the same time in Xcode. This means an effort should be taken to make them unique across all projects. I don’t know how true that needs to be, because most UUIDs are only used locally to the project that they are found in.
There’s an interesting competing need that Premake satisfies but the Xcode algorithm does not, and that is that if you regenerate a project, it would be nice to have the same GUIDs, so that diffs are minimized (e.g. when checking in to source control).
Premake uses a simple algorithm that is completely deterministic and relies on “paths” being unique.
Each element that gets a GUID has a sequence of strings that creates a virtual path, and this path is
hashed with a variant of the DJB algorithm. For example, creating a target ID uses the path
[projectname, configname, “target”]; this whole thing is turned into a string and hashed to get a
UUID. Since this is deterministic, regenerating the project will create the same UUID. The assumption
is that things that are named the same are the same thing. See uses of
and refer to
for the low-level C code.
Xcode uses the algorithm linked here: PBXProj Identifiers. This basically creates a memorized per-user structure that used with current time each time a new identifer is created. This code was apparently reverse-engineered from DevToolsCore.framework. This is safer, but requires some extra work if you want to minimize change on project regeneration.
All objects in the
objects directory have an
isa that indicates the type of object. As of Xcode 3.2,
there are the following object kinds
The root object points to an instance of PBXProject. Typically, there is just one in an .xcodeproj file. The one for cppget looks like this:
buildConfigurationList key points to a XCConfigurationList object, which is an array
of XCBuildConfiguration objects. Many projects have a Debug and Release configuration, but
there are no mandatory configurations; projects can declare ones specific to their needs.
See below: there are two sets of config, one for projects, and the other for targets. This
is distinguished in the Xcode GUI but can be a subtle difference, since many settings are
mainGroup key points to a PBXGroup object, which is the list of files that are built
for this project. As seen below, this is a list of individual items as well as other groups.
A group is just a decorative container, but Xcode will use standard names for some groups
like Products and Frameworks and Projects. Every file that contributes to the build will
be traced from
targets key points to a list of targets that this project creates. These are:
- PBXNativeTarget: a build target that makes a binary (library or executable)
- PBXAggregateTarget: a build target that aggregates several others
- PBXLegacyTarget: (not supported any more?)
A PBXGroup object is a list of zero or more:
- PBXFileReference: a group can contain a reference to a file; this can be a source file, a framework, a build artifact (like a binary), or even another project.
- PBXReferenceProxy: a group can contain a reference to something from outside the project; this is usually an object built by a dependent project.
- PBXGroup: a group can contain another group, and this shows up as a sub-folder in the UI.
As an example:
This group has the name Products, has its source tree as the group itself, and then has zero or more children. Typically groups are not empty.
All physical items are identified by PBXFileReference objects.
sourceTree entry is the location where this object can be found. Typically, this is
that the location is inside the group containing this PBXFileReference (or the project itself). Sometimes
sourceTree will be a variable like
BUILT_PRODUCTS_DIR, which is either defined in the project itself
or externally (say as a default by Xcode).
The majority of entries will be source code, with a
lastKnownFileType indicating the file type that Xcode
believes the file has. This is a hierarchy, so all source code tags start with
sourcecode.; C++ files
sourcecode.cpp., and so on. So a C++ source file would be
sourcecode.cpp.cpp, and a C
header file would be
Some entries will indicate built objects, like an executable. These have an
explicitFileType entry with a
value such as
Some entries will indicate external items. These have a
"wrapper.pb-project", and a
pointing at the project itself, located relative to
sourceTree. In this case a
indicates a variable set in Xcode pointing to the workspace or project.
Some entries indicate Mac OS X frameworks, either installed by the system (like LDAP.framework), or custom
to the project. In this case,
path points to the path for
the framework relative to
sourceTree. In the example below, the source tree is
SDKROOT, which is
predefined by Xcode to point to the system SDKs.
A PBXNativeTarget objects describes how to build a native target. This is the various build phases, the output name
In this example, this is an executable binary, as indicated by
to a PBXFileReference that is a
compiled.mach-o.executable, but also by
being “command line tool”,
Note that a PBXNativeTarget has its own XCConfigurationList; these are named the same as the project configurations, but contain target-specific information. See below for the cascade order for config.
For example, the debug configuration has this as its target config:
There are at least these 7 kinds of build phases
- PBXAppleScriptBuildPhase: run an AppleScript
- PBXCopyFilesBuildPhase: copy files
- PBXFrameworksBuildPhase: link frameworks
- PBXHeadersBuildPhase: build precompiled headers
- PBXResourcesBuildPhase: build resources
- PBXShellScriptBuildPhase: run an sh shell script
- PBXSourcesBuildPhase: build sources
Of these, the most interesting to us is PBXSourcesBuildPhase.
Without a PBXSourcesBuildPhase, an Xcode project is just an expensive container.
This is just a list of PBXBuildFile entries, with a note as to whether this is done in all builds or just for deployment post-processing (e.g. a PBXCopyFilesBuildPhase would typically have this set).
This is the heart of building something in Xcode. There are several distinct kinds of file builds.
First is source code. This just points to a PBXFileReference object that contains the information actually needed to build it. The indirection is no doubt useful to Apple, since the file entry is also used in a PBXGroup for display purposes. For convenience, we show the PBXBuildFile entry and its associated PBXFileReference entry.
Not everything is a source code file. We also link static libraries built from elsewhere. Note in this case that the reference is to a PBXReferenceProxy, indicating that this file is from a dependent project, which is itself pointed to by a PBXContainerItemProxy, which finally ends up at a PBXFileReference.
I don’t know what
remoteGlobalIDString is, I can’t find that GUID in any project in
my hierarchy. Premake just assigns a value to it based on the hash of the path name (its
idea of the hierarchy to this point).
Frameworks are semi-magic libraries with versioning and header files used with the libraries. For the build phase, this is linking a framework, so it just ends up directly at the PBXFileReference, no indirection needed.
An XCConfigurationList object is just a list of XCBuildConfiguration objects.
defaultConfigurationName key indicates which configuration, by name, is the one to
pick as the default when a project is opened for the first time.
defaultConfigurationIsVisible key, if set to non-zero, shows the default configuration
information in the UI.
An XCBuildConfiguration object contains the settings that describe the configuration. At its minimum, a build configuration looks like this:
This is an example where a name needs to be unique, because, for example, the UI will look up a configuration by name and not by UUID. Although, it must be doing some disambiguation, because in a typical project there are two distinct XCConfigurationList objects, one for the project object, and one for the PBXNativeTarget object.
Any settings not specified here come from Xcode defaults. There is a cascade for configuration. Inheritance is performed in the following order (lowest to highest precedence):
- platform defaults
- xcconfig file for project
- xcconfig file for target
Value assignment is performed in the following order (lowest to highest precedence):
- platform defaults
- xcconfig for project file
- xcconfig for target
The distinction is due to handling of inheritance. TBD: explain.
It is possible to just have an .xcodeproj visible to the desktop, and have a .xcworkspace directory
inside the .xcodeproj directory. In this case, the workspace is always called
and is at the root directory of the project. This is what you get if you use the Xcode wizard to
create a new project first, instead of creating a workspace and adding projects to it.
The simplest possible arrangement is to have the following:
project.xcworkspace/contents.xcworkspacedata look like this:
self tag points to the top level of the
one.xcodeproj directory. The
as detailed above.
Write a DTD for
contents.xcworkspacedata files. And maybe a grammar; are DTDs comprehensive
enough that they can serve as prescriptive grammars? Looks like not really. Or maybe an XML Schema?
Compile a list of more Xcode versions matched up againts
objectVersion. What was before objectVersion 45?
A few Xcode projects to test a xcworkspace parser against:
A few Xcode projects to test a xcodeproj parser against:
There is no formal documentation. Others have done their share of reverse engineering the project format, for their needs. Premake and CMake generate project files, CocoaPods is a package manager for Mac OS/iOS developers, and so on. Here is a collection of what other people have written.
A series of articles on the Xcode project format:
- A brief look at the Xcode project format
- More on the Xcode project format
- Xcode project object UUIDs
- Xcode build configurations
- Xcode project files (bsxtools) - points to Github repo danwr/bsxtools
OpenOffice.org XML File Format 1.0. An example of a complex XML file format.
A number of projects that can manipulate Xcode projects
- XCoder. A no-longer-maintained Ruby project to manipulate Xcode projects. Lots of comments in the source, though.
- Xcodeproj. This is the library used by CocoaPods to create static libraries from scratch for iOS and OSX. Github is https://github.com/CocoaPods/Xcodeproj
- node-xcode. Includes a PEG grammar.
- kronenthaler/mod-pbxproj. Python.
Old-Style ASCII Property Lists. Except this doesn’t seem to match observed project.pbxproj files.