Project Structure & the Build System

Xcode: The IDE Itself

Chapter 2 ยท Project Structure & the Build System

Chapter 1 described Xcode as a bundle of tools. This chapter covers the part every other tool depends on: how Xcode organizes the things you build and how it turns them into an app. It sets out projects, workspaces, targets, schemes and build configurations, then the Swift Package Manager as the way Xcode handles dependencies. The definitions come from Apple's archived Xcode concepts documentation and general references. Apple's current documentation pages did not return usable text when I fetched them, so I have marked what I could not check.

Projects and Workspaces

An Xcode project is a repository for all the files, resources and information needed to build one or more software products. It contains one or more targets, which specify how to build products. A workspace is an Xcode document that groups projects and can contain any number of them.

ConceptWhat it isDetail from the sources
ProjectThe files and settings for building one or more productsHolds one or more targets
WorkspaceA document that groups projectsAll projects in a workspace are built in the same directory, the workspace build directory, so files in one project can be visible to another. A project can belong to several workspaces and keeps its own identity.
TargetA single product you buildHas its own build configurations and settings; for example an app or an extension to it
SchemeA collection of targets to build, a configuration to use, and tests to runYou can have many; only one is active at a time
Project-Level and Workspace-Level Schemes
Apple's archived documentation says schemes can be stored at project level, where they are available in every workspace that includes the project, or at workspace level, where they are available only in that workspace. Selecting an active scheme also selects a run destination, which the same page describes as the architecture of the hardware the products are built for. That page dates from 2011, so treat its details as background, not as the current interface.

Build Configurations

A project can define more than one build configuration, typically Debug and Release. Each configuration inherits the build settings from an individual target, but can override any of them so the override applies only when building with that configuration. That is how one project can produce a debug build and a release build from the same source with different compiler settings.

Scheme Chooses the Configuration
A scheme names which configuration it uses. Changing the active scheme can therefore change how your code is compiled, not only which target is built. When a build behaves differently on your machine and in a colleague's project, comparing the active scheme and configuration is a reasonable first check.

Building From the Command Line

Xcode's builds can be run without the interface. To build a workspace with xcodebuild, you pass both the -workspace and -scheme options, and the scheme's parameters control which targets are built and how. The names below are placeholders for your own.

xcodebuild -workspace MyApp.xcworkspace -scheme MyApp build

I have not run this against a project for this chapter, so treat it as the documented shape of the command rather than a tested example. For a project without a workspace, xcodebuild also has a -project option; I did not verify its exact behavior here.

Dependencies: Swift Package Manager

Swift Package Manager integration was added to Xcode in Xcode 11. In Xcode you add a package through File → Add Package Dependencies, where you choose a dependency rule. The rule "Up to Next Major Version" is the default and the commonly recommended one: Xcode takes the given minimum version and updates until the first digit increases, so bug fixes and features arrive while breaking changes are avoided. "Up to Next Minor Version" stops when the middle number increases.

RuleUpdates untilUse it when
Up to Next MajorThe first digit increases (from 1.x.x)The default; you trust the author to follow semantic versioning
Up to Next MinorThe middle number increases (from 1.3.x)You want fewer changes between updates

The Package.resolved file records the result of dependency resolution: each time you add a package, the Swift Package Manager downloads the newest version that meets your requirement and records the exact version number in that file. That is what makes builds reproducible across machines. The sources I found describe the file in the context of a Swift package's top-level directory; where Xcode stores it for an app project, and whether it should be committed to version control, I did not verify.

Two Other Options I Did Not Cover
Xcode also offers exact-version, branch and commit rules for a dependency. I could not confirm their wording or menu labels from a fetched source, so they are left out. CocoaPods, the older dependency tool, is only touched on in the sources as the thing the Swift Package Manager is compared with.

What This Means for a Team

  1. The project file, the workspace and the shared schemes are what a teammate needs to build the same product, so decide which of them go into version control.
  2. Dependencies live in the project through the Swift Package Manager, with exact versions recorded in Package.resolved.
  3. Build differences usually trace back to the scheme, the target or the configuration, so those are the first places to look.

Hands-On Exercises

Exercise 1

Explain in a few sentences the relationship between a workspace, a project, a target and a scheme, using one imaginary app that has a widget extension.

๐Ÿ“„ View solution
Exercise 2

A release build crashes but the debug build does not. Using this chapter's material, list the three things you would compare first and say what each could reveal.

๐Ÿ“„ View solution
Exercise 3

A package is at version 1.3.2 and its author releases 1.4.0 and 2.0.0. Under "Up to Next Major" and under "Up to Next Minor," say which versions Xcode would accept, and name one thing this chapter could not verify about how a project records the result.

๐Ÿ“„ View solution

Chapter 2 Quick Reference

  • Project: files and settings for one or more products, with one or more targets
  • Workspace: groups projects; a project can be in several workspaces
  • Target: one product, with its own build settings and configurations
  • Scheme: targets to build, a configuration, tests to run; only one active at a time
  • Configurations: Debug and Release inherit target settings and can override them
  • Command line: xcodebuild with -workspace and -scheme
  • Swift Package Manager: in Xcode since version 11; File → Add Package Dependencies; Up to Next Major is the default; Package.resolved records the exact version
  • Unverified: current Apple documentation pages, exact-version/branch/commit rules, where Package.resolved lives in an app project