What does an Xcode scheme control, how do test plans and derived data fit in, and how do you use xcodebuild and simulators when something is wrong?

23.7.1 · L2 · Interview

Predict first

A Swift package has no Xcode project. What does xcodebuild -list show for schemes?

// === Package.swift ===
// swift-tools-version:6.0
import PackageDescription
let package = Package(name: "Lib", targets: [.target(name: "Lib"), .testTarget(name: "LibTests", dependencies: ["Lib"])])
// === Sources/Lib/L.swift ===
public func f() {}
// === Tests/LibTests/T.swift ===
import XCTest
  • An automatically generated scheme named Lib-Package
  • No schemes: a package cannot be built by xcodebuild
  • One scheme per source file

Mental model. A scheme is a recipe card, and derived data is the pile of prepared ingredients on the counter. If the dish tastes wrong, check the recipe first, and clear the counter only if the ingredients are stale.

A scheme says what to build and what to do with it, and has an action per task: Build (which targets), Run (which build configuration, the launch arguments, environment variables and the Diagnostics tab where sanitizers, Zombie Objects, Main Thread Checker and Malloc Stack Logging are switched on), Test (which tests, and the test plan), Profile (Instruments, usually a Release configuration), Analyze and Archive. Many “it behaves differently” problems are scheme differences: a different configuration, an argument set on one machine, a sanitizer on, or a shared versus personal scheme (check the shared scheme into source control so everyone and CI use the same one). A test plan (.xctestplan) moves test configuration into a file: which tests, the language and region, environment, code coverage, parallelism, random order and repeat-on-failure, and several configurations of the same tests.

Derived data is where Xcode puts intermediate build products, indexes and logs for each project (by default under ~/Library/Developer/Xcode/DerivedData). It is a cache, so deleting a project’s folder (or the lot) is safe and fixes a long list of strange problems: stale modules, “no such module” after a branch switch, a corrupted index that breaks autocomplete or navigation; the cost is a full rebuild, so do not do it reflexively, and find out why if it recurs. From the command line xcodebuild -list shows the schemes and targets (for a Swift package it generates a scheme such as Lib-Package, as the check below shows), -showBuildSettings prints the resolved settings (Q369), -derivedDataPath puts the cache somewhere clean for CI, build-for-testing and test-without-building separate building from testing, and -resultBundlePath keeps the evidence. For simulators, xcrun simctl lists, boots, erases and resets devices (Erase All Content and Settings clears a bad state), and Window ▸ Devices and Simulators manages devices, runtimes and logs. If a build breaks mysteriously, change one thing at a time: clean the build folder, then derived data, then the simulator state, then check the toolchain and Xcode version.

Xcode and xcodebuild derive a scheme from the package manifest, so packages can be built and tested without a project file.

Common traps

  • Deleting derived data at every error. It hides the cause and costs a full rebuild; find what keeps going stale.
  • A scheme that is not shared. Teammates and CI run something different from what you debugged.

They might follow up with

  • Where do you turn on Address Sanitizer or Zombies? The Run action’s Diagnostics tab in the scheme.
  • How do you run the exact CI build locally? The same xcodebuild command, scheme, destination and derived-data path.

Related questions

  • 23.6.2 How do you reproduce a bug reliably: environment, feature flags, seeded data, launch arguments, simulator…
  • 23.3.1 What do the Address, Thread and Undefined Behavior sanitizers catch, what do they cost, and when do you use…
  • 16.4.1 What are build configurations and build settings, how do xcconfig files layer, and how does #if DEBUG work?
  • 16.4.2 How do you speed up and diagnose slow builds: incremental builds, whole-module optimisation, explicit modules…
  • 22.4.2 What makes a test non-deterministic, and how do you control time zones, locales, randomness, UUIDs and the…