
Xcode Build Fixer
- 3k installs
- 1.2k repo stars
- Updated April 15, 2026
- avdlee/xcode-build-optimization-agent-skill
xcode-build-fixer is an agent skill that applies approved Xcode build optimizations across settings, scripts, Swift code, and SPM, then re-benchmarks to verify wall-clock build improvements.
About
xcode-build-fixer implements approved Xcode build optimization changes and verifies them with structured benchmarks. Core rules require explicit developer approval, one logical fix at a time, re-benchmarking after each pass, and clear reporting of files touched and measured deltas. Fix categories span build settings in project.pbxproj such as DEBUG_INFORMATION_FORMAT dwarf, SWIFT_COMPILATION_MODE singlefile, COMPILATION_CACHE_ENABLE_CACHING, and EAGER_LINKING; script phase guards with input and output file lists; source-level Swift compilation fixes including explicit type annotations, final classes, and smaller SwiftUI bodies; and SPM restructuring to reduce module variants and pin dependencies. The workflow reads an approved plan from .build-benchmark/optimization-plan.md or explicit instructions, applies each change, runs xcodebuild build to confirm compilation, then re-benchmarks with benchmark_builds.py. Reporting emphasizes wall-clock medians for clean, cached clean, and incremental builds, distinguishes best-practice settings from speculative changes, and produces a structured execution report with Kept, Reverted, or Blocked statuses. Developers use it after xcode-build-orc.
- Applies only developer-approved fixes one at a time, then re-benchmarks to verify wall-clock deltas.
- Covers pbxproj build settings, script phase input/output guards, Swift compile fixes, and SPM restructuring.
- Benchmark workflow compares clean, cached clean, and incremental medians via benchmark_builds.py.
- Distinguishes Apple best-practice settings from speculative changes when evaluating regressions.
- Produces structured execution reports with Kept, Reverted, Blocked, and No improvement statuses.
Xcode Build Fixer by the numbers
- 2,968 all-time installs (skills.sh)
- +70 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #48 of 1,048 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
xcode-build-fixer capabilities & compatibility
- Capabilities
- project.pbxproj build setting edits · run script phase input/output guards · swift compilation hotspot refactors · spm module restructuring and dependency pinning · post change benchmark comparison reporting
- Works with
- github
- Use cases
- devops · debugging
What xcode-build-fixer says it does
Only apply changes that have explicit developer approval.
Re-benchmark after applying changes to verify improvement.
Lead with the wall-clock result in plain language
npx skills add https://github.com/avdlee/xcode-build-optimization-agent-skill --skill xcode-build-fixerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3k |
|---|---|
| repo stars | ★ 1.2k |
| Security audit | 3 / 3 scanners passed |
| Last updated | April 15, 2026 |
| Repository | avdlee/xcode-build-optimization-agent-skill ↗ |
How do I safely implement recommended Xcode build fixes and prove they actually reduce clean or incremental build times?
Apply approved Xcode build setting, script phase, Swift compilation, and SPM fixes, then re-benchmark clean and incremental builds to verify wall-clock gains.
Who is it for?
iOS and macOS developers implementing an approved optimization plan from xcode-build-orchestrator or explicit build-setting instructions.
Skip if: Skip when you need initial project analysis without approved fixes; hand off to xcode-project-analyzer or xcode-compilation-analyzer instead.
When should I use this skill?
User has an approved optimization plan or asks to apply specific Xcode build setting, script phase, Swift compilation, or SPM fixes with benchmarking.
What you get
Applied, reviewable build changes with benchmark deltas, execution report status per fix, and clear guidance on keeping or reverting speculative optimizations.
- Applied build fixes
- Benchmark delta report
- Structured execution report
Files
Xcode Build Fixer
Use this skill to implement approved build optimization changes and verify them with a benchmark.
Core Rules
- Only apply changes that have explicit developer approval.
- Apply one logical fix at a time so changes are reviewable and reversible.
- Re-benchmark after applying changes to verify improvement.
- Report exactly what changed, which files were touched, and the measured delta.
- If a change produces no improvement or causes a regression, flag it immediately.
Inputs
The fixer expects one of:
- An approved optimization plan at
.build-benchmark/optimization-plan.mdwith checked approval boxes. - An explicit developer instruction describing the fix to apply (e.g., "set
DEBUG_INFORMATION_FORMATtodwarffor Debug").
When working from an optimization plan, read the approval checklist and implement only the checked items.
Fix Categories
Build Settings
Change project.pbxproj values to match the recommendations in build-settings-best-practices.md.
Typical fixes:
- Set
DEBUG_INFORMATION_FORMAT = dwarffor Debug - Set
SWIFT_COMPILATION_MODE = singlefilefor Debug - Enable
COMPILATION_CACHE_ENABLE_CACHING = YES - Enable
EAGER_LINKING = YESfor Debug - Align cross-target settings to eliminate module variants
When editing project.pbxproj, locate the correct buildSettings block by matching the target name and configuration name. Verify the change with xcodebuild -showBuildSettings after applying.
Script Phases
Fix run script phases that waste time during incremental or debug builds.
Typical fixes:
- Add input and output file declarations so Xcode can skip unchanged scripts.
- Add configuration guards:
[[ "$CONFIGURATION" != "Release" ]] && exit 0for release-only scripts. - Move input/output lists into
.xcfilelistfiles when the list is long. - Enable
Based on dependency analysiswhen inputs and outputs are declared.
Source-Level Compilation Fixes
Apply code changes that reduce type-checker and compiler overhead. See references/fix-patterns.md for before/after patterns.
Typical fixes:
- Add explicit type annotations to complex expressions.
- Break long chained or nested expressions into intermediate typed variables.
- Mark classes
finalwhen they are not subclassed. - Tighten access control (
private/fileprivate) for internal-only symbols. - Extract monolithic SwiftUI
bodyproperties into smaller composed subviews. - Replace deeply nested result-builder code with separate typed helpers.
- Add explicit return types to closures passed to generic functions.
SPM Restructuring
Restructure Swift packages to improve build parallelism and reduce rebuild scope.
Typical fixes:
- Move shared types to a lower-layer module to eliminate circular or upward dependencies.
- Split oversized modules (200+ files) by feature area.
- Extract protocol definitions into lightweight interface modules.
- Remove unnecessary
@_exported importusage. - Align build options across targets that import the same packages to prevent module variant duplication.
- Pin branch-tracked dependencies to tagged versions or commit hashes for deterministic resolution.
Before applying version pin changes:
- Run
git ls-remote --tags <url>to confirm tags exist. If the upstream has no tags, pin to a specific revision hash instead. - Verify the pinned version resolves successfully with
xcodebuild -resolvePackageDependenciesbefore proceeding.
Execution Workflow
1. Read the approved optimization plan or developer instruction. 2. For each approved item, identify the exact files and locations to change. 3. Apply the change. 4. Verify the change compiles: run a quick xcodebuild build to confirm no errors were introduced. 5. After all approved changes are applied, re-benchmark using the same inputs from the original baseline:
python3 scripts/benchmark_builds.py \
--project App.xcodeproj \
--scheme MyApp \
--configuration Debug \
--destination "platform=iOS Simulator,name=iPhone 16" \
--output-dir .build-benchmark6. Compare post-change medians to the baseline and report deltas.
Evaluating Regressions
Not every slower number is a true regression. The fixer must evaluate the full picture before recommending a revert.
Compilation caching trade-off
A change like COMPILATION_CACHE_ENABLE_CACHING = YES may make a standard clean build slightly slower (cache population overhead) while making cached clean builds significantly faster. Since cached clean builds reflect the realistic developer workflow (branch switching, pulling changes, Clean Build Folder with a warm cache), a slower standard clean build paired with a faster cached clean build is a net improvement, not a regression. The same logic applies to any change where the first-time cost is higher but subsequent builds benefit.
Compare all build types
Evaluate standard clean, cached clean (when available), and incremental builds together. A change is only a clear regression if it makes the metrics that matter to the developer's daily workflow worse. If only the cold-start clean build is slower but the developer rarely does truly cold builds, that may be acceptable.
Per-change vs. cumulative
A single change that regresses on its own may still be worth keeping if it enables a larger cumulative improvement (e.g., enabling compilation caching makes one build type slower but unlocks cache benefits for everything else).
Best-practice settings are not revert candidates
Some build settings are Apple's recommended modern defaults. These should be applied and kept regardless of whether the immediate benchmark shows a measurable improvement. They are not speculative optimizations -- they align the project with Apple's toolchain direction and will compound in value over future Xcode releases. The fixer must never recommend reverting these settings based on a single benchmark result.
Best-practice settings that should always be kept once applied:
COMPILATION_CACHE_ENABLE_CACHING = YES-- Apple is actively investing in this; the cache improves with each Xcode release and compounds across real workflowsEAGER_LINKING = YES(Debug) -- allows the linker to overlap with compilationSWIFT_USE_INTEGRATED_DRIVER = YES-- eliminates inter-process scheduling overheadDEBUG_INFORMATION_FORMAT = dwarf(Debug) -- avoids unnecessary dSYM generationSWIFT_COMPILATION_MODE = singlefile(Debug) -- incremental recompilationONLY_ACTIVE_ARCH = YES(Debug) -- no reason to build all architectures locally
When reporting on these settings, use language like: "Applied recommended build setting. No immediate benchmark improvement measured, but this aligns with Apple's recommended configuration and positions the project for future Xcode improvements."
When to recommend revert (speculative changes only)
For changes that are not best-practice settings (e.g., source refactors, linkage experiments, script phase modifications, dependency restructuring):
- If the cumulative pass shows wall-clock regression across all measured build types (standard clean, cached clean, and incremental are all slower), recommend reverting all speculative changes unless the developer explicitly asks to keep specific items for non-performance reasons.
- For each individual speculative change: if it shows no median improvement and no cached/incremental benefit either, flag it with
Recommend revertand the measured delta. - Distinguish between "outlier reduction only" (improved worst-case but not median) and "median improvement" (improved typical developer wait).
- When a change trades off one build type for another (e.g., slower standard clean but faster cached clean), present both numbers clearly and let the developer decide. Frame it as: "Standard clean builds are X.Xs slower, but cached clean builds (the realistic daily workflow) are Y.Ys faster."
Reporting
Lead with the wall-clock result in plain language:
"Your clean build now takes X.Xs (was Y.Ys) -- Z.Zs faster."
"Your incremental build now takes X.Xs (was Y.Ys) -- Z.Zs faster."
Then include:
- Post-change clean build wall-clock median
- Post-change incremental build wall-clock median
- Absolute and percentage wall-clock deltas for both
- Confidence notes if benchmark noise is high
- List of files modified per fix
- Any deviations from the original recommendation
If cumulative task metrics improved but wall-clock did not, say plainly: "Compiler workload decreased but build wait time did not improve. This is expected when Xcode runs these tasks in parallel with other equally long work."
If a fix produced no measurable wall-time improvement, note No measurable wall-time improvement and suggest whether to keep (e.g. for code quality) or revert.
For changes valuable for non-benchmark reasons (deterministic package resolution, branch-switch caching), label them: "No wait-time improvement expected from this change. The benefit is [deterministic builds / faster branch switching / reduced CI cost]."
Note: COMPILATION_CACHE_ENABLE_CACHING has been measured at 5-14% faster clean builds across tested projects (87 to 1,991 Swift files). The benefit compounds in real developer workflows where the cache persists between builds -- branch switching, pulling changes, and CI with persistent DerivedData. The benchmark script auto-detects this setting and runs a cached clean phase for validation.
Execution Report
After the optimization pass is complete, produce a structured execution report. This gives the developer a clear summary of what was attempted, what worked, and what the final state is.
Structure:
## Execution Report
### Baseline
- Clean build median: X.Xs
- Cached clean build median: X.Xs (if applicable)
- Incremental build median: X.Xs
### Changes Applied
| # | Change | Actionability | Measured Result | Status |
|---|--------|---------------|-----------------|--------|
| 1 | Description | repo-local | Clean: X.Xs→Y.Ys, Incr: X.Xs→Y.Ys | Kept / Reverted / Blocked |
| 2 | ... | ... | ... | ... |
### Final Cumulative Result
- Clean build median: X.Xs (was Y.Ys) -- Z.Zs faster/slower
- Cached clean build median: X.Xs (was Y.Ys) -- Z.Zs faster/slower
- Incremental build median: X.Xs (was Y.Ys) -- Z.Zs faster/slower
- **Net result:** Faster / Slower / Unchanged
### Blocked or Non-Actionable Findings
- Finding: reason it could not be addressed from the repoStatus values:
Kept-- Change improved or maintained build times and was kept.Kept (best practice)-- Change is a recommended build setting; kept regardless of immediate benchmark result.Reverted-- Change regressed build times and was reverted.Blocked-- Change could not be applied due to project structure, Xcode behavior, or external constraints.No improvement-- Change compiled but showed no measurable wall-time benefit. Include whether it was kept (for non-performance reasons) or reverted.
Escalation
If during implementation you discover issues outside this skill's scope:
- Project-level analysis gaps: hand off to `xcode-project-analyzer`
- Compilation hotspot analysis: hand off to `xcode-compilation-analyzer`
- Package graph issues: hand off to `spm-build-analysis`
Additional Resources
- For concrete before/after fix patterns, see references/fix-patterns.md
- For build settings best practices, see references/build-settings-best-practices.md
- For the recommendation format, see references/recommendation-format.md
Build Settings Best Practices
This reference lists Xcode build settings that affect build performance. Use it to audit a project and produce a pass/fail checklist.
The scope is strictly build performance. Do not flag language-migration settings like SWIFT_STRICT_CONCURRENCY or SWIFT_UPCOMING_FEATURE_* -- those are developer adoption choices unrelated to build speed.
How To Read This Reference
Each setting includes:
- Setting name and the Xcode build-settings key
- Recommended value for Debug and Release
- Why it matters for build time
- Risk of changing it
Use checkmark and cross indicators when reporting:
[x]-- setting matches the recommended value[ ]-- setting does not match; include the actual value and the expected value
Debug Configuration
These settings optimize for fast iteration during development.
Compilation Mode
- Key:
SWIFT_COMPILATION_MODE - Recommended:
singlefile(Xcode UI: "Incremental"; or unset -- Xcode defaults to singlefile for Debug) - Why: Single-file mode recompiles only changed files.
wholemodulerecompiles the entire target on every change. - Risk: Low
Swift Optimization Level
- Key:
SWIFT_OPTIMIZATION_LEVEL - Recommended:
-Onone - Why: Optimization passes add significant compile time. Debug builds do not benefit from runtime speed improvements.
- Risk: Low
GCC Optimization Level
- Key:
GCC_OPTIMIZATION_LEVEL - Recommended:
0 - Why: Same rationale as Swift optimization level, but for C/C++/Objective-C sources.
- Risk: Low
Build Active Architecture Only
- Key:
ONLY_ACTIVE_ARCH(BUILD_ACTIVE_ARCHITECTURE_ONLY) - Recommended:
YES - Why: Building all architectures doubles or triples compile and link time for no debug benefit.
- Risk: Low
Debug Information Format
- Key:
DEBUG_INFORMATION_FORMAT - Recommended:
dwarf - Why:
dwarf-with-dsymgenerates a separate dSYM bundle which adds overhead. Plaindwarfembeds debug info directly in the binary, which is sufficient for local debugging. - Risk: Low
Enable Testability
- Key:
ENABLE_TESTABILITY - Recommended:
YES - Why: Required for
@testable import. Adds minor overhead by exporting internal symbols, but this is expected during development. - Risk: Low
Active Compilation Conditions
- Key:
SWIFT_ACTIVE_COMPILATION_CONDITIONS - Recommended: Should include
DEBUG - Why: Guards conditional compilation blocks (e.g.,
#if DEBUG) and ensures debug-only code paths are included. - Risk: Low
Eager Linking
- Key:
EAGER_LINKING - Recommended:
YES - Why: Allows the linker to start work before all compilation tasks finish, reducing wall-clock build time. Particularly effective for Debug builds where link time is a meaningful fraction of total build time.
- Risk: Low
Release Configuration
These settings optimize for production builds.
Compilation Mode
- Key:
SWIFT_COMPILATION_MODE - Recommended:
wholemodule - Why: Whole-module optimization produces faster runtime code. Build time is secondary for release.
- Risk: Low
Swift Optimization Level
- Key:
SWIFT_OPTIMIZATION_LEVEL - Recommended:
-Oor-Osize - Why: Produces optimized binaries.
-Osizetrades some speed for smaller binary size. - Risk: Low
GCC Optimization Level
- Key:
GCC_OPTIMIZATION_LEVEL - Recommended:
s - Why: Optimizes C/C++/Objective-C for size, matching the typical release expectation.
- Risk: Low
Build Active Architecture Only
- Key:
ONLY_ACTIVE_ARCH - Recommended:
NO - Why: Release builds must include all supported architectures for distribution.
- Risk: Low
Debug Information Format
- Key:
DEBUG_INFORMATION_FORMAT - Recommended:
dwarf-with-dsym - Why: dSYM bundles are required for crash symbolication in production.
- Risk: Low
Enable Testability
- Key:
ENABLE_TESTABILITY - Recommended:
NO - Why: Removes internal-symbol export overhead from release builds. Testing should use Debug configuration.
- Risk: Low
General (All Configurations)
Compilation Caching
- Key:
COMPILATION_CACHE_ENABLE_CACHING - Recommended:
YES - Why: Caches compilation results for Swift and C-family sources so repeated compilations of the same inputs are served from cache. The biggest wins come from branch switching and clean builds where source files are recompiled unchanged. This is an opt-in feature. The umbrella setting controls both
SWIFT_ENABLE_COMPILE_CACHEandCLANG_ENABLE_COMPILE_CACHEunder the hood; those can be toggled independently if needed. - Measurement: Measured 5-14% faster clean builds across tested projects (87 to 1,991 Swift files). The benefit compounds in real developer workflows where the cache persists between builds -- branch switching, pulling changes, and CI with persistent DerivedData -- though the exact savings depend on how many files change between builds.
- Risk: Low -- can also be enabled via per-user project settings so it does not need to be committed to the shared project file.
Integrated Swift Driver
- Key:
SWIFT_USE_INTEGRATED_DRIVER - Recommended:
YES - Why: Uses the integrated Swift driver which runs inside the build system process, eliminating inter-process overhead for compilation scheduling. Enabled by default in modern Xcode but worth verifying in migrated projects.
- Risk: Low
Clang Module Compilation
- Key:
CLANG_ENABLE_MODULES - Recommended:
YES - Why: Enables Clang module compilation for C/Objective-C sources, caching module maps on disk instead of reprocessing headers on every import. Eliminates redundant header parsing across translation units.
- Risk: Low
Explicit Module Builds
- Key:
SWIFT_ENABLE_EXPLICIT_MODULES(C/ObjC enabled by default in Xcode 16+; for Swift use_EXPERIMENTAL_SWIFT_EXPLICIT_MODULES) - Recommended: Evaluate per-project
- Why: Makes module compilation visible to the build system as discrete tasks, improving parallelism and scheduling. Reduces redundant module rebuilds by making dependency edges explicit. Some projects see regressions due to the overhead of dependency scanning, so benchmark before and after enabling.
- Risk: Medium -- test thoroughly; currently experimental for Swift targets.
Cross-Target Consistency
These checks find settings differences between targets that cause redundant build work.
Project-Level vs Target-Level Overrides
Build-affecting settings should be set at the project level unless a target has a specific reason to override. Unnecessary per-target overrides cause confusion and can silently create module variants.
Settings to check for project-level consistency:
SWIFT_COMPILATION_MODESWIFT_OPTIMIZATION_LEVELONLY_ACTIVE_ARCHDEBUG_INFORMATION_FORMAT
Module Variant Duplication
When multiple targets import the same SPM package but compile with different Swift compiler options, the build system produces separate module variants for each combination. This inflates SwiftEmitModule task counts.
Check for drift in:
SWIFT_OPTIMIZATION_LEVELSWIFT_COMPILATION_MODEOTHER_SWIFT_FLAGS- Target-level build settings that override project defaults
Out of Scope
Do not flag the following as build-performance issues:
SWIFT_STRICT_CONCURRENCY-- language migration choiceSWIFT_UPCOMING_FEATURE_*-- language migration choiceSWIFT_APPROACHABLE_CONCURRENCY-- language migration choiceSWIFT_ACTIVE_COMPILATION_CONDITIONSvalues beyondDEBUG(e.g.,WIDGETS,APPCLIP) -- intentional per-target customization
Checklist Output Format
When reporting results, use this structure:
### Debug Configuration
- [x] `SWIFT_COMPILATION_MODE`: `singlefile` (recommended: `singlefile`)
- [ ] `DEBUG_INFORMATION_FORMAT`: `dwarf-with-dsym` (recommended: `dwarf`)
- [x] `SWIFT_OPTIMIZATION_LEVEL`: `-Onone` (recommended: `-Onone`)
...
### Release Configuration
- [x] `SWIFT_COMPILATION_MODE`: `wholemodule` (recommended: `wholemodule`)
...
### General (All Configurations)
- [ ] `COMPILATION_CACHE_ENABLE_CACHING`: `NO` (recommended: `YES`)
...
### Cross-Target Consistency
- [x] All targets inherit `SWIFT_OPTIMIZATION_LEVEL` from project level
- [ ] `OTHER_SWIFT_FLAGS` differs between Stock Analyzer and StockAnalyzerClip
...Fix Patterns
Concrete before/after examples for each fix category. Reference this when applying approved changes to ensure consistency.
Build Settings Fixes
Debug Information Format (Debug)
Before (project.pbxproj):
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";After:
DEBUG_INFORMATION_FORMAT = dwarf;Compilation Mode (Debug)
Before:
SWIFT_COMPILATION_MODE = wholemodule;After:
SWIFT_COMPILATION_MODE = singlefile;Enable Compilation Caching
Before (setting absent or):
COMPILATION_CACHE_ENABLE_CACHING = NO;After:
COMPILATION_CACHE_ENABLE_CACHING = YES;Enable Eager Linking (Debug)
Before (setting absent):
After:
EAGER_LINKING = YES;Script Phase Fixes
Add Configuration Guard
Before:
# Upload dSYMs to crash reporter
./scripts/upload-dsyms.shAfter:
# Upload dSYMs to crash reporter
[[ "$CONFIGURATION" != "Release" ]] && exit 0
./scripts/upload-dsyms.shAdd Input/Output Declarations
When a script has no declared inputs or outputs, Xcode runs it on every build. Declare them in the build phase or use .xcfilelist files for long lists.
Before (in Xcode build phase):
Input Files: (none)
Output Files: (none)After:
Input Files:
$(SRCROOT)/scripts/generate-constants.sh
$(SRCROOT)/Config/constants.json
Output Files:
$(DERIVED_FILE_DIR)/GeneratedConstants.swiftSource-Level Fixes
Add Explicit Type Annotations
Before:
let result = items.map { $0.value }.filter { $0 > threshold }.reduce(0, +)After:
let mapped: [Double] = items.map { $0.value }
let filtered: [Double] = mapped.filter { $0 > threshold }
let result: Double = filtered.reduce(0, +)Break Complex Expressions
Before:
let config = try JSONDecoder().decode(
AppConfig.self,
from: Data(contentsOf: Bundle.main.url(forResource: "config", withExtension: "json")!)
)After:
let configURL: URL = Bundle.main.url(forResource: "config", withExtension: "json")!
let configData: Data = try Data(contentsOf: configURL)
let config: AppConfig = try JSONDecoder().decode(AppConfig.self, from: configData)Mark Classes Final
Before:
class NetworkService {
func fetchData() async throws -> Data { ... }
}After:
final class NetworkService {
func fetchData() async throws -> Data { ... }
}
Only apply when the class is not subclassed anywhere in the project. Search for : NetworkService and class ... : NetworkService before marking final.
Tighten Access Control
Before:
class ViewModel {
var internalState: State = .idle
func processQueue() { ... }
}After:
class ViewModel {
private var internalState: State = .idle
private func processQueue() { ... }
}Apply private when the symbol is only used within the same declaration. Apply fileprivate when used within the same file but outside the declaration.
Extract SwiftUI Subviews
Before:
struct ContentView: View {
var body: some View {
VStack {
HStack {
Image(systemName: "person")
Text(user.name)
Spacer()
Button("Edit") { showEdit = true }
}
List(items) { item in
HStack {
Text(item.title)
Spacer()
Text(item.subtitle)
.foregroundStyle(.secondary)
}
}
}
}
}After:
struct ContentView: View {
var body: some View {
VStack {
UserHeaderView(user: user, showEdit: $showEdit)
ItemListView(items: items)
}
}
}
struct UserHeaderView: View {
let user: User
@Binding var showEdit: Bool
var body: some View {
HStack {
Image(systemName: "person")
Text(user.name)
Spacer()
Button("Edit") { showEdit = true }
}
}
}
struct ItemListView: View {
let items: [Item]
var body: some View {
List(items) { item in
ItemRowView(item: item)
}
}
}
struct ItemRowView: View {
let item: Item
var body: some View {
HStack {
Text(item.title)
Spacer()
Text(item.subtitle)
.foregroundStyle(.secondary)
}
}
}Add Explicit Closure Return Types
Before:
let handler = { value in
guard let result = try? process(value) else { return nil }
return result.transformed()
}After:
let handler: (InputType) -> OutputType? = { (value: InputType) -> OutputType? in
guard let result = try? process(value) else { return nil }
return result.transformed()
}SPM Restructuring Fixes
Extract Shared Types to Lower-Layer Module
Before (Package.swift):
.target(name: "FeatureA", dependencies: ["FeatureB"]),
.target(name: "FeatureB", dependencies: ["FeatureA"]),After:
.target(name: "SharedContracts", dependencies: []),
.target(name: "FeatureA", dependencies: ["SharedContracts"]),
.target(name: "FeatureB", dependencies: ["SharedContracts"]),Move the shared protocols and types into SharedContracts so both features depend downward instead of on each other.
Extract Interface Module
Before:
.target(name: "Networking", dependencies: ["Models"]),
.target(name: "FeatureA", dependencies: ["Networking"]),
.target(name: "FeatureB", dependencies: ["Networking"]),After:
.target(name: "NetworkingInterface", dependencies: []),
.target(name: "Networking", dependencies: ["NetworkingInterface", "Models"]),
.target(name: "FeatureA", dependencies: ["NetworkingInterface"]),
.target(name: "FeatureB", dependencies: ["NetworkingInterface"]),Feature modules compile against the lightweight interface without waiting for the full implementation to build.
Recommendation Format
All optimization skills should report recommendations in a shared structure so the orchestrator can merge and prioritize them cleanly.
Required Fields
Each recommendation should include:
titlewait_time_impact-- plain-language statement of expected wall-clock impact, e.g. "Expected to reduce your clean build by ~3s", "Reduces parallel compile work but unlikely to reduce build wait time", or "Impact on wait time is uncertain -- re-benchmark to confirm"actionability-- classifies how fixable the issue is from the project (see values below)categoryobserved_evidenceestimated_impactconfidenceapproval_requiredbenchmark_verification_status
Actionability Values
Every recommendation must include an actionability classification:
repo-local-- Fix lives entirely in project files, source code, or local configuration. The developer can apply it without side effects outside the repo.package-manager-- Requires CocoaPods or SPM configuration changes that may have broad side effects (e.g., linkage mode, dependency restructuring). These should be benchmarked before and after.xcode-behavior-- Observed cost is driven by Xcode internals and is not suppressible from the project. Report the finding for awareness but do not promise a fix.upstream-- Requires changes in a third-party dependency or external tool. The developer cannot fix it locally.
Suggested Optional Fields
scopeaffected_filesaffected_targetsaffected_packagesimplementation_notesrisk_level
JSON Example
{
"recommendations": [
{
"title": "Guard a release-only symbol upload script",
"wait_time_impact": "Expected to reduce your incremental build by approximately 6 seconds.",
"actionability": "repo-local",
"category": "project",
"observed_evidence": [
"Incremental builds spend 6.3 seconds in a run script phase.",
"The script runs for Debug builds even though the output is only needed in Release."
],
"estimated_impact": "High incremental-build improvement",
"confidence": "High",
"approval_required": true,
"benchmark_verification_status": "Not yet verified",
"scope": "Target build phase",
"risk_level": "Low"
}
]
}Markdown Rendering Guidance
When rendering for human review, preserve the same field order:
1. title 2. wait-time impact 3. actionability 4. observed evidence 5. estimated impact 6. confidence 7. approval required 8. benchmark verification status
That makes it easier for the developer to approve or reject specific items quickly.
Verification Status Values
Recommended values:
Not yet verifiedQueued for verificationVerified improvementNo measurable improvementInconclusive due to benchmark noise
#!/usr/bin/env python3
import argparse
import json
import os
import platform
import re
import shutil
import statistics
import subprocess
import sys
import tempfile
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Benchmark Xcode clean and incremental builds.")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--workspace", help="Path to the .xcworkspace file")
group.add_argument("--project", help="Path to the .xcodeproj file")
parser.add_argument("--scheme", required=True, help="Scheme to build")
parser.add_argument("--configuration", default="Debug", help="Build configuration")
parser.add_argument("--destination", help="xcodebuild destination string")
parser.add_argument("--derived-data-path", help="DerivedData path override")
parser.add_argument("--output-dir", default=".build-benchmark", help="Output directory for artifacts")
parser.add_argument("--repeats", type=int, default=3, help="Measured runs per build type")
parser.add_argument("--skip-warmup", action="store_true", help="Skip the validation build")
parser.add_argument(
"--touch-file",
help="Path to a source file to touch before each incremental build. "
"When provided, measures a real edit-rebuild loop instead of a zero-change build.",
)
parser.add_argument(
"--no-cached-clean",
action="store_true",
help="Skip cached clean builds even when COMPILATION_CACHE_ENABLE_CACHING is detected.",
)
parser.add_argument(
"--extra-arg",
action="append",
default=[],
help="Additional xcodebuild argument to append. Can be passed multiple times.",
)
return parser.parse_args()
def command_base(args: argparse.Namespace) -> List[str]:
command = ["xcodebuild"]
if args.workspace:
command.extend(["-workspace", args.workspace])
if args.project:
command.extend(["-project", args.project])
command.extend(["-scheme", args.scheme, "-configuration", args.configuration])
if args.destination:
command.extend(["-destination", args.destination])
if args.derived_data_path:
command.extend(["-derivedDataPath", args.derived_data_path])
command.extend(args.extra_arg)
return command
def shell_join(parts: List[str]) -> str:
return " ".join(subprocess.list2cmdline([part]) for part in parts)
_TASK_COUNT_RE = re.compile(r"^(.+?)\s*\((\d+)\s+tasks?\)$")
def _extract_task_count(name: str) -> tuple[str, Optional[int]]:
"""Split 'Category (N tasks)' into ('Category', N)."""
match = _TASK_COUNT_RE.match(name)
if match:
return match.group(1).strip(), int(match.group(2))
return name, None
def parse_timing_summary(output: str) -> List[Dict]:
categories: Dict[str, float] = {}
task_counts: Dict[str, Optional[int]] = {}
for raw_line in output.splitlines():
line = raw_line.strip()
if not line:
continue
for suffix in (" seconds", " second", " sec"):
if not line.endswith(suffix):
continue
trimmed = line[: -len(suffix)]
if "|" in trimmed:
name_part, _, seconds_text = trimmed.rpartition("|")
else:
name_part, _, seconds_text = trimmed.rpartition(" ")
try:
seconds = float(seconds_text.strip())
except ValueError:
continue
cleaned_name = name_part.replace(" ", " ").strip(" -:")
if len(cleaned_name) < 3:
continue
base_name, count = _extract_task_count(cleaned_name)
categories[base_name] = categories.get(base_name, 0.0) + seconds
if count is not None:
task_counts[base_name] = (task_counts.get(base_name) or 0) + count
break
result: List[Dict] = []
for name, seconds in sorted(categories.items(), key=lambda item: item[1], reverse=True):
entry: Dict = {"name": name, "seconds": round(seconds, 3)}
if name in task_counts:
entry["task_count"] = task_counts[name]
result.append(entry)
return result
def run_command(command: List[str]) -> subprocess.CompletedProcess:
return subprocess.run(command, capture_output=True, text=True)
def stats_for(runs: List[Dict[str, object]]) -> Dict[str, float]:
durations = [run["duration_seconds"] for run in runs if run.get("success")]
if not durations:
return {
"count": 0,
"min_seconds": 0.0,
"max_seconds": 0.0,
"median_seconds": 0.0,
"average_seconds": 0.0,
}
return {
"count": len(durations),
"min_seconds": round(min(durations), 3),
"max_seconds": round(max(durations), 3),
"median_seconds": round(statistics.median(durations), 3),
"average_seconds": round(statistics.fmean(durations), 3),
}
def xcode_version() -> str:
result = run_command(["xcodebuild", "-version"])
return result.stdout.strip() if result.returncode == 0 else "unknown"
def detect_compilation_caching(base_command: List[str]) -> bool:
"""Check whether COMPILATION_CACHE_ENABLE_CACHING is enabled in the resolved build settings."""
result = run_command([*base_command, "-showBuildSettings"])
if result.returncode != 0:
return False
for line in result.stdout.splitlines():
stripped = line.strip()
if stripped.startswith("COMPILATION_CACHE_ENABLE_CACHING") and "=" in stripped:
value = stripped.split("=", 1)[1].strip()
return value == "YES"
return False
def measure_build(
base_command: List[str],
artifact_stem: str,
output_dir: Path,
build_type: str,
run_index: int,
) -> Dict[str, object]:
build_command = [*base_command, "build", "-showBuildTimingSummary"]
started = time.perf_counter()
result = run_command(build_command)
elapsed = round(time.perf_counter() - started, 3)
log_path = output_dir / f"{artifact_stem}-{build_type}-{run_index}.log"
log_path.write_text(result.stdout + result.stderr)
return {
"id": f"{build_type}-{run_index}",
"build_type": build_type,
"duration_seconds": elapsed,
"success": result.returncode == 0,
"exit_code": result.returncode,
"command": shell_join(build_command),
"raw_log_path": str(log_path),
"timing_summary_categories": parse_timing_summary(result.stdout + result.stderr),
}
def main() -> int:
args = parse_args()
output_dir = Path(args.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
artifact_stem = f"{timestamp}-{args.scheme.replace(' ', '-').lower()}"
base_command = command_base(args)
if not args.skip_warmup:
warmup = run_command([*base_command, "build"])
if warmup.returncode != 0:
sys.stderr.write(warmup.stdout + warmup.stderr)
return warmup.returncode
warmup_clean = run_command([*base_command, "clean"])
if warmup_clean.returncode != 0:
sys.stderr.write(warmup_clean.stdout + warmup_clean.stderr)
return warmup_clean.returncode
warmup_rebuild = run_command([*base_command, "build"])
if warmup_rebuild.returncode != 0:
sys.stderr.write(warmup_rebuild.stdout + warmup_rebuild.stderr)
return warmup_rebuild.returncode
runs: Dict[str, list] = {"clean": [], "incremental": []}
for index in range(1, args.repeats + 1):
clean_result = run_command([*base_command, "clean"])
clean_log_path = output_dir / f"{artifact_stem}-clean-prep-{index}.log"
clean_log_path.write_text(clean_result.stdout + clean_result.stderr)
if clean_result.returncode != 0:
sys.stderr.write(clean_result.stdout + clean_result.stderr)
return clean_result.returncode
runs["clean"].append(measure_build(base_command, artifact_stem, output_dir, "clean", index))
# --- Cached clean builds ---------------------------------------------------
# When COMPILATION_CACHE_ENABLE_CACHING is enabled, the compilation cache lives outside
# DerivedData and survives product deletion. We measure "cached clean"
# builds by pointing DerivedData at a temp directory, warming the cache with
# one build, then deleting the DerivedData directory (but not the cache)
# before each measured rebuild. This captures the realistic scenario:
# branch switching, pulling changes, or Clean Build Folder.
should_cached_clean = not args.no_cached_clean and detect_compilation_caching(base_command)
if should_cached_clean:
dd_path = Path(args.derived_data_path) if args.derived_data_path else Path(
tempfile.mkdtemp(prefix="xcode-bench-dd-")
)
cached_cmd = list(base_command)
if not args.derived_data_path:
cached_cmd.extend(["-derivedDataPath", str(dd_path)])
cache_warmup = run_command([*cached_cmd, "build"])
if cache_warmup.returncode != 0:
sys.stderr.write("Warning: cached clean warmup build failed, skipping cached clean benchmarks.\n")
sys.stderr.write(cache_warmup.stdout + cache_warmup.stderr)
should_cached_clean = False
if should_cached_clean:
runs["cached_clean"] = []
for index in range(1, args.repeats + 1):
shutil.rmtree(dd_path, ignore_errors=True)
runs["cached_clean"].append(
measure_build(cached_cmd, artifact_stem, output_dir, "cached-clean", index)
)
shutil.rmtree(dd_path, ignore_errors=True)
# --- Incremental / zero-change builds --------------------------------------
incremental_label = "incremental"
if args.touch_file:
touch_path = Path(args.touch_file)
if not touch_path.exists():
sys.stderr.write(f"--touch-file path does not exist: {touch_path}\n")
return 1
incremental_label = "incremental"
else:
incremental_label = "zero-change"
for index in range(1, args.repeats + 1):
if args.touch_file:
touch_path.touch()
runs["incremental"].append(
measure_build(base_command, artifact_stem, output_dir, incremental_label, index)
)
summary: Dict[str, object] = {
"clean": stats_for(runs["clean"]),
"incremental": stats_for(runs["incremental"]),
}
if "cached_clean" in runs:
summary["cached_clean"] = stats_for(runs["cached_clean"])
artifact = {
"schema_version": "1.2.0" if "cached_clean" in runs else "1.1.0",
"created_at": datetime.now(timezone.utc).isoformat(),
"build": {
"entrypoint": "workspace" if args.workspace else "project",
"path": args.workspace or args.project,
"scheme": args.scheme,
"configuration": args.configuration,
"destination": args.destination or "",
"derived_data_path": args.derived_data_path or "",
"command": shell_join(base_command),
},
"environment": {
"host": platform.node(),
"macos_version": platform.platform(),
"xcode_version": xcode_version(),
"cwd": os.getcwd(),
},
"runs": runs,
"summary": summary,
"notes": [f"touch-file: {args.touch_file}"] if args.touch_file else [],
}
artifact_path = output_dir / f"{artifact_stem}.json"
artifact_path.write_text(json.dumps(artifact, indent=2) + "\n")
print(f"Saved benchmark artifact: {artifact_path}")
print(f"Clean median: {artifact['summary']['clean']['median_seconds']}s")
if "cached_clean" in artifact["summary"]:
print(f"Cached clean median: {artifact['summary']['cached_clean']['median_seconds']}s")
inc_label = "Incremental" if args.touch_file else "Zero-change"
print(f"{inc_label} median: {artifact['summary']['incremental']['median_seconds']}s")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Related skills
How it compares
Implementation and verification companion to xcode-build-orchestrator analysis, not a greenfield project audit.
FAQ
What inputs does xcode-build-fixer expect?
An approved optimization plan at .build-benchmark/optimization-plan.md with checked items, or explicit developer instructions naming the fix to apply.
When should a build optimization change be reverted?
Revert speculative changes when cumulative wall-clock regresses across measured build types; keep Apple best-practice settings even without immediate benchmark gains.
Is Xcode Build Fixer safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.