
Xcode Build Orchestrator
- 3k installs
- 1.2k repo stars
- Updated April 15, 2026
- avdlee/xcode-build-optimization-agent-skill
xcode-build-orchestrator is a skill that benchmarks Xcode builds, runs specialist analyses, prioritizes wall-clock improvements, and delegates approved fixes after explicit developer approval.
About
Xcode Build Orchestrator is the recommend-first entrypoint for end-to-end Xcode build optimization where wall-clock wait time is the primary success metric. Phase one Analyze runs in agent mode but is recommend-only: collect workspace or project, scheme, configuration, and destination context, then run xcode-build-benchmark for baseline medians including cached clean builds when COMPILATION_CACHE_ENABLE_CACHING is enabled. Verify benchmark artifacts have non-empty timing_summary_categories and flag high variance when min-max spread exceeds 20 percent of median. Run diagnose_compilation.py when SwiftCompile or emit module tasks likely block the critical path, and invoke specialist skills xcode-compilation-analyzer, xcode-project-analyzer, and spm-build-analysis as evidence dictates. Merge findings into a prioritized plan saved via generate_optimization_report.py to .build-benchmark/optimization-plan.md with approval checkboxes, then stop for developer review. Phase two Execute delegates approved items to xcode-build-fixer for atomic implementation and re-benchmarking. Prioritization ranks by likely wall-time savings, not cumulative parallel task reduction. Impact language must stat.
- Wall-clock build wait time is the primary metric, not cumulative parallel task seconds.
- Two phases: recommend-only analysis with benchmark, then approved execution via xcode-build-fixer.
- Runs xcode-build-benchmark, diagnose_compilation.py, and three specialist analyzer skills.
- Flags benchmark variance above 20 percent median and recommends extra repetitions.
- Generates .build-benchmark/optimization-plan.md with approval checklist before any file changes.
Xcode Build Orchestrator by the numbers
- 2,986 all-time installs (skills.sh)
- +66 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #47 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-orchestrator capabilities & compatibility
- Capabilities
- baseline benchmarking via xcode build benchmark · compilation diagnostics via diagnose_compilation · specialist skill orchestration for compilation, · prioritized optimization plan.md generation with · post approval delegation to xcode build fixer wi
- Use cases
- devops · testing · debugging
- Platforms
- macOS
What xcode-build-orchestrator says it does
Wall-clock build time (how long the developer waits) is the primary success metric.
Do not modify project files, source files, packages, or scripts without explicit developer approval.
If the spread (max - min) exceeds 20% of the median, flag the benchmark as having high variance
npx skills add https://github.com/avdlee/xcode-build-optimization-agent-skill --skill xcode-build-orchestratorAdd 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 speed up Xcode clean and incremental builds with evidence-backed recommendations ranked by actual developer wait time?
Orchestrate recommend-first Xcode build optimization: benchmark baseline, run specialist analyzers, prioritize by wall-clock impact, approve fixes, re-benchmark.
Who is it for?
iOS and macOS developers with slow Xcode builds who can run benchmark scripts and review an approval-gated optimization plan.
Skip if: Skip for Android or non-Apple builds, or when you want changes applied without benchmark evidence and explicit approval.
When should I use this skill?
User wants end-to-end Xcode build optimization, full build audit, speed up builds, or recommend-first optimization covering compilation and packages.
What you get
Baseline and post-change benchmark medians, prioritized optimization-plan.md, approved fixes applied, and reported wall-clock deltas.
- benchmark JSON artifacts
- optimization-plan.md with approval checklist
- before and after wall-clock delta report
By the numbers
- [object Object]
- [object Object]
Files
Xcode Build Orchestrator
Use this skill as the recommend-first entrypoint for end-to-end Xcode build optimization work.
Non-Negotiable Rules
- Wall-clock build time (how long the developer waits) is the primary success metric. Every recommendation must state its expected impact on the developer's actual wait time.
- Start in recommendation mode.
- Benchmark before making changes.
- Do not modify project files, source files, packages, or scripts without explicit developer approval.
- Preserve the evidence trail for every recommendation.
- Re-benchmark after approved changes and report the wall-clock delta.
Two-Phase Workflow
The orchestration is designed as two distinct phases separated by developer review.
Phase 1 -- Analyze (recommend-only)
Run this phase in agent mode because the agent needs to execute builds, run benchmark scripts, write benchmark artifacts, and generate the optimization report. However, treat Phase 1 as recommend-only: do not modify any project files, source files, packages, or build settings. The only files the agent creates during this phase are benchmark artifacts and the optimization plan inside .build-benchmark/.
1. Collect the build target context: workspace or project, scheme, configuration, destination, and current pain point. When both .xcworkspace and .xcodeproj exist, prefer .xcodeproj unless the workspace contains sub-projects required for the build. Workspaces that reference external projects may fail if those projects are not checked out. 2. Run xcode-build-benchmark to establish a baseline if no fresh benchmark exists. The benchmark script auto-detects COMPILATION_CACHE_ENABLE_CACHING = YES and includes cached clean builds that measure the realistic developer experience (warm cache). If the build fails to compile, check git log for a recent buildable commit. When working in a worktree, cherry-picking a targeted build fix from a feature branch is acceptable to reach a buildable state. If SPM packages reference gitignored directories in their exclude: paths (e.g., __Snapshots__), create those directories before building -- worktrees do not contain gitignored content and xcodebuild -resolvePackageDependencies will crash otherwise. 3. Verify the benchmark artifact has non-empty timing_summary_categories. If empty, the timing summary parser may have failed -- re-parse the raw logs or inspect them manually. If COMPILATION_CACHE_ENABLE_CACHING is enabled, also verify the artifact includes cached_clean runs.
- Benchmark confidence check: For each build type (clean, cached clean, incremental), compare the min and max values. If the spread (max - min) exceeds 20% of the median, flag the benchmark as having high variance and recommend running additional repetitions (5+ runs) before drawing conclusions. High variance makes it difficult to distinguish real improvements from noise. After applying changes, only claim an improvement if the post-change median falls outside the baseline's min-max range.
4. If incremental builds are the primary pain point and Xcode 16.4+ is available, recommend the developer enable Task Backtraces (Scheme Editor > Build tab > Build Debugging > "Task Backtraces"). This reveals why each task re-ran, which is critical for diagnosing unexpected replanning or input invalidation. Include any Task Backtrace evidence in the analysis. 5. Determine whether compile tasks are likely blocking wall-clock progress or just consuming parallel CPU time. Compare the sum of all timing-summary category seconds against the wall-clock median: if the sum is 2x+ the median, most work is parallelized and compile hotspot fixes are unlikely to reduce wait time. If SwiftCompile, CompileC, SwiftEmitModule, or Planning Swift module dominate the timing summary and appear likely to be on the critical path, run diagnose_compilation.py to capture type-checking hotspots. If they are parallelized, still run diagnostics but label findings as "parallel efficiency improvements" rather than "build time improvements." 6. Run the specialist analyses that fit the evidence by reading each skill's SKILL.md and applying its workflow:
- `xcode-compilation-analyzer`
- `xcode-project-analyzer`
- `spm-build-analysis`
7. Merge findings into a single prioritized improvement plan. 8. Generate the markdown optimization report using generate_optimization_report.py and save it to .build-benchmark/optimization-plan.md. This report includes the build settings audit, timing analysis, prioritized recommendations, and an approval checklist. 9. Stop and present the plan to the developer for review.
The developer reviews .build-benchmark/optimization-plan.md, checks the approval boxes for the recommendations they want implemented, and then triggers phase 2.
Phase 2 -- Execute and verify (agent mode)
Run this phase in agent mode after the developer has reviewed and approved recommendations from the plan. Delegate all implementation work to `xcode-build-fixer` by reading its SKILL.md and applying its workflow.
10. Read .build-benchmark/optimization-plan.md and identify the approved items from the approval checklist. 11. Hand off to xcode-build-fixer with the approved plan. The fixer applies each approved change, verifies compilation, and re-benchmarks. 12. Append verification results to the optimization plan: post-change medians, absolute and percentage deltas, and confidence notes. 13. Report before and after results, plus any remaining follow-up opportunities.
Prioritization Rules
The goal is to reduce how long the developer waits for builds to finish.
1. Identify the developer's primary pain (clean build, incremental build, or both) and the measured wall-clock median. 2. Determine what is likely blocking wall-clock progress:
- If the sum of all timing-summary category seconds is 2x+ the wall-clock median, most work is parallelized. Compile hotspot fixes are unlikely to reduce wait time.
- If a single serial category (e.g.
PhaseScriptExecution,CompileAssetCatalog,CodeSign) accounts for a large fraction of wall-clock, that is the real bottleneck. - If
Planning Swift moduleorSwiftEmitModuledominates incremental builds, the cause is likely invalidation or module size, not individual file compile speed.
3. Rank recommendations by likely wall-time savings, not cumulative task reduction. 4. Source-level compile fixes should not outrank project/graph/configuration fixes unless evidence suggests they are on the critical path.
Prefer changes that are measurable, reversible, and low-risk.
Recommendation Impact Language
Every recommendation presented to the developer must include one of these impact statements:
- "Expected to reduce your [clean/incremental] build by approximately X seconds."
- "Reduces parallel compile work but is unlikely to reduce your build wait time because other tasks take equally long."
- "Impact on wait time is uncertain -- re-benchmark after applying to confirm."
- "No wait-time improvement expected. The benefit is [deterministic builds / faster branch switching / reduced CI cost]."
- For COMPILATION_CACHE_ENABLE_CACHING specifically: "Measured 5-14% faster clean builds across tested projects. The benefit compounds in real workflows where the cache persists between builds -- branch switching, pulling changes, and CI with persistent DerivedData."
Never quote cumulative task-time savings as the headline impact. If a change reduces 5 seconds of parallel compile work but another equally long task still runs, the developer's wait time does not change.
Approval Gate
Before implementing anything, present a short approval list that includes:
- recommendation name
- expected wait-time impact (using the impact language above)
- evidence summary
- affected files or settings
- whether the change is low, medium, or high risk
Wait for explicit developer approval.
Post-Approval Execution
After approval, delegate to xcode-build-fixer:
- the fixer implements only the approved items
- changes are applied atomically and kept scoped
- any deviations from the original recommendation plan are noted
- the fixer re-benchmarks with the same benchmark contract
Final Report
Lead with the wall-clock result in plain language, e.g.: "Your clean build now takes 82s (was 86s) -- 4s faster." Then include:
- baseline clean and incremental wall-clock medians
- post-change clean and incremental wall-clock medians
- absolute and percentage wall-clock deltas
- what changed
- what was intentionally left unchanged
- confidence notes if noise prevents a strong conclusion -- if benchmark variance is high (min-to-max spread exceeds 20% of median), say so explicitly rather than presenting noisy numbers as definitive improvements or regressions
- 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."
- a ready-to-paste community results row and a link to open a PR (see the report template)
Preferred Command Paths
Benchmark
python3 scripts/benchmark_builds.py \
--project App.xcodeproj \
--scheme MyApp \
--configuration Debug \
--destination "platform=iOS Simulator,name=iPhone 16" \
--output-dir .build-benchmarkFor macOS apps use --destination "platform=macOS". For watchOS use --destination "platform=watchOS Simulator,name=Apple Watch Series 10". For tvOS use --destination "platform=tvOS Simulator,name=Apple TV". Omit --destination to use the scheme's default.
To measure real incremental builds (file-touched rebuild) instead of zero-change builds, add --touch-file path/to/SomeFile.swift.
Compilation Diagnostics
python3 scripts/diagnose_compilation.py \
--project App.xcodeproj \
--scheme MyApp \
--configuration Debug \
--destination "platform=iOS Simulator,name=iPhone 16" \
--threshold 100 \
--output-dir .build-benchmarkOptimization Report
python3 scripts/generate_optimization_report.py \
--benchmark .build-benchmark/<artifact>.json \
--project-path App.xcodeproj \
--diagnostics .build-benchmark/<diagnostics>.json \
--output .build-benchmark/optimization-plan.mdAdditional Resources
- For the report template, see references/orchestration-report-template.md
- For benchmark artifact requirements, see references/benchmark-artifacts.md
- For the recommendation format, see references/recommendation-format.md
- For build settings best practices, see references/build-settings-best-practices.md
Benchmark Artifacts
All skills in this repository should treat .build-benchmark/ as the canonical location for measured build evidence.
Goals
- Keep build measurements reproducible.
- Make clean and incremental build data easy to compare.
- Preserve enough context for later specialist analysis without rerunning the benchmark.
Wall-Clock vs Cumulative Task Time
The duration_seconds field on each run and the median_seconds in the summary represent wall-clock time -- how long the developer actually waits. This is the primary success metric.
The timing_summary_categories are aggregated task times parsed from Xcode's Build Timing Summary. Because Xcode runs many tasks in parallel across CPU cores, these totals typically exceed the wall-clock duration. A large cumulative SwiftCompile value is diagnostic evidence of compiler workload, not proof that compilation is blocking the build. Always compare category totals against the wall-clock median before concluding that a category is a bottleneck.
File Layout
Recommended outputs:
.build-benchmark/<timestamp>-<scheme>.json.build-benchmark/<timestamp>-<scheme>-clean-1.log.build-benchmark/<timestamp>-<scheme>-clean-2.log.build-benchmark/<timestamp>-<scheme>-clean-3.log.build-benchmark/<timestamp>-<scheme>-cached-clean-1.log(when COMPILATION_CACHE_ENABLE_CACHING is enabled).build-benchmark/<timestamp>-<scheme>-cached-clean-2.log.build-benchmark/<timestamp>-<scheme>-cached-clean-3.log.build-benchmark/<timestamp>-<scheme>-incremental-1.log.build-benchmark/<timestamp>-<scheme>-incremental-2.log.build-benchmark/<timestamp>-<scheme>-incremental-3.log
Use an ISO-like UTC timestamp without spaces so the files sort naturally.
Artifact Requirements
Each JSON artifact should include:
- schema version
- creation timestamp
- project context
- environment details when available
- the normalized build command
- separate
cleanandincrementalrun arrays - summary statistics for each build type
- parsed timing-summary categories
- free-form notes for caveats or noise
Clean, Cached Clean, And Incremental Separation
Do not merge different build type measurements into a single list. They answer different questions:
- Clean builds show full build-system, package, and module setup cost with a cold compilation cache.
- Cached clean builds show clean build cost when the compilation cache is warm. This is the realistic scenario for branch switching, pulling changes, or Clean Build Folder. Only present when
COMPILATION_CACHE_ENABLE_CACHING = YESis detected. - Incremental builds show edit-loop productivity and script or cache invalidation problems.
Raw Logs
Store raw xcodebuild output beside the JSON artifact whenever possible. That allows later skills to:
- re-parse timing summaries
- inspect failed builds
- search for long type-check warnings
- correlate build-system phases with recommendations
Measurement Caveats
COMPILATION_CACHE_ENABLE_CACHING
COMPILATION_CACHE_ENABLE_CACHING = YES stores compiled artifacts in a system-managed cache outside DerivedData so that repeated compilations of identical inputs are served from cache. The standard clean-build benchmark (xcodebuild clean between runs) may add overhead from cache population without showing the corresponding cache-hit benefit.
The benchmark script automatically detects COMPILATION_CACHE_ENABLE_CACHING = YES and runs a cached clean benchmark phase. This phase:
1. Builds once to warm the compilation cache. 2. Deletes DerivedData (but not the compilation cache) before each measured run. 3. Rebuilds, measuring the cache-hit clean build time.
The cached clean metric captures the realistic developer experience: branch switching, pulling changes, and Clean Build Folder. Use the cached clean median as the primary comparison metric when evaluating COMPILATION_CACHE_ENABLE_CACHING impact.
To skip this phase, pass --no-cached-clean.
First-Run Variance
The first clean build after the warmup cycle often runs 20-40% slower than subsequent clean builds due to cold OS-level caches (disk I/O, dynamic linker cache, etc.). The benchmark script mitigates this by running a warmup clean+build cycle before measured runs. If variance between the first and later clean runs is still high, prefer the median or min over the mean, and note the variance in the artifact's notes field.
Shared Consumer Expectations
Any skill reading a benchmark artifact should be able to identify:
- what was measured
- how it was measured
- whether the run succeeded
- whether the results are stable enough to compare
For the authoritative field-level schema, see the build-benchmark.schema.json bundled with the xcode-build-benchmark skill.
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
...Orchestration Report Template
Use this structure when the orchestrator consolidates benchmark evidence and specialist findings. The generate_optimization_report.py script produces this format automatically when given the benchmark and diagnostics artifacts.
# Xcode Build Optimization Plan
## Project Context
- **Project:** `App.xcodeproj`
- **Scheme:** `MyApp`
- **Configuration:** `Debug`
- **Destination:** `platform=iOS Simulator,name=iPhone 16`
- **Xcode:** Xcode 26.x
- **Date:** 2026-01-01T00:00:00Z
- **Benchmark artifact:** `.build-benchmark/<timestamp>-<scheme>.json`
## Baseline Benchmarks
| Metric | Clean | Cached Clean | Zero-Change |
|--------|-------|-------------|-------------|
| Median | 0.000s | 0.000s | 0.000s |
| Min | 0.000s | 0.000s | 0.000s |
| Max | 0.000s | 0.000s | 0.000s |
| Runs | 3 | 3 | 3 |
> **Cached Clean** = clean build with a warm compilation cache. This is the realistic scenario for branch switching, pulling changes, or Clean Build Folder. Only present when `COMPILATION_CACHE_ENABLE_CACHING = YES` is detected. "Zero-Change" = rebuild with no edits (measures fixed overhead). Use `--touch-file` in the benchmark script to measure true incremental builds where a source file is modified.
### Clean Build Timing Summary
> **Note:** These are aggregated task times across all CPU cores. Because Xcode runs many tasks in parallel, these totals typically exceed the actual build wait time shown above. A large number here does not mean it is blocking your build.
| Category | Tasks | Seconds |
|----------|------:|--------:|
| SwiftCompile | 325 | 271.245s |
| SwiftEmitModule | 30 | 23.625s |
| ... | ... | ... |
## Build Settings Audit
### Debug Configuration
- [x] `SWIFT_COMPILATION_MODE`: `(unset)` (recommended: `singlefile`)
- [x] `SWIFT_OPTIMIZATION_LEVEL`: `-Onone` (recommended: `-Onone`)
- [x] `GCC_OPTIMIZATION_LEVEL`: `0` (recommended: `0`)
- [x] `ONLY_ACTIVE_ARCH`: `YES` (recommended: `YES`)
- [x] `DEBUG_INFORMATION_FORMAT`: `dwarf` (recommended: `dwarf`)
- [x] `ENABLE_TESTABILITY`: `YES` (recommended: `YES`)
- [x] `EAGER_LINKING`: `YES` (recommended: `YES`)
### General (All Configurations)
- [x] `COMPILATION_CACHE_ENABLE_CACHING`: `YES` (recommended: `YES`)
- [x] `SWIFT_USE_INTEGRATED_DRIVER`: `YES` (recommended: `YES`)
- [x] `CLANG_ENABLE_MODULES`: `YES` (recommended: `YES`)
### Release Configuration
- [x] `SWIFT_COMPILATION_MODE`: `wholemodule` (recommended: `wholemodule`)
- [x] `SWIFT_OPTIMIZATION_LEVEL`: `-O` (recommended: `-O`)
- ...
### Cross-Target Consistency
- [x] `SWIFT_COMPILATION_MODE` is consistent across all targets
- [ ] `OTHER_SWIFT_FLAGS` has target-level overrides: ...
## Compilation Diagnostics
| Duration | Kind | File | Line | Name |
|---------:|------|------|-----:|------|
| 150ms | function-body | MyView.swift | 42 | body |
| ... | ... | ... | ... | ... |
## Prioritized Recommendations
### 1. Recommendation title
**Wait-Time Impact:** Expected to reduce your clean build by approximately 3 seconds.
**Category:** project
**Evidence:** ...
**Impact:** High
**Confidence:** High
**Risk:** Low
## Approval Checklist
- [ ] **1. Recommendation title** -- Wait-Time Impact: ~3s clean build reduction | Risk: Low
- [ ] **2. Another recommendation** -- Wait-Time Impact: Uncertain, re-benchmark to confirm | Risk: Low
## Next Steps
After implementing approved changes, re-benchmark with the same inputs:
...
Compare the new wall-clock medians against the baseline. Report results as:
"Your [clean/incremental] build now takes X.Xs (was Y.Ys) -- Z.Zs faster/slower."
## Execution Report (post-approval)
### 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 of change | repo-local | Clean: X.Xs→Y.Ys, Incr: X.Xs→Y.Ys | Kept / Reverted / Blocked |
| 2 | ... | ... | ... | ... |
Status values: `Kept`, `Kept (best practice)`, `Reverted`, `Blocked`, `No improvement`
### Final Cumulative Result
- Post-change clean build: X.Xs (was Y.Ys) -- Z.Zs faster/slower
- Post-change cached clean build: X.Xs (was Y.Ys) -- Z.Zs faster/slower (when COMPILATION_CACHE_ENABLE_CACHING enabled)
- Post-change incremental build: X.Xs (was Y.Ys) -- Z.Zs faster/slower
- **Net result:** Faster / Slower / Unchanged
- If cumulative task metrics improved but wall-clock did not: "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 standard clean builds are slower but cached clean builds are faster: "Standard clean builds show overhead from compilation cache population. Cached clean builds (the realistic developer workflow) are faster, confirming the net benefit."
### Blocked or Non-Actionable Findings
- Finding: reason it could not be addressed from the repo
## Remaining follow-up ideas
- Item:
- Why it was deferred:
## Share your results
Add your improvement to the community results table by opening a pull request.
Copy the row below and append it to the table in README.md:
| <project-name> | X.Xs → X.Xs (-X.Xs / X% faster) | X.Xs → X.Xs (-X.Xs / X% faster) |
Open a PR: https://github.com/AvdLee/Xcode-Build-Optimization-Agent-Skill/edit/main/README.mdUsage Notes
- Keep approval-required items explicit.
- Do not imply that an unapproved recommendation was applied.
- If results are noisy, say that the verification is inconclusive instead of overstating success.
- The Build Settings Audit scope is strictly build performance. Do not flag language-migration settings like
SWIFT_STRICT_CONCURRENCYorSWIFT_UPCOMING_FEATURE_*. - The Compilation Diagnostics section is populated by
diagnose_compilation.py. If not run, note that it was skipped. COMPILATION_CACHE_ENABLE_CACHINGhas been measured at 5-14% faster clean builds across tested projects. The benefit compounds in real developer workflows (branch switching, pulling changes, CI with persistent DerivedData). The benchmark script auto-detects this setting and runs a cached clean phase for validation.- When recommending SPM version pins, verify that tagged versions exist (
git ls-remote --tags) before suggesting a pin-to-tag change. If no tags exist, recommend pinning to a commit revision hash. - Before including a local package in a build-time recommendation, verify it is referenced in
project.pbxprojviaXCLocalSwiftPackageReference. Packages that exist on disk but are not linked do not affect build time.
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())
#!/usr/bin/env python3
"""Run a single Xcode build with -Xfrontend diagnostics to find slow type-checking."""
import argparse
import json
import re
import subprocess
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional
_TYPECHECK_RE = re.compile(
r"^(?P<file>.+?):(?P<line>\d+):(?P<col>\d+): warning: "
r"(?P<kind>instance method|global function|getter|type-check|expression) "
r"'?(?P<name>[^']*?)'?\s+took\s+(?P<ms>\d+)ms\s+to\s+type-check"
)
_EXPRESSION_RE = re.compile(
r"^(?P<file>.+?):(?P<line>\d+):(?P<col>\d+): warning: "
r"expression took\s+(?P<ms>\d+)ms\s+to\s+type-check"
)
_FILE_TIME_RE = re.compile(
r"^\s*(?P<seconds>\d+(?:\.\d+)?)\s+seconds\s+.*\s+compiling\s+(?P<file>\S+)"
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Run an Xcode build with -Xfrontend type-checking diagnostics."
)
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")
parser.add_argument(
"--threshold",
type=int,
default=100,
help="Millisecond threshold for -warn-long-function-bodies and "
"-warn-long-expression-type-checking (default: 100)",
)
parser.add_argument("--skip-clean", action="store_true", help="Skip clean before build")
parser.add_argument(
"--per-file-timing",
action="store_true",
help="Add -Xfrontend -debug-time-compilation to report per-file compile times.",
)
parser.add_argument(
"--stats-output",
action="store_true",
help="Add -Xfrontend -stats-output-dir to collect detailed compiler statistics.",
)
parser.add_argument(
"--extra-arg",
action="append",
default=[],
help="Additional xcodebuild argument. 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 parse_diagnostics(output: str) -> List[Dict]:
"""Extract type-checking warnings from xcodebuild output."""
warnings: List[Dict] = []
seen = set()
for raw_line in output.splitlines():
line = raw_line.strip()
match = _TYPECHECK_RE.match(line)
if match:
key = (match.group("file"), match.group("line"), match.group("col"), "function-body")
if key in seen:
continue
seen.add(key)
warnings.append(
{
"file": match.group("file"),
"line": int(match.group("line")),
"column": int(match.group("col")),
"duration_ms": int(match.group("ms")),
"kind": "function-body",
"name": match.group("name"),
}
)
continue
match = _EXPRESSION_RE.match(line)
if match:
key = (match.group("file"), match.group("line"), match.group("col"), "expression")
if key in seen:
continue
seen.add(key)
warnings.append(
{
"file": match.group("file"),
"line": int(match.group("line")),
"column": int(match.group("col")),
"duration_ms": int(match.group("ms")),
"kind": "expression",
"name": "",
}
)
warnings.sort(key=lambda w: w["duration_ms"], reverse=True)
return warnings
def parse_file_timings(output: str) -> List[Dict]:
"""Extract per-file compile times from -debug-time-compilation output."""
timings: List[Dict] = []
seen = set()
for raw_line in output.splitlines():
match = _FILE_TIME_RE.match(raw_line.strip())
if match:
filepath = match.group("file")
if filepath in seen:
continue
seen.add(filepath)
timings.append(
{
"file": filepath,
"duration_seconds": float(match.group("seconds")),
}
)
timings.sort(key=lambda t: t["duration_seconds"], reverse=True)
return timings
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")
scheme_slug = args.scheme.replace(" ", "-").lower()
artifact_stem = f"{timestamp}-{scheme_slug}"
base = command_base(args)
if not args.skip_clean:
print("Cleaning build products...")
clean = subprocess.run([*base, "clean"], capture_output=True, text=True)
if clean.returncode != 0:
sys.stderr.write(clean.stdout + clean.stderr)
return clean.returncode
threshold = str(args.threshold)
swift_flags = (
f"$(inherited) -Xfrontend -warn-long-function-bodies={threshold} "
f"-Xfrontend -warn-long-expression-type-checking={threshold}"
)
if args.per_file_timing:
swift_flags += " -Xfrontend -debug-time-compilation"
stats_dir: Optional[Path] = None
if args.stats_output:
stats_dir = output_dir / f"{artifact_stem}-stats"
stats_dir.mkdir(parents=True, exist_ok=True)
swift_flags += f" -Xfrontend -stats-output-dir -Xfrontend {stats_dir}"
build_command = [
*base,
"build",
"-showBuildTimingSummary",
f"OTHER_SWIFT_FLAGS={swift_flags}",
]
extras = []
if args.per_file_timing:
extras.append("per-file timing")
if args.stats_output:
extras.append("stats output")
extras_label = f" + {', '.join(extras)}" if extras else ""
print(f"Building with type-check threshold {threshold}ms{extras_label}...")
started = time.perf_counter()
result = subprocess.run(build_command, capture_output=True, text=True)
elapsed = round(time.perf_counter() - started, 3)
combined_output = result.stdout + result.stderr
log_path = output_dir / f"{artifact_stem}-diagnostics.log"
log_path.write_text(combined_output)
warnings = parse_diagnostics(combined_output)
file_timings: Optional[List[Dict]] = None
if args.per_file_timing:
file_timings = parse_file_timings(combined_output)
artifact = {
"schema_version": "1.0.0",
"created_at": datetime.now(timezone.utc).isoformat(),
"type": "compilation-diagnostics",
"build": {
"entrypoint": "workspace" if args.workspace else "project",
"path": args.workspace or args.project,
"scheme": args.scheme,
"configuration": args.configuration,
"destination": args.destination or "",
},
"threshold_ms": args.threshold,
"build_duration_seconds": elapsed,
"build_success": result.returncode == 0,
"raw_log_path": str(log_path),
"warnings": warnings,
"summary": {
"total_warnings": len(warnings),
"function_body_warnings": sum(1 for w in warnings if w["kind"] == "function-body"),
"expression_warnings": sum(1 for w in warnings if w["kind"] == "expression"),
"slowest_ms": warnings[0]["duration_ms"] if warnings else 0,
},
}
if file_timings is not None:
artifact["per_file_timings"] = file_timings
if stats_dir is not None:
artifact["stats_dir"] = str(stats_dir)
artifact_path = output_dir / f"{artifact_stem}-diagnostics.json"
artifact_path.write_text(json.dumps(artifact, indent=2) + "\n")
print(f"\nSaved diagnostics artifact: {artifact_path}")
print(f"Build {'succeeded' if result.returncode == 0 else 'failed'} in {elapsed}s")
print(f"Found {len(warnings)} type-check warnings above {threshold}ms threshold\n")
if warnings:
print(f"{'Duration':>10} {'Kind':<15} {'Location'}")
print(f"{'--------':>10} {'----':<15} {'--------'}")
for w in warnings[:20]:
loc = f"{w['file']}:{w['line']}:{w['column']}"
label = w["name"] if w["name"] else "(expression)"
print(f"{w['duration_ms']:>8}ms {w['kind']:<15} {loc} {label}")
if len(warnings) > 20:
print(f"\n ... and {len(warnings) - 20} more (see {artifact_path})")
else:
print("No type-checking hotspots found above threshold.")
if file_timings:
print(f"\nPer-file compile times (top 20):\n")
print(f"{'Duration':>12} {'File'}")
print(f"{'--------':>12} {'----'}")
for t in file_timings[:20]:
print(f"{t['duration_seconds']:>10.3f}s {t['file']}")
if len(file_timings) > 20:
print(f"\n ... and {len(file_timings) - 20} more (see {artifact_path})")
if stats_dir is not None:
stat_files = list(stats_dir.glob("*.json"))
print(f"\nCompiler statistics: {len(stat_files)} files written to {stats_dir}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
"""Generate a Markdown optimization report from benchmark and diagnostics artifacts."""
import argparse
import json
import re
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
# ---------------------------------------------------------------------------
# pbxproj helpers
# ---------------------------------------------------------------------------
_SETTING_RE = re.compile(r"^\s*([A-Z_][A-Z_0-9]*)\s*=\s*(.+?)\s*;", re.MULTILINE)
_CONFIG_ID_RE = re.compile(r"([0-9A-F]{24})\s*/\*\s*(Debug|Release)\s*\*/")
_CONFIG_LIST_RE = re.compile(
r"([0-9A-F]{24})\s*/\*\s*Build configuration list for "
r"(?P<kind>PBXProject|PBXNativeTarget)\s+\"(?P<name>[^\"]+)\"\s*\*/"
)
def _parse_all_build_configs(pbxproj: str) -> Dict[str, Tuple[str, Dict[str, str]]]:
"""Return {config_id: (config_name, {key: value})} for every XCBuildConfiguration."""
configs: Dict[str, Tuple[str, Dict[str, str]]] = {}
for match in re.finditer(
r"([0-9A-F]{24})\s*/\*\s*(Debug|Release)\s*\*/\s*=\s*\{\s*"
r"isa\s*=\s*XCBuildConfiguration;\s*buildSettings\s*=\s*\{([^}]*)\}",
pbxproj,
re.DOTALL,
):
config_id = match.group(1)
config_name = match.group(2)
body = match.group(3)
settings: Dict[str, str] = {}
for s in _SETTING_RE.finditer(body):
val = s.group(2).strip().strip('"')
settings[s.group(1)] = val
configs[config_id] = (config_name, settings)
return configs
def _resolve_config_list(
pbxproj: str, all_configs: Dict[str, Tuple[str, Dict[str, str]]], kind: str
) -> Dict[str, Dict[str, Dict[str, str]]]:
"""Resolve configuration lists for a given kind (PBXProject or PBXNativeTarget)."""
results: Dict[str, Dict[str, Dict[str, str]]] = {}
for list_match in _CONFIG_LIST_RE.finditer(pbxproj):
if list_match.group("kind") != kind:
continue
entity_name = list_match.group("name")
list_id = list_match.group(1)
block_start = pbxproj.find(f"{list_id} /*", list_match.end())
if block_start == -1:
block_start = list_match.start()
block = pbxproj[block_start : block_start + 500]
configs: Dict[str, Dict[str, str]] = {}
for cid_match in _CONFIG_ID_RE.finditer(block):
cid = cid_match.group(1)
if cid in all_configs:
cname, settings = all_configs[cid]
configs[cname] = settings
if configs:
results[entity_name] = configs
return results
def _parse_project_level_configs(pbxproj: str) -> Dict[str, Dict[str, str]]:
"""Extract project-level Debug and Release build settings."""
all_configs = _parse_all_build_configs(pbxproj)
resolved = _resolve_config_list(pbxproj, all_configs, "PBXProject")
if resolved:
return next(iter(resolved.values()))
return {}
def _parse_target_configs(pbxproj: str) -> Dict[str, Dict[str, Dict[str, str]]]:
"""Extract per-target Debug and Release build settings."""
all_configs = _parse_all_build_configs(pbxproj)
return _resolve_config_list(pbxproj, all_configs, "PBXNativeTarget")
# ---------------------------------------------------------------------------
# Best-practices audit
# ---------------------------------------------------------------------------
_DEBUG_EXPECTATIONS: List[Tuple[str, str, str]] = [
("SWIFT_COMPILATION_MODE", "singlefile", "Single-file mode recompiles only changed files (Xcode UI: Incremental)"),
("SWIFT_OPTIMIZATION_LEVEL", "-Onone", "Optimization passes add compile time without debug benefit"),
("GCC_OPTIMIZATION_LEVEL", "0", "C/ObjC optimization adds compile time without debug benefit"),
("ONLY_ACTIVE_ARCH", "YES", "Building all architectures multiplies compile and link time"),
("DEBUG_INFORMATION_FORMAT", "dwarf", "dwarf-with-dsym generates a separate dSYM, adding overhead"),
("ENABLE_TESTABILITY", "YES", "Required for @testable import during development"),
("EAGER_LINKING", "YES", "Allows linker to start before all compilation finishes, reducing wall-clock time"),
]
_GENERAL_EXPECTATIONS: List[Tuple[str, str, str]] = [
("COMPILATION_CACHE_ENABLE_CACHING", "YES", "Caches compilation results so repeat builds of unchanged inputs are served from cache. Measured 5-14% faster clean builds across tested projects; benefit compounds during branch switching and pulling changes"),
]
_RELEASE_EXPECTATIONS: List[Tuple[str, str, str]] = [
("SWIFT_COMPILATION_MODE", "wholemodule", "Whole-module optimization produces faster runtime code"),
("SWIFT_OPTIMIZATION_LEVEL", "-O", "Optimized binaries for production (-Osize also acceptable)"),
("GCC_OPTIMIZATION_LEVEL", "s", "Optimizes C/ObjC for size in release"),
("ONLY_ACTIVE_ARCH", "NO", "Release builds must include all architectures for distribution"),
("DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym", "dSYM bundles are needed for crash symbolication"),
("ENABLE_TESTABILITY", "NO", "Removes internal-symbol export overhead from release builds"),
]
_CONSISTENCY_KEYS = [
"SWIFT_COMPILATION_MODE",
"SWIFT_OPTIMIZATION_LEVEL",
"ONLY_ACTIVE_ARCH",
"DEBUG_INFORMATION_FORMAT",
]
def _effective_value(
project: Dict[str, str], target: Dict[str, str], key: str
) -> Optional[str]:
return target.get(key, project.get(key))
def _check(actual: Optional[str], expected: str) -> bool:
if actual is None:
if expected in ("singlefile",):
return True
return False
if expected == "-O" and actual in ("-O", '"-O"', '"-Osize"', "-Osize"):
return True
return actual.strip('"') == expected
def _merged_project_settings(
project_configs: Dict[str, Dict[str, str]],
) -> Dict[str, str]:
"""Return a flat dict of all settings across Debug and Release for general checks."""
merged: Dict[str, str] = {}
for config in project_configs.values():
merged.update(config)
return merged
def _audit_config(
project_settings: Dict[str, str],
expectations: List[Tuple[str, str, str]],
config_name: str,
) -> List[str]:
lines: List[str] = []
for key, expected, _reason in expectations:
actual = project_settings.get(key)
display_actual = actual if actual else "(unset)"
passed = _check(actual, expected)
mark = "[x]" if passed else "[ ]"
lines.append(f"- {mark} `{key}`: `{display_actual}` (recommended: `{expected}`)")
return lines
def _audit_consistency(
project_configs: Dict[str, Dict[str, str]],
target_configs: Dict[str, Dict[str, Dict[str, str]]],
) -> List[str]:
lines: List[str] = []
for key in _CONSISTENCY_KEYS:
overrides = []
for target_name, configs in target_configs.items():
for config_name in ("Debug", "Release"):
target_settings = configs.get(config_name, {})
if key in target_settings:
proj_val = project_configs.get(config_name, {}).get(key, "(unset)")
tgt_val = target_settings[key]
if tgt_val != proj_val:
overrides.append(
f"{target_name} ({config_name}): `{tgt_val}` vs project `{proj_val}`"
)
if overrides:
lines.append(f"- [ ] `{key}` has target-level overrides:")
for o in overrides:
lines.append(f" - {o}")
else:
lines.append(f"- [x] `{key}` is consistent across all targets")
return lines
# ---------------------------------------------------------------------------
# Auto-generated recommendations from audit
# ---------------------------------------------------------------------------
def _auto_recommendations_from_audit(
project_configs: Dict[str, Dict[str, str]],
) -> Dict[str, Any]:
"""Generate basic recommendations from failing build settings audit checks."""
items: List[Dict[str, str]] = []
debug_settings = project_configs.get("Debug", {})
for key, expected, reason in _DEBUG_EXPECTATIONS:
if not _check(debug_settings.get(key), expected):
actual = debug_settings.get(key, "(unset)")
items.append({
"title": f"Set `{key}` to `{expected}` for Debug",
"category": "build-settings",
"observed_evidence": f"Current value: `{actual}`. {reason}.",
"estimated_impact": "Medium",
"confidence": "High",
"risk_level": "Low",
})
merged = {}
for config in project_configs.values():
merged.update(config)
for key, expected, reason in _GENERAL_EXPECTATIONS:
if not _check(merged.get(key), expected):
actual = merged.get(key, "(unset)")
items.append({
"title": f"Enable `{key} = {expected}`",
"category": "build-settings",
"observed_evidence": f"Current value: `{actual}`. {reason}.",
"estimated_impact": "High",
"confidence": "High",
"risk_level": "Low",
})
release_settings = project_configs.get("Release", {})
for key, expected, reason in _RELEASE_EXPECTATIONS:
if not _check(release_settings.get(key), expected):
actual = release_settings.get(key, "(unset)")
items.append({
"title": f"Set `{key}` to `{expected}` for Release",
"category": "build-settings",
"observed_evidence": f"Current value: `{actual}`. {reason}.",
"estimated_impact": "Medium",
"confidence": "High",
"risk_level": "Low",
})
if not items:
return {"recommendations": []}
return {"recommendations": items}
# ---------------------------------------------------------------------------
# Report generation
# ---------------------------------------------------------------------------
def _section_context(benchmark: Dict[str, Any]) -> str:
build = benchmark.get("build", {})
env = benchmark.get("environment", {})
lines = [
"## Project Context\n",
f"- **Project:** `{build.get('path', 'unknown')}`",
f"- **Scheme:** `{build.get('scheme', 'unknown')}`",
f"- **Configuration:** `{build.get('configuration', 'unknown')}`",
f"- **Destination:** `{build.get('destination', 'unknown')}`",
f"- **Xcode:** {env.get('xcode_version', 'unknown').replace(chr(10), ' ')}",
f"- **macOS:** {env.get('macos_version', 'unknown')}",
f"- **Date:** {benchmark.get('created_at', 'unknown')}",
f"- **Benchmark artifact:** `{benchmark.get('_artifact_path', 'unknown')}`",
]
return "\n".join(lines)
def _section_baseline(benchmark: Dict[str, Any]) -> str:
summary = benchmark.get("summary", {})
clean = summary.get("clean", {})
cached_clean = summary.get("cached_clean", {})
incremental = summary.get("incremental", {})
has_cached = bool(cached_clean and cached_clean.get("count", 0) > 0)
if has_cached:
lines = [
"## Baseline Benchmarks\n",
"| Metric | Clean | Cached Clean | Incremental |",
"|--------|-------|-------------|-------------|",
f"| Median | {clean.get('median_seconds', 0):.3f}s | {cached_clean.get('median_seconds', 0):.3f}s | {incremental.get('median_seconds', 0):.3f}s |",
f"| Min | {clean.get('min_seconds', 0):.3f}s | {cached_clean.get('min_seconds', 0):.3f}s | {incremental.get('min_seconds', 0):.3f}s |",
f"| Max | {clean.get('max_seconds', 0):.3f}s | {cached_clean.get('max_seconds', 0):.3f}s | {incremental.get('max_seconds', 0):.3f}s |",
f"| Runs | {clean.get('count', 0)} | {cached_clean.get('count', 0)} | {incremental.get('count', 0)} |",
]
lines.append(
"\n> **Cached Clean** = clean build with a warm compilation cache. "
"This is the realistic scenario for branch switching, pulling changes, or "
"Clean Build Folder. The compilation cache lives outside DerivedData and "
"survives product deletion.\n"
)
else:
lines = [
"## Baseline Benchmarks\n",
"| Metric | Clean | Incremental |",
"|--------|-------|-------------|",
f"| Median | {clean.get('median_seconds', 0):.3f}s | {incremental.get('median_seconds', 0):.3f}s |",
f"| Min | {clean.get('min_seconds', 0):.3f}s | {incremental.get('min_seconds', 0):.3f}s |",
f"| Max | {clean.get('max_seconds', 0):.3f}s | {incremental.get('max_seconds', 0):.3f}s |",
f"| Runs | {clean.get('count', 0)} | {incremental.get('count', 0)} |",
]
build_types = ["clean", "cached_clean", "incremental"] if has_cached else ["clean", "incremental"]
label_map = {"clean": "Clean", "cached_clean": "Cached Clean", "incremental": "Incremental"}
for build_type in build_types:
runs = benchmark.get("runs", {}).get(build_type, [])
all_cats: Dict[str, Dict] = {}
for run in runs:
for cat in run.get("timing_summary_categories", []):
name = cat["name"]
if name not in all_cats:
all_cats[name] = {"seconds": 0.0, "task_count": 0}
all_cats[name]["seconds"] += cat["seconds"]
all_cats[name]["task_count"] += cat.get("task_count", 0)
if all_cats:
count = len(runs) or 1
ranked = sorted(all_cats.items(), key=lambda x: x[1]["seconds"], reverse=True)
label = label_map.get(build_type, build_type.title())
lines.append(f"\n### {label} Build Timing Summary\n")
lines.append(
"> **Note:** These are aggregated task times across all CPU cores. "
"Because Xcode runs many tasks in parallel, these totals typically exceed "
"the actual build wait time shown above. A large number here does not mean "
"it is blocking your build.\n"
)
lines.append("| Category | Tasks | Seconds |")
lines.append("|----------|------:|--------:|")
for name, data in ranked:
avg_sec = data["seconds"] / count
tasks = data["task_count"] // count if data["task_count"] else ""
lines.append(f"| {name} | {tasks} | {avg_sec:.3f}s |")
return "\n".join(lines)
def _section_settings_audit(
project_configs: Dict[str, Dict[str, str]],
target_configs: Dict[str, Dict[str, Dict[str, str]]],
) -> str:
lines = ["## Build Settings Audit\n"]
lines.append("### Debug Configuration\n")
lines.extend(_audit_config(project_configs.get("Debug", {}), _DEBUG_EXPECTATIONS, "Debug"))
lines.append("\n### General (All Configurations)\n")
merged = _merged_project_settings(project_configs)
lines.extend(_audit_config(merged, _GENERAL_EXPECTATIONS, "General"))
lines.append("\n### Release Configuration\n")
lines.extend(_audit_config(project_configs.get("Release", {}), _RELEASE_EXPECTATIONS, "Release"))
lines.append("\n### Cross-Target Consistency\n")
lines.extend(_audit_consistency(project_configs, target_configs))
return "\n".join(lines)
def _section_diagnostics(diagnostics: Optional[Dict[str, Any]]) -> str:
if diagnostics is None:
return "## Compilation Diagnostics\n\nNo diagnostics artifact provided. Run `diagnose_compilation.py` to identify type-checking hotspots."
warnings = diagnostics.get("warnings", [])
summary = diagnostics.get("summary", {})
threshold = diagnostics.get("threshold_ms", 100)
lines = [
"## Compilation Diagnostics\n",
f"Threshold: {threshold}ms | "
f"Total warnings: {summary.get('total_warnings', 0)} | "
f"Function bodies: {summary.get('function_body_warnings', 0)} | "
f"Expressions: {summary.get('expression_warnings', 0)}\n",
]
if warnings:
lines.append("| Duration | Kind | File | Line | Name |")
lines.append("|---------:|------|------|-----:|------|")
for w in warnings[:30]:
short_file = Path(w["file"]).name
name = w.get("name", "") or "(expression)"
lines.append(
f"| {w['duration_ms']}ms | {w['kind']} | {short_file} | {w['line']} | {name} |"
)
if len(warnings) > 30:
lines.append(f"\n*... and {len(warnings) - 30} more warnings (see full artifact)*")
else:
lines.append("No type-checking hotspots found above threshold.")
return "\n".join(lines)
def _section_recommendations(recommendations: Optional[Dict[str, Any]]) -> str:
if recommendations is None:
return "## Prioritized Recommendations\n\nNo recommendations artifact provided."
items = recommendations.get("recommendations", [])
if not items:
return "## Prioritized Recommendations\n\nNo recommendations found."
lines = ["## Prioritized Recommendations\n"]
for i, item in enumerate(items, 1):
title = item.get("title", "Untitled")
lines.append(f"### {i}. {title}\n")
for field, label in [
("wait_time_impact", "Wait-Time Impact"),
("actionability", "Actionability"),
("category", "Category"),
("observed_evidence", "Evidence"),
("estimated_impact", "Impact"),
("confidence", "Confidence"),
("risk_level", "Risk"),
("scope", "Scope"),
]:
val = item.get(field)
if val is None:
continue
if isinstance(val, list):
lines.append(f"**{label}:**")
for entry in val:
lines.append(f"- {entry}")
else:
lines.append(f"**{label}:** {val}")
lines.append("")
return "\n".join(lines)
def _section_approval(recommendations: Optional[Dict[str, Any]]) -> str:
if recommendations is None:
return "## Approval Checklist\n\nNo recommendations to approve."
items = recommendations.get("recommendations", [])
if not items:
return "## Approval Checklist\n\nNo recommendations to approve."
lines = ["## Approval Checklist\n"]
for i, item in enumerate(items, 1):
title = item.get("title", "Untitled")
wait_impact = item.get("wait_time_impact", "")
impact = item.get("estimated_impact", "")
risk = item.get("risk_level", "")
actionability = item.get("actionability", "")
impact_str = wait_impact if wait_impact else impact
actionability_str = f" | Actionability: {actionability}" if actionability else ""
lines.append(f"- [ ] **{i}. {title}** -- Impact: {impact_str}{actionability_str} | Risk: {risk}")
return "\n".join(lines)
def _section_next_steps(benchmark: Dict[str, Any]) -> str:
build = benchmark.get("build", {})
command = build.get("command", "xcodebuild build")
lines = [
"## Next Steps\n",
"After implementing approved changes, re-benchmark with the same inputs:\n",
"```bash",
f"python3 scripts/benchmark_builds.py \\",
]
if build.get("entrypoint") == "workspace":
lines.append(f" --workspace {build.get('path', 'App.xcworkspace')} \\")
else:
lines.append(f" --project {build.get('path', 'App.xcodeproj')} \\")
lines.extend([
f" --scheme {build.get('scheme', 'App')} \\",
f" --configuration {build.get('configuration', 'Debug')} \\",
])
if build.get("destination"):
lines.append(f' --destination "{build["destination"]}" \\')
lines.append(" --output-dir .build-benchmark")
lines.append("```\n")
lines.append("Compare the new wall-clock medians against the baseline. Report results as:")
lines.append('"Your [clean/incremental] build now takes X.Xs (was Y.Ys) -- Z.Zs faster/slower."')
return "\n".join(lines)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate a Markdown build optimization report.")
parser.add_argument("--benchmark", required=True, help="Path to benchmark JSON artifact")
parser.add_argument("--recommendations", help="Path to recommendations JSON")
parser.add_argument("--diagnostics", help="Path to diagnostics JSON")
parser.add_argument("--project-path", help="Path to .xcodeproj for build settings audit")
parser.add_argument("--output", help="Output Markdown path (default: stdout)")
return parser.parse_args()
def main() -> int:
args = parse_args()
benchmark = json.loads(Path(args.benchmark).read_text())
benchmark["_artifact_path"] = args.benchmark
recommendations = None
if args.recommendations:
recommendations = json.loads(Path(args.recommendations).read_text())
diagnostics = None
if args.diagnostics:
diagnostics = json.loads(Path(args.diagnostics).read_text())
project_configs: Dict[str, Dict[str, str]] = {}
target_configs: Dict[str, Dict[str, Dict[str, str]]] = {}
if args.project_path:
pbxproj_path = Path(args.project_path) / "project.pbxproj"
if pbxproj_path.exists():
pbxproj = pbxproj_path.read_text()
project_configs = _parse_project_level_configs(pbxproj)
target_configs = _parse_target_configs(pbxproj)
if recommendations is None and project_configs:
auto = _auto_recommendations_from_audit(project_configs)
if auto["recommendations"]:
recommendations = auto
sections = [
"# Xcode Build Optimization Plan\n",
_section_context(benchmark),
_section_baseline(benchmark),
]
if project_configs:
sections.append(_section_settings_audit(project_configs, target_configs))
sections.append(_section_diagnostics(diagnostics))
sections.append(_section_recommendations(recommendations))
sections.append(_section_approval(recommendations))
sections.append(_section_next_steps(benchmark))
report = "\n\n".join(sections) + "\n"
if args.output:
Path(args.output).write_text(report)
print(f"Saved optimization report: {args.output}")
else:
print(report, end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Related skills
How it compares
Pick xcode-build-orchestrator when you need structured before-and-after Xcode timing files instead of one-off xcodebuild log grepping.
FAQ
What metric matters most?
Wall-clock build time the developer waits is primary; cumulative parallel task seconds must not be quoted as headline savings.
When are project files modified?
Only in Phase 2 after the developer approves items from .build-benchmark/optimization-plan.md.
What if benchmark variance is high?
Flag spread above 20 percent of median and recommend 5+ runs; only claim improvement if post-change median falls outside baseline range.
Is Xcode Build Orchestrator safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.