Xcode - xcworkspace and xcodeproj
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.
*.xcworkspace (external)
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:
and the 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, cppget.xcworkspace/contents.xcworkspacedata
:
The document root object is <Workspace>
. The root object has a version
attribute
(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>
and/or <Group>
.
Each 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 vendor
from project
requires ../vendor
).
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
with either group:
or self:
.
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.
*.xcodeproj
Like the workspace, an Xcode project is a bundle containing one or more files. The most important
and only required file is the project.pbxproj
file.
The simplest possible project looks like this
The 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 cppget.xcodeproj/project.pbxproj
,
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 xcode.newid
in
premake/premake-xcode,
and refer to
premake-core/src/host/string_hash.c
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.
Xcode objects
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
- PBXAppleScriptBuildPhase
- PBXBuildFile
- PBXContainerItemProxy
- PBXCopyFilesBuildPhase
- PBXFileReference
- PBXFrameworksBuildPhase
- PBXHeadersBuildPhase
- PBXGroup
- PBXNativeTarget
- PBXProject
- PBXReferenceProxy
- PBXResourcesBuildPhase
- PBXShellScriptBuildPhase
- PBXSourcesBuildPhase
- PBXTargetDependency
- PBXVariantGroup
- XCBuildConfiguration
- XCConfigurationList
PBXProject
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:
The 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
shared.
The 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 mainGroup
.
The 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?)
PBXGroup
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.
PBXFilereference
All physical items are identified by PBXFileReference objects.
A sourceTree
entry is the location where this object can be found. Typically, this is <group>
, meaning
that the location is inside the group containing this PBXFileReference (or the project itself). Sometimes
the 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
begin with sourcecode.cpp.
, and so on. So a C++ source file would be sourcecode.cpp.cpp
, and a C
header file would be sourcecode.c.h
.
Some entries will indicate built objects, like an executable. These have an explicitFileType
entry with a
value such as compiled.mach-o.executable
.
Some entries will indicate external items. These have a lastKnowFileType
of "wrapper.pb-project"
, and a path
pointing at the project itself, located relative to sourceTree
. In this case a sourceTree
of SOURCE_ROOT
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, lastKnownFileType
is wrapper.framework
and 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.
PBXNativeTarget
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 productReference
pointing
to a PBXFileReference that is a compiled.mach-o.executable
, but also by productType
being “command line tool”, com.apple.product-type.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:
PBXBuildPhase
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.
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).
PBXBuildFile
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.
XCConfigurationList
An XCConfigurationList object is just a list of XCBuildConfiguration objects.
The defaultConfigurationName
key indicates which configuration, by name, is the one to
pick as the default when a project is opened for the first time.
The defaultConfigurationIsVisible
key, if set to non-zero, shows the default configuration
information in the UI.
XCBuildConfiguration
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
- project
- xcconfig file for project
- target
- xcconfig file for target
Value assignment is performed in the following order (lowest to highest precedence):
- platform defaults
- xcconfig for project file
- project
- xcconfig for target
- target
The distinction is due to handling of inheritance. TBD: explain.
*.xcworkspace (internal)
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 project.xcworkspace
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:
where project.xcworkspace/contents.xcworkspacedata
look like this:
The self
tag points to the top level of the one.xcodeproj
directory. The project.pbxproj
is
as detailed above.
To-do
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:
- https://github.com/mattt/MsgPackSerialization/blob/master/MsgPackSerialization.xcworkspace/contents.xcworkspacedata
- https://github.com/MSOpenTech/orc-for-ios/blob/master/orc.xcworkspace/contents.xcworkspacedata
- https://github.com/seatgeek/SGAPI/blob/master/SGAPI.xcworkspace/contents.xcworkspacedata
- http://opensource.apple.com//source/libutil/libutil-30/libutil.xcodeproj/project.xcworkspace/contents.xcworkspacedata
- https://github.com/StefanLage/Location-Generator/blob/master/Location%20Generator.xcodeproj/project.xcworkspace/contents.xcworkspacedata
A few Xcode projects to test a xcodeproj parser against:
Reference
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 dive into Xcode projects and workspaces.
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.
- appsquickly/XcodeEditor.
CocoaPods. This is a package manager for Swift and Objective-C projects. Source code is on Github at CocoaPods/CocoaPods.
Old-Style ASCII Property Lists. Except this doesn’t seem to match observed project.pbxproj files.
Xcode Build Settings Reference.