Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Essential Xcodebuild CLI Flags for Faster CI

Most CI build scripts start with a minimal xcodebuild invocation and only accumulate flags after a painful failure. The result works but leaves minutes of build time and several silent failure modes on the table. This guide gives an annotated command, explains each flag in one line, and ends with a short cost worksheet you can run against your own runner bill.

The Annotated xcodebuild Command

Copy this as a starting point; remove flags that do not apply to your project.

#!/usr/bin/env bash
# xcodebuild-ci.sh — pass workspace/scheme/UDID through env; never hardcode
set -euo pipefail

xcodebuild test \
  -workspace "${XCODE_WORKSPACE:?}" \
  -scheme    "${XCODE_SCHEME:?}" \
  -configuration Debug \
  -destination "id=${SIM_UDID:?}" \
  -derivedDataPath "${DERIVED_DATA:-build/DerivedData}" \
  -resultBundlePath "build/results.xcresult" \
  -enableCodeCoverage YES \
  -quiet \
  -jobs "$(sysctl -n hw.logicalcpu)" \
  -maximum-concurrent-test-simulator-destinations 1 \
  ONLY_ACTIVE_ARCH=YES \
  | tee build/xcodebuild.log
BUILD_EXIT=${PIPESTATUS[0]}   # capture xcodebuild's exit code, not tee's
exit "${BUILD_EXIT}"

The xcodebuild tool's option set is documented in Apple's xcodebuild technote TN2339 and in the man xcodebuild page shipped with Xcode. Behaviour of the underlying build system is governed by Apple's build settings reference.

Flag reference — one line each:

Flag / SettingPurpose
-workspace pathUse .xcworkspace when CocoaPods or SPM packages are involved
-scheme nameExplicit scheme; xcodebuild -list prints the available names
-configuration DebugDebug for simulator tests; Release for archive and device builds
-destination "id=UDID"Pin by UDID; name lookups break after simulator runtime updates
-derivedDataPathStable directory you can cache between CI jobs
-resultBundlePathWrites a structured .xcresult for xcresulttool and dashboards
-enableCodeCoverage YESAdds a coverage report inside the result bundle
-quietSuppresses per-file progress; errors still print
-jobs NParallel compile tasks; match the runner's logical CPU count
-maximum-concurrent-test-simulator-destinations NCaps simultaneous simulator instances to control RAM
ONLY_ACTIVE_ARCH=YESCompile only the host architecture for faster simulator builds

Parallelism: -jobs and Concurrent Simulators

-jobs N sets the number of concurrent compile tasks. The default is the logical CPU count, which is right for a dedicated machine but can starve other processes on shared runners. Set it explicitly so the value is visible in logs:

-jobs "$(sysctl -n hw.logicalcpu)"   # dynamic: all CPUs on this machine
-jobs 4                               # static: safe for a 4-vCPU runner

-maximum-concurrent-test-simulator-destinations N limits simultaneous simulators when you pass multiple -destination flags. Two simulators on a 16 GB machine can swap during test runs; keep it at 1 unless you have measured headroom.

Destination Pinning

Name-based strings such as platform=iOS Simulator,name=iPhone 16,OS=18.0 fail when the runtime updates. Resolve the UDID first and pin it:

SIM_UDID=$(xcrun simctl list devices available -j | python3 -c '
import sys, json
runtimes = json.load(sys.stdin)["devices"]
for _, devs in runtimes.items():
    for d in devs:
        if "iPhone" in d["name"] and d["state"] != "unavailable":
            print(d["udid"]); raise SystemExit')

If the lookup returns nothing, the script fails at that step rather than producing a cryptic error from deep inside xcodebuild. For archive jobs, use -destination 'generic/platform=iOS'.

Clean vs Incremental

xcodebuild clean build discards derived data and forces a full rebuild. On a cold runner that is unavoidable; on a warm runner with a persistent -derivedDataPath, omitting clean can cut compile time on large projects. A practical split:

  • PRs / feature branches: omit clean; use the cache.
  • Main / release: always clean; absorb the full cost once per merge.

Cache the derived data path keyed on Podfile.lock + Package.resolved so dependency changes invalidate it.

Output and Exit Codes

Pipe xcodebuild through tee or xcpretty for readable logs, but always capture ${PIPESTATUS[0]}. If you read the pipe's own exit code, you will read the formatter's, which is usually 0 even when xcodebuild exited 65 (test failure) or 70 (internal error). A green build that was actually broken is the single most common silent-failure pattern in iOS CI.

As a secondary check, parse the result bundle after the run:

xcresulttool get --path build/results.xcresult --format json \
  | python3 -c 'import sys,json; r=json.load(sys.stdin); \
    s=r.get("metrics",{}).get("testsFailedCount",{}).get("_value","0"); \
    sys.exit(1 if int(s)>0 else 0)'

Never time a failed build and call it a benchmark. Record wall time only on green runs with the same scheme, destination, and derived-data state.

A Short CI-Cost Worksheet

Pick a representative job and measure three runs:

  1. T_full — wall time with clean build.
  2. T_warm — wall time with warm derived data and -quiet.
  3. R — runs per working day (PRs + main).

Monthly saved minutes ≈ (T_full − T_warm) × R × 21. At a $95/month Figment M-series entry price, a flat month is 730 hours of wall clock; a dedicated runner you control does not meter minutes, so the saving shows up as queue headroom rather than invoice reduction.

Price check (2026-11-10): CloudMac lists its base Mac mini M4 (16 GB / 256 GB) at $99.99/month, and the CloudMac home page advertises a $999/year option on the same base config — effectively $83.25/month, or about $141 less than 12 × $95. Figment's M-series entry price is $95/month; the specific chip generation, RAM, and disk at that price are confirmed in the quote, so compare entry cost rather than assuming identical hardware. Figment's $249/month Managed Build Mac adds CI setup, backups, monitoring, and 30 minutes of monthly support on one M-series Mac — the piece neither of those self-serve plans includes.

Which Figment Plan Fits This Workflow

  • $29 virtual Intel macOS VM (2 vCPU, 8 GiB RAM, 80 GiB disk) runs compatible Intel Xcode toolchains, including Xcode 26 or earlier. It handles the flag audit, scheme inspection, and small Intel-target test runs. No GPU, no audio, and ARM execution and Xcode 27 are out of scope.
  • i3 Mac at $49/month is Intel and request-based; useful when you need a physical Intel host for the same Xcode line.
  • M-series from $95/month is request-based and runs the arm64 simulator natively, which is what ONLY_ACTIVE_ARCH=YES compiles to on Apple silicon.
  • Managed Build Mac at $249/month bundles the M-series host with CI setup, backups, monitoring, and 30 minutes of monthly support.

Start a configuration at Figment mac-hosting or go straight to /mac-hosting/start.


Related guides:


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms