Xcode - xcworkspace and xcodeproj - part 2
This is an update of the previous post Xcode - xcworkspace and xcodeproj. I imagine that when I’m done, I’ll have a mini-book.
In the examples below, I used Xcode 6.1.1 to create a command-line tool:
I used all defaults, and a project name of xplore
. I refer to this in places as “using the Xcode wizard”.
Project with embedded workspace
If you create a project from Xcode instead of a workspace (File -> New -> Project), you still get a workspace, it’s just embedded inside your project file. Here’s the project visually:
On disk, the project looks like this (ignoring irrelevant files):
contents.xcworkspacedata
looks like this:
Note that the project location is self:xplore.xcodeproj
. I’m guessing that self
in this context
means the directory containing the opened project file. In this case, we’re literally
pointing to ourself, but this keeps things sane for Xcode, I imagine.
This was the default until Xcode 4 - the workspace file existed, but was hidden from sight. With Xcode 4, the workspace file became a user-visible item so that groups of projects could be more easily navigated and built.
Project with external workspace
If you create a workspace (File -> New -> Workspace), then you have a separate external workspace file. Unlike like with, say, Visual Studio, you aren’t prompted to create a project. But when you now go to create a project, you’ll have a prompt to add it to a currently open workspace:
Visually, there’s little to distinguish workspace+project from workspace-inside-project:
I don’t know why the embedded workspace has ‘?’ marks next to items, and the separate workspace has ‘A’ characters.
On disk, we now have a named workspace visible in the Finder:
contents.xcworkspacedata
looks like this:
Note that the location is now group:xplore.xcodeproj
. This time, our base is the directory containing
the opened workspace file.
Project file walkthrough
The project file xplore.xcodeproj
is identical, whether it is attached to an external
workspace or hosting its own embedded workspace. Let’s walk through it a bit.
This is the on-disk layout, ignoring non-important files:
The project file itself is the project.pbxproj
file inside the xplore.xcodeproj package.
This is the top-level view of a project file, ignoring the contents of the objects
key:
This is using the text version of NeXt/Mac OS X plist files; this format is now obsolete except for Xcode, and I think Xcode 7 is now switching to XML, which is a pity, because the text version is straightforward to read. Plist files have numbers, strings, uuids (which are really just strings of hexadecimal characters), lists and dictionaries.
archiveVersion
is always 1, and classes
is always (?) empty.
objectVersion
indicates the project format. As you can see, this project was made with Xcode 3.2
compatibility:
- 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
objects
is a dictionary containing all the objects in the project, as key
= value
, where value
can be one of the aforementioned number, string, uuid, list or dictionary. The keys in our dictionary
are always uuids. There doesn’t appear to be any rule as to the length of the uuids, I’ve seen both
24-character (12-byte) and 48-character (24-byte) uuids in Xcode projects. I’ve even see 47-character
uuids work (a bug in many versions of Mac Premake up through at least 5.0.0-alpha8). Xcode seems to
be forgiving, as long as the uuids don’t collide.
rootObject
points to a PBXProject
object that is the root for this project. There’s only one
PBXProject
object in our project file, and this is typical.
mainGroup
is the PBXGroup
that is the root of the displayed items. Don’t confuse this with build
instructions; this is purely for visual looks.
Groups can contain files and more groups. The main group contains two more groups:
Each of these groups contain files - or rather, PBXFileReference
objects, which actually hold the
information about files:
For some reason, this is the one dictionary that Xcode puts into a single-line flattened form,
so for readability, I’ve shown the PBXFileReference
items in indented form, instead of what’s in the
Xcode project file. All other items are displayed exactly as they are in the project file.
Since Xcode likes to put helpful comments in the project file, we can see that this matches the GUI display:
If you were to rearrange the order of the items in mainGroup
, then the GUI output would change.
Going back to the PBXProject
object, note that there is one other group that gets top billing, Products:
In Xcode parlance, products are the things that are built. Products are built by targets, and configurations are orthogonal groups of build settings that can be applied to any number of targets or projects.
I assume that Products gets top billing in the PBXProject
object so that the various actions in
Xcode know what to operate on.
The two build-related items in the PBXProject
are buildConfigurationList
, which points to the
project configurations available, and targets
, which is the list of build targets themselves.
In this simple project, we have one build target
This points to a PBXNativeTarget
object, which describes how to build a native Mac OS X binary:
Once again, we have a reference productReference
to the Products group, presumably because that’s
how Xcode finds where to put the built target.
The actual binary type is noted by the productType
key: its value declares this to be a command-line
tool, e.g. com.apple.product-type.tool
.
Note that the target also has a buildConfigurationList
. In this case, this points to configurations
for the target, which are distinct from configurations for the project. So let’s look at configurations
for a bit.
The project configurations are in a XCConfigurationList
:
Xcode defaults to having a Debug configuration and a Release configuration. While these are pretty common, there is no standard configuration. Do not count on all projects having Debug and Release configs.
Xcode’s project wizard creates a pretty comprehensive Debug and Release configuration for you. And in point of fact, you’ll find out that a lot of these settings can be omitted, because Xcode defaults are pretty reasonable. This is a matter of style. Note that defining everything possible is many hundreds of settings; you might get tired after a bit.
By comparison, target configurations are much leaner, and are usually just about settings
related to the target itself. This is the XCConfigurationList
for target settings
And here is the Debug target configuration:
The variable $(TARGET_NAME)
contains the value from the name
key in PBXNativeTarget
, and this
in turn is creating an environment variable that is passed to the compiler when it runs. In the GUI
configuration editor for target, it looks like this:
And I have turned on two settings in the GUI, Show Setting Names and Show Definitions, to see the underlying names instead of “human-readable” ones:
Returning to the PBXNativeTarget
object, the most important part from the point of actually
doing stuff is the buildPhases
key, which is a list of build phases.
These are all magic singleton objects. There are a number of possible phases, not all projects have all phases. Here are the possible phases I know about
- PBXAppleScriptBuildPhase
- PBXCopyFilesBuildPhase
- PBXFileReference
- PBXFrameworksBuildPhase
- PBXHeadersBuildPhase
- PBXResourcesBuildPhase
- PBXShellScriptBuildPhase
- PBXSourcesBuildPhase
The two most important ones are PBXSourcesBuildPhase
for building your own sources, and then
PBXFrameworksBuildPhase
for linking in external libraries and frameworks. In our Xcode wizard
generated project, we also have a PBXCopyFilesBuildPhase
, although this is a legacy of how
Unix command-line tools are built (it copies man files into /usr/share/man/man1/
, but in vain).
We’ll ignore a Copy Files phase for now.
If you want to see this in the GUI, select a target (in our case, xplore) and click on the Build Phases tab. Since we have no files to copy, Copy Files says “0 items”, and since we have no frameworks or libraries to link against yet, Link Binary With Libraries also says “0 items”.
Since this is a small project, our Sources Build phase is short
We have one file to build, indicated with a PBXBuildFile
object in the files
list in the
PBXSourcesBuildPhase
object.
This in turn points to a PBXFileReference
object. Xcode’s indirection is a little annoying,
but keeps duplication and duplication-related error to a minimum. In this specific case, I’m not sure
what is gained, because the build file item has no extra annotations on it, but maybe other kinds
of objects have some build-specific annotation that goes here.
The Xcode wizard added a Build Frameworks phase for us as a courtesy, but there’s nothing in it:
And, that’s the entire wizard-generated project file.
Adding a library to our project
Let’s add a static library to our workspace. This is File -> New -> Project, and choosing static library:
We will name it xlib, add it to our workspace, and put it in the top group.
Now, let’s look at the workspace. We see a new project added (it’s in a folder because Xcode likes to do things that way, but we could move it around and edit the workspace to match):
At this point, we have a new xlib.xcodeproj that will build a library (assuming we add sources to it), but there are no changes to the xplore.xcodeproj/project.pbxproj file. And if we put code into our static library and built, we would find out that this library is not being linked to from our main command-line binary.
So we add xlib into the Build Frameworks phase:
Now, if we diff the project before and after adding this library to the Build Frameworks phase,
we see a handful of important changes to xplore.xcodeproj/project.pbxproj
:
- a new
PBXBuildFile
entry to link against libxlib.a - a new
PBXFileReference
object pointing to the file libxlib.a - an
files
entry in thePBXFrameworksBuildPhase
object - a libxlib.a entry added to the top
PBXGroup
(which is a weird place to put it) - a library search path added to the
XCBuildConfiguration
for the target