How to read your build timeline and find what's slow

A practical guide to measuring a slow build, from the activity bar to the timing summary and the build timeline, and what each slow pattern usually means.

“The build is slow” isn’t something you can fix. “This script runs on every build” or “this one file takes twenty seconds to type-check” is. The tools to tell the two apart are already in the IDE, and most of them are one setting or one menu item away. This guide goes from the quickest check to the most detailed one. The commands in it are also scripts you can run, free on GitHub.

Step 1: See every build’s total time

Turn on the build duration in the activity bar at the top of the window:

defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES

Restart the IDE, and every build shows how long it took. It sounds trivial, but a week of glancing at that number tells you which kinds of change are slow, and that’s where to look first.

Step 2: Measure the same build the same way

A single build time is noisy. Before changing anything, write down a few standard builds and time each one at least three times, keeping the middle value:

  • A build with no changes. Press build twice; time the second one. This is the floor: what the build system costs before any real work.
  • A one-line change inside a function body. The most common edit, and usually the cheapest.
  • A change to a type that many files use. A new property on a shared model, for example. This shows how far a change can spread.

Use the same scheme, the same Simulator and the same configuration each time, and don’t measure while something else is compiling or indexing. From the command line, the shell’s time is enough:

time xcodebuild build -scheme MyApp \
  -destination "platform=iOS Simulator,name=iPhone 17" -quiet

Keep the numbers. Every change you make later is judged against them.

To do all of this in one command, use build_baseline.py from the same free repo. It finds the workspace, scheme and Simulator, makes the two edits for you, times each of the three builds three times and prints the medians, with each build’s timing summary (step 3). It edits only files that git tracks and that have no uncommitted changes, and puts them back when it’s done:

cd path/to/your/app
python3 path/to/faster-builds/scripts/build_baseline.py

It runs eleven builds, and the first one is a full build if nothing is built yet, so on a large app it takes a while. Coding agents often stop a command after a few minutes; to hand it to one, ask it to run the script in the background:

Run python3 path/to/faster-builds/scripts/build_baseline.py --json > baseline.json from the project folder in the background, wait for it to finish, then tell me the three medians and what the timing summaries show.

Step 3: The timing summary

Build with Product › Perform Action › Build With Timing Summary. At the end of the build log, there’s now a summary of how much time each kind of task took in total: compiling Swift sources, linking, running scripts, compiling asset catalogs, signing and so on. From the command line, add -showBuildTimingSummary:

xcodebuild build -scheme MyApp \
  -destination "platform=iOS Simulator,name=iPhone 17" -showBuildTimingSummary

The summary answers the first question: is the time going to the compiler, or to something around it? If script phases or resource steps are a noticeable share of a one-line change, that’s often the easiest win you’ll find.

The totals add up work across all your Mac’s cores, so they can be larger than the build’s wall-clock time. Use them to compare kinds of work, not as a stopwatch.

Step 4: The build timeline

The timeline shows the same build as lanes of tasks over time. Open the build in the report navigator, then show the assistant editor (Editor › Assistant) to see the timeline beside the log. Each lane is a parallel slot on your Mac, and each block is a task: a compile, a link, a script, a copy, a signature.

What to look for:

  • Long single blocks. One compile task much longer than its neighbours usually means a file that is slow to type-check. Select it to see which file it is.
  • Narrow stretches. When most lanes are empty and one task is running, everything else is waiting for it. That task, or the chain of targets leading to it, sets the build’s length. Splitting a large target, or removing a dependency that isn’t needed, can let more work run side by side.
  • The same script every time. A script block that shows up in a no-change build is running when it doesn’t need to.
  • A long link at the end. Linking can’t start until everything it links is compiled, and it works on the whole app. If it’s a large part of a one-line change, the app’s size and the number of libraries it links are the levers.

Step 5: Turn what you found into fixes

What you see What it usually means What to try
A script runs in a no-change build, and the log warns that it “will be run during every build” The script phase declares no outputs Add its input and output files, or untick “Based on dependency analysis” only for scripts that truly must always run
One file’s compile is much longer than others Slow type checking in that file Add -Xfrontend -warn-long-function-bodies=200 and -Xfrontend -warn-long-expression-type-checking=200 to Other Swift Flags (Debug) and fix what it flags, usually with an explicit type or a smaller expression
A small change to a shared type recompiles much of the app Many files depend on that declaration Move widely used types behind a small, stable interface, or into their own module
Most lanes idle while one target compiles A dependency chain sets the pace Check whether each target dependency is really needed; split very large targets
Asset catalogs or other resources rebuild every time Something touches them on each build Find the script or generated file that changes them

What this won’t show you

The timeline shows what ran, not whether it had to. A one-line change that recompiles one file, links the app, signs it and installs it can still be a long wait on a large app, even when every task in the timeline is “necessary”. Measuring tells you how far fixes like these can take you, and where the floor is.

For the reasons a small change can mean a big build, read why a one-line change rebuilds so much of a big app.