Add Xcode build optimization skills, references, scripts, and plugin metadata

First versioned snapshot of the full skill repository including:
- Five installable agent skills under skills/
- Shared references, schemas, and helper scripts
- Plugin and marketplace metadata for Claude Code
- GitHub workflows for release and README sync
- Compilation caching as a recommended build setting
- Four deeper Swift compiler diagnostic flags for compilation analysis
- Fixed SWIFT_COMPILATION_MODE to use singlefile (actual build setting value)
This commit is contained in:
Antoine van der Lee
2026-03-14 17:09:17 +01:00
parent 9942446a0e
commit cc642e0b52
33 changed files with 3410 additions and 2 deletions
+50
View File
@@ -0,0 +1,50 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "xcode-build-skills",
"version": "1.0.0",
"owner": {
"name": "Antoine van der Lee",
"email": "contact@avanderlee.com"
},
"metadata": {
"description": "Multi-skill Xcode build optimization plugin covering benchmarking, compile hotspots, project settings, SPM dependency analysis, and recommend-first orchestration."
},
"plugins": [
{
"name": "xcode-build-skills",
"description": "Five Xcode build optimization skills in one plugin: benchmark, code compilation analysis, project optimization, SPM build analysis, and a recommend-first orchestrator.",
"repository": "https://github.com/AvdLee/Xcode-Build-Optimization-Agent-Skill",
"version": "1.0.0",
"author": {
"name": "Antoine van der Lee",
"email": "contact@avanderlee.com"
},
"license": "MIT",
"category": "development",
"keywords": [
"xcode",
"builds",
"incremental-builds",
"clean-builds",
"swift",
"ios",
"macos",
"spm",
"benchmarking",
"compilation",
"project-settings",
"explicit-modules"
],
"tags": [
"xcode",
"build-optimization",
"swift",
"ios",
"spm",
"benchmarking",
"apple-platforms"
],
"source": "./"
}
]
}
+32
View File
@@ -0,0 +1,32 @@
{
"name": "xcode-build-skills",
"version": "1.0.0",
"description": "A multi-skill plugin for benchmarking and optimizing Xcode builds, compile hotspots, project settings, and Swift Package Manager dependency overhead.",
"author": {
"name": "Antoine van der Lee",
"email": "contact@avanderlee.com"
},
"repository": "https://github.com/AvdLee/Xcode-Build-Optimization-Agent-Skill",
"license": "MIT",
"keywords": [
"xcode",
"builds",
"incremental-builds",
"clean-builds",
"swift",
"ios",
"macos",
"spm",
"benchmarking",
"compilation",
"project-settings",
"explicit-modules"
],
"skills": [
"./skills/xcode-build-benchmark",
"./skills/xcode-code-compilation-optimizer",
"./skills/xcode-project-optimizer",
"./skills/spm-build-analysis",
"./skills/xcode-build-optimizer"
]
}
+69
View File
@@ -0,0 +1,69 @@
# GitHub Copilot Instructions: Xcode Build Optimization Skills
Review changes as an Agent Skills expert and Xcode build optimization reviewer.
## Repository Mission
Keep this repository focused on:
- Xcode clean and incremental build benchmarking
- compile hotspot analysis
- build settings and scheme auditing
- build script and dependency-analysis guidance
- Swift Package Manager build overhead and module variant analysis
Do not turn the repository into general iOS architecture guidance or product documentation.
## Agent Skill Requirements
- Every skill directory must contain a `SKILL.md` with valid `name` and `description` frontmatter.
- Descriptions must state both what the skill does and when to use it.
- Keep `SKILL.md` concise and delegate deeper material to one-level-deep reference files.
- Reference files should be discoverable from the matching `SKILL.md`.
## Behavior Constraints
- Default to recommend-first behavior.
- Never introduce instructions that apply project, build-setting, or source changes without explicit developer approval.
- Preserve evidence-driven workflows: command used, build type, target or package affected, and measured impact.
- Prefer deterministic shell commands and structured artifacts over ad hoc prose.
## Build Optimization Focus Areas
When reviewing changes, prioritize:
1. Benchmark correctness
- clean vs incremental builds are treated separately
- warm-up and repetition rules are explicit
- output artifacts are stored in `.build-benchmark/`
2. Evidence quality
- recommendations cite timing summaries, script phases, compiler diagnostics, or dependency data
- estimated impact and confidence are separated from observed evidence
3. Apple-aligned guidance
- target dependencies and build order accuracy
- script input/output declarations and `.xcfilelist` usage
- `DEFINES_MODULE`, module maps, and framework-qualified imports
- explicit module dependency and module variant reduction guidance
- explicit type information and simpler expressions for Swift compile efficiency
4. Safety
- scripts should analyze and format data, not mutate user projects
- workflows should avoid surprising side effects
## Common Issues to Flag
- Vague skill descriptions with no trigger conditions
- Overlong `SKILL.md` files that duplicate reference content
- Advice that skips measuring before optimizing
- Guidance that ignores incremental builds
- Destructive automation or auto-fix behavior without approval
- Inconsistent benchmark or recommendation fields across files
## Useful References
- `references/build-optimization-sources.md`
- `references/benchmark-artifacts.md`
- `references/recommendation-format.md`
- `schemas/build-benchmark.schema.json`
+96
View File
@@ -0,0 +1,96 @@
const fs = require("fs");
const path = require("path");
const README_PATH = path.join(process.cwd(), "README.md");
const SKILLS_ROOT = path.join(process.cwd(), "skills");
const SKILL_DIRS = [
"xcode-build-benchmark",
"xcode-code-compilation-optimizer",
"xcode-project-optimizer",
"spm-build-analysis",
"xcode-build-optimizer",
];
const beginMarker = "<!-- BEGIN SKILL STRUCTURE -->";
const endMarker = "<!-- END SKILL STRUCTURE -->";
const describeReference = (fileName) => {
const descriptions = {
"benchmarking-workflow.md": "Benchmark contract, clean vs incremental rules, and artifact expectations",
"code-compilation-checks.md": "Swift compile hotspot checks and code-level heuristics",
"project-audit-checks.md": "Build setting, script phase, and dependency audit checklist",
"spm-analysis-checks.md": "Package graph, plugin overhead, and module variant review guide",
"orchestration-report-template.md": "Prioritization, approval, and verification report template",
};
return descriptions[fileName] || "Reference file";
};
const buildTree = () => {
const lines = [
"xcode-build-optimization-agent-skill/",
" .claude-plugin/",
" marketplace.json",
" plugin.json",
" references/",
" benchmark-artifacts.md",
" build-optimization-sources.md",
" recommendation-format.md",
" schemas/",
" build-benchmark.schema.json",
" scripts/",
" benchmark_builds.py",
" render_recommendations.py",
" summarize_build_timing.py",
" skills/",
];
for (const skillDir of SKILL_DIRS) {
lines.push(` ${skillDir}/`);
lines.push(" SKILL.md");
const referencesDir = path.join(SKILLS_ROOT, skillDir, "references");
if (!fs.existsSync(referencesDir)) {
continue;
}
const references = fs
.readdirSync(referencesDir)
.filter((entry) => entry.endsWith(".md"))
.sort((left, right) => left.localeCompare(right));
if (references.length === 0) {
continue;
}
lines.push(" references/");
for (const fileName of references) {
lines.push(` ${fileName} - ${describeReference(fileName)}`);
}
}
return `\`\`\`text\n${lines.join("\n")}\n\`\`\``;
};
const syncReadme = () => {
if (!fs.existsSync(README_PATH)) {
throw new Error("README.md not found.");
}
const readme = fs.readFileSync(README_PATH, "utf8");
const start = readme.indexOf(beginMarker);
const end = readme.indexOf(endMarker);
if (start === -1 || end === -1 || end <= start) {
throw new Error("README skill structure markers not found.");
}
const prefix = readme.slice(0, start + beginMarker.length);
const suffix = readme.slice(end);
const replacement = `\n${buildTree()}\n`;
const updated = `${prefix}${replacement}${suffix}`;
if (updated !== readme) {
fs.writeFileSync(README_PATH, updated);
console.log("README structure block updated.");
} else {
console.log("README already up to date.");
}
};
syncReadme();
+89
View File
@@ -0,0 +1,89 @@
name: Release
on:
workflow_dispatch:
inputs:
version:
description: "Version to release (for example 1.0.1)"
required: true
type: string
permissions:
contents: write
jobs:
release:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
env:
VERSION: ${{ inputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.GITHUB_TOKEN }}
- name: Validate version input
run: |
set -euo pipefail
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid semver version: $VERSION"
exit 1
fi
- name: Fail if tag already exists
run: |
set -euo pipefail
if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
echo "Tag $VERSION already exists."
exit 1
fi
- name: Fail if version is not newer
run: |
set -euo pipefail
latest_tag="$(git tag -l '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n 1)"
if [[ -z "$latest_tag" ]]; then
echo "No existing semver tags found. Proceeding."
exit 0
fi
greatest="$(printf '%s\n%s\n' "$latest_tag" "$VERSION" | sort -V | tail -n 1)"
if [[ "$greatest" != "$VERSION" || "$latest_tag" == "$VERSION" ]]; then
echo "Version $VERSION must be greater than $latest_tag."
exit 1
fi
- name: Bump plugin manifest version
run: |
set -euo pipefail
jq --arg version "$VERSION" '.version = $version' .claude-plugin/plugin.json > .claude-plugin/plugin.json.tmp
mv .claude-plugin/plugin.json.tmp .claude-plugin/plugin.json
- name: Bump marketplace version fields
run: |
set -euo pipefail
jq --arg version "$VERSION" '.version = $version | .plugins |= map(.version = $version)' .claude-plugin/marketplace.json > .claude-plugin/marketplace.json.tmp
mv .claude-plugin/marketplace.json.tmp .claude-plugin/marketplace.json
- name: Commit and push version bump
run: |
set -euo pipefail
if git diff --quiet .claude-plugin/plugin.json .claude-plugin/marketplace.json; then
echo "No version change detected."
exit 1
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .claude-plugin/plugin.json .claude-plugin/marketplace.json
git commit -m "Bump version to $VERSION"
git push
- name: Create tag and GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ inputs.version }}
generate_release_notes: true
@@ -0,0 +1,42 @@
name: Sync README reference structure
on:
pull_request:
paths:
- "references/**"
- "skills/**"
jobs:
sync-readme:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Sync README structure block
run: node .github/scripts/sync-readme.js
- name: Commit README update
run: |
if git diff --quiet README.md; then
echo "README is up to date."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git add README.md
git commit -m "chore: sync README structure [skip ci]"
git push
+10
View File
@@ -60,3 +60,13 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Python bytecode
__pycache__/
*.pyc
# Local artifacts created while using this skill repo
.build-benchmark/
# Internal drafting aid; generated locally, not versioned
.agents/product-marketing-context.md
+31
View File
@@ -0,0 +1,31 @@
# Agent Guidance
This is a multi-skill Xcode build optimization repository.
## Layout
- `skills/` contains five installable Agent Skills, each with a `SKILL.md` entrypoint.
- `references/`, `schemas/`, and `scripts/` at the repo root are shared support files used by the skills.
- `.claude-plugin/` contains plugin and marketplace metadata.
## Skills
| Skill | Purpose |
|-------|---------|
| `xcode-build-benchmark` | Repeatable clean and incremental build benchmarking |
| `xcode-code-compilation-optimizer` | Swift compile hotspot analysis and source-level recommendations |
| `xcode-project-optimizer` | Build settings, scheme, script phase, and target dependency auditing |
| `spm-build-analysis` | Package graph, plugin overhead, and module variant review |
| `xcode-build-optimizer` | Orchestrator: benchmark, analyze, prioritize, approve, implement, re-benchmark |
## Rules
- Recommend-first by default. Never apply project, source, or package changes without explicit developer approval.
- Benchmark before optimizing. Use `.build-benchmark/` artifacts as evidence.
- Treat clean and incremental builds as separate metrics.
- The orchestrator (`xcode-build-optimizer`) is the primary entrypoint for end-to-end work.
- Shared references and schemas live at the repo root, not inside individual skills.
## Handoff Between Skills
When one skill identifies an issue outside its scope, read the target skill's `SKILL.md` under `skills/` and apply its workflow to the same project context. Pass along any benchmark artifacts or timing evidence already collected.
+92
View File
@@ -0,0 +1,92 @@
# Contributing to Xcode Build Optimization Agent Skills
Thanks for helping improve this repository. Contributions are welcome when they keep the repo focused on Xcode build analysis, deterministic benchmarking, and recommend-first optimization guidance.
## About This Repository
This is a multi-skill repository that follows the Agent Skills open format:
- Skills live under `skills/`, each with a `SKILL.md` entrypoint.
- Shared reference material lives in `references/`, `schemas/`, and `scripts/` at the repo root.
- The skills are intentionally recommend-first. They should not make project or source changes without explicit developer approval.
## Recommended Workflow
### Use skill-authoring assistance
If you have access to a skill-authoring assistant such as `skill-creator`, use it when updating any `SKILL.md` or reference file. That helps preserve:
- valid YAML frontmatter
- concise, trigger-oriented descriptions
- progressive disclosure into reference files
- consistent terminology across the five skills
### Keep the repo on mission
Contributions should stay within Xcode build optimization topics:
- benchmark design and interpretation
- compile hotspot analysis
- project and scheme build settings
- build script behavior
- Swift Package Manager build overhead
- explicit module dependency and module variant analysis
Avoid broad iOS architecture guidance, CI platform evangelism, or product documentation unrelated to build optimization.
## Quality Standards
### Skill quality
- Every `SKILL.md` must include valid `name` and `description` frontmatter.
- Descriptions must clearly state what the skill does and when to use it.
- Keep `SKILL.md` concise; push deep details into nearby reference files.
- Preserve recommend-first behavior, especially in `xcode-build-optimizer`.
### Technical quality
- Prefer deterministic instructions over vague advice.
- Cite Apple guidance, the SwiftLee article, or RocketSim docs when changing shared source summaries.
- Keep benchmark and recommendation formats consistent with `schemas/build-benchmark.schema.json` and `references/recommendation-format.md`.
- Avoid destructive automation. Scripts should gather evidence or format reports, not rewrite user projects.
### Scripts and automation
- Keep helper scripts dependency-light and portable.
- Use standard library tooling when possible.
- Avoid network calls in scripts and GitHub Actions unless they are clearly required.
- If you add or rename reference files, update the README structure block or let the sync workflow do it.
## Typical Contribution Types
- Improve one of the five skill entrypoints.
- Add a focused reference file for a missing Xcode build topic.
- Improve the benchmark schema or helper scripts.
- Clarify README installation or usage guidance.
- Update the stored source summaries as Apple or RocketSim docs evolve.
- Add your optimization results to the Community Results table in the README.
## Pull Request Process
1. Create a focused branch.
2. Keep related changes grouped together.
3. Verify the changed markdown still reads well as an Agent Skill.
4. If you touched scripts or workflows, run a quick sanity check locally.
5. Open a PR with a short summary, rationale, and any validation notes.
## Development Notes
- The README structure block is maintained by `.github/scripts/sync-readme.js`.
- Release automation bumps the shared plugin metadata version in `.claude-plugin/`.
- `.agents/product-marketing-context.md` is an internal draft aid and is intentionally ignored by Git.
## Resources
- Agent Skills format: <https://agentskills.io/home>
- Claude Code Agent Skills docs: <https://code.claude.com/en/skills>
- Apple Xcode build optimization docs: see `references/build-optimization-sources.md`
- SwiftLee build performance article: <https://www.avanderlee.com/optimization/analysing-build-performance-xcode/>
## Code of Conduct
Be respectful, specific, and evidence-driven. Favor measurable build improvements over subjective preferences.
+274 -2
View File
@@ -1,2 +1,274 @@
# Xcode-Build-Optimization-Agent-Skill
An Agent Skill helping you to optimize Xcode incremental and clean builds by running benchmarks and optimizing build settings.
# Xcode Build Optimization Agent Skills
Open-source Agent Skills for benchmarking and optimizing Xcode build performance across clean builds, incremental builds, compile hotspots, project settings, and Swift Package Manager overhead.
This repository is the "analyze and improve now" toolkit:
- benchmark both clean and incremental builds
- identify the biggest code, project, and package bottlenecks
- prioritize measured improvements
- keep the workflow recommend-first until a developer explicitly approves changes
For long-term monitoring across days, machines, Xcode versions, and teams, use [RocketSim Build Insights](https://www.rocketsim.app/docs/features/build-insights/build-insights/) and [Team Build Insights](https://www.rocketsim.app/docs/features/build-insights/team-build-insights/).
## See Also My Other Skills
- [Swift Concurrency Expert](https://github.com/AvdLee/Swift-Concurrency-Agent-Skill)
- [SwiftUI Expert](https://github.com/AvdLee/SwiftUI-Agent-Skill)
- [Core Data Expert](https://github.com/AvdLee/Core-Data-Agent-Skill)
- [Swift Testing Expert](https://github.com/AvdLee/Swift-Testing-Agent-Skill)
## Who This Is For
- iOS and macOS teams with slow local build loops
- developers investigating a recent build-time regression
- teams that want evidence-backed Xcode build optimization instead of guesswork
- developers who want a reusable Agent Skills package, not a one-off script
## Included Skills
This repo ships five installable skills:
- `xcode-build-benchmark`
- `xcode-code-compilation-optimizer`
- `xcode-project-optimizer`
- `spm-build-analysis`
- `xcode-build-optimizer`
### What Each Skill Does
- `xcode-build-benchmark`: Runs repeatable clean and incremental build benchmarks and writes timestamped `.build-benchmark/` artifacts.
- `xcode-code-compilation-optimizer`: Uses timing summaries and Swift frontend diagnostics to rank compile hotspots and source-level improvements.
- `xcode-project-optimizer`: Audits schemes, target dependencies, scripts, and build settings for project-level wins.
- `spm-build-analysis`: Reviews package graph shape, build plugins, module variants, and CI-sensitive dependency overhead.
- `xcode-build-optimizer`: Orchestrates the full workflow in two phases: analyze in plan mode (benchmark, run specialists, produce an optimization plan), then execute in agent mode (implement approved changes, re-benchmark, report deltas).
## Why Clean And Incremental Builds Both Matter
Clean builds expose:
- package and module setup cost
- full project graph overhead
- target structure and explicit-module issues
Incremental builds expose:
- edit-loop pain
- run script bottlenecks
- cache invalidation problems
- repeated package-plugin overhead
That distinction is central to this repo and follows both Apple's Xcode guidance and the SwiftLee workflow in [Build performance analysis for speeding up Xcode builds](https://www.avanderlee.com/optimization/analysing-build-performance-xcode/).
## How To Use These Skills
### Option A: Using skills.sh
Install a single skill:
```bash
npx skills add https://github.com/avdlee/xcode-build-optimization-agent-skill --skill xcode-build-benchmark
```
Swap the skill name for any of the five skills under `skills/`:
- `xcode-build-benchmark`
- `xcode-code-compilation-optimizer`
- `xcode-project-optimizer`
- `spm-build-analysis`
- `xcode-build-optimizer`
Start in plan mode and ask:
> Use the xcode build optimizer skill and analyze the current project for clean and incremental build improvements.
The agent produces `.build-benchmark/optimization-plan.md`. Review it, check the approval boxes, then switch to agent mode to implement.
### Option B: Claude Code Plugin
Install the shared plugin to make all five skills available under one namespace.
#### Personal Usage
1. Add the marketplace:
```bash
/plugin marketplace add AvdLee/Xcode-Build-Optimization-Agent-Skill
```
2. Install the plugin:
```bash
/plugin install xcode-build-skills@xcode-build-skills
```
The installed skill names will be available under the `xcode-build-skills` namespace.
#### Project Configuration
To enable the plugin for everyone working in a repository:
```json
{
"enabledPlugins": {
"xcode-build-skills@xcode-build-skills": true
},
"extraKnownMarketplaces": {
"xcode-build-skills": {
"source": {
"source": "github",
"repo": "AvdLee/Xcode-Build-Optimization-Agent-Skill"
}
}
}
}
```
### Option C: Manual Install
1. Clone this repository.
2. Install or symlink the specific skill folder from `skills/` that you want.
3. Ask your AI coding tool to use the corresponding skill.
Useful docs:
- [Codex Skills](https://developers.openai.com/codex/skills/#where-to-save-skills)
- [Claude Code Agent Skills](https://code.claude.com/en/skills)
- [Cursor Skills](https://cursor.com/docs/context/skills#enabling-skills)
## Recommend-First Workflow
The orchestrator uses a two-phase approach that separates analysis from implementation. This keeps the developer in control and produces a reviewable artifact before anything changes.
### Phase 1 -- Analyze in plan mode
Start the orchestrator in **plan mode** (read-only). The agent benchmarks your project, runs all specialist analyses, and produces a markdown optimization plan at `.build-benchmark/optimization-plan.md`.
The plan includes:
- baseline benchmark results with a full timing summary table
- a build settings audit with pass/fail indicators for Debug and Release
- compilation diagnostics showing type-checking hotspots (if any)
- prioritized recommendations with evidence, impact estimates, and risk levels
- an approval checklist where you check the items you want implemented
Example prompt for plan mode:
> Use the xcode build optimizer skill and analyze the current project for clean and incremental build improvements.
The agent will benchmark, analyze, and stop after producing the plan. No project files are modified.
### Phase 2 -- Execute in agent mode
After reviewing the plan, switch to **agent mode** and ask the agent to implement the approved items. It reads the optimization plan, applies only the checked recommendations, re-benchmarks with the same inputs, and reports the measured improvement.
Example prompt for agent mode:
> Implement the approved items from the optimization plan at .build-benchmark/optimization-plan.md, then re-benchmark to verify the improvements.
This two-phase approach is suitable for real repositories where build settings, package manifests, and source changes should never be modified casually. The plan file becomes the evidence trail -- shareable with teammates, reviewable in pull requests, and diffable over time.
## Shared Support Layer
The skills share:
- a common `.build-benchmark/` artifact contract
- a shared JSON schema for benchmark output
- helper scripts for benchmarking, timing-summary parsing, compilation diagnostics, report generation, and recommendation rendering
- a build settings best practices reference for the pass/fail audit
- a single source summary file so README and skill guidance stay aligned
## Skill Structure
<!-- BEGIN SKILL STRUCTURE -->
```text
xcode-build-optimization-agent-skill/
.claude-plugin/
marketplace.json
plugin.json
references/
benchmark-artifacts.md
build-optimization-sources.md
build-settings-best-practices.md
recommendation-format.md
schemas/
build-benchmark.schema.json
scripts/
benchmark_builds.py
diagnose_compilation.py
generate_optimization_report.py
render_recommendations.py
summarize_build_timing.py
skills/
xcode-build-benchmark/
SKILL.md
references/
benchmarking-workflow.md - Benchmark contract, clean vs incremental rules, and artifact expectations
xcode-code-compilation-optimizer/
SKILL.md
references/
code-compilation-checks.md - Swift compile hotspot checks and code-level heuristics
xcode-project-optimizer/
SKILL.md
references/
project-audit-checks.md - Build setting, script phase, and dependency audit checklist
spm-build-analysis/
SKILL.md
references/
spm-analysis-checks.md - Package graph, plugin overhead, and module variant review guide
xcode-build-optimizer/
SKILL.md
references/
orchestration-report-template.md - Prioritization, approval, and verification report template
```
<!-- END SKILL STRUCTURE -->
## Research Basis
This repo deliberately aligns with:
- Apple's incremental-build guidance: accurate target dependencies, script input/output declarations, module maps, and parallel-friendly project structure
- Apple's compile-efficiency guidance: explicit type information, simpler expressions, narrower bridging surfaces, and framework-qualified imports
- Apple's explicit module dependency guidance: reducing duplicate module variants caused by configuration drift
- the SwiftLee workflow for measuring with Build Timeline, Build Timing Summary, and Swift frontend diagnostics
The stored reference summaries live in `references/build-optimization-sources.md`.
## RocketSim Positioning
This repo helps you optimize point-in-time build performance with an agent-guided workflow.
RocketSim complements it by monitoring build performance over time:
- automatic clean vs incremental build tracking
- duration trends and percentile metrics
- machine, Xcode, and macOS comparisons
- team-wide visibility without custom build scripts
If you want to catch regressions earlier and see whether your build times are improving over weeks or months, use [RocketSim Build Insights](https://www.rocketsim.app/docs/features/build-insights/build-insights/) after you apply the improvements from this repo.
## Community Results
Real-world improvements reported by developers who used these skills. Add your own results by opening a pull request.
The `xcode-build-optimizer` orchestrator generates your table row at the end of every optimization run, so contributing is a single copy-paste.
| App | Incremental Before | Incremental After | Clean Before | Clean After |
|-----|-------------------:|------------------:|-------------:|------------:|
## Contributing
Contributions are welcome when they keep the repo focused on Xcode build optimization and Agent Skills format quality.
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for:
- skill-authoring guidance
- repo scope and quality standards
- workflow notes for scripts and README sync
## About The Author
Created by [Antoine van der Lee](https://www.avanderlee.com), creator of SwiftLee and RocketSim. The practical build workflow in this repository is informed by the SwiftLee article [Build performance analysis for speeding up Xcode builds](https://www.avanderlee.com/optimization/analysing-build-performance-xcode/) and ongoing work on RocketSim Build Insights.
## License
This repository is available under the MIT License. See [LICENSE](LICENSE) for details.
+64
View File
@@ -0,0 +1,64 @@
# 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.
## 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>-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 `clean` and `incremental` run arrays
- summary statistics for each build type
- parsed timing-summary categories
- free-form notes for caveats or noise
## Clean And Incremental Separation
Do not merge clean and incremental measurements into a single list. They answer different questions:
- Clean builds show full build-system, package, and module setup cost.
- 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
## 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 [../schemas/build-benchmark.schema.json](../schemas/build-benchmark.schema.json).
+87
View File
@@ -0,0 +1,87 @@
# Build Optimization Sources
This file stores the external sources that the README and skill docs should cite consistently.
## Apple: Improving the speed of incremental builds
Source:
- <https://developer.apple.com/documentation/xcode/improving-the-speed-of-incremental-builds>
Key takeaways:
- Measure first with `Build With Timing Summary` or `xcodebuild -showBuildTimingSummary`.
- Accurate target dependencies improve correctness and parallelism.
- Run scripts should declare inputs and outputs so Xcode can skip unnecessary work.
- `.xcfilelist` files are appropriate when scripts have many inputs or outputs.
- Custom frameworks and libraries benefit from module maps, typically by enabling `DEFINES_MODULE`.
- Module reuse is strongest when related sources compile with consistent options.
- Breaking monolithic targets into better-scoped modules can reduce unnecessary rebuilds.
## Apple: Improving build efficiency with good coding practices
Source:
- <https://developer.apple.com/documentation/xcode/improving-build-efficiency-with-good-coding-practices>
Key takeaways:
- Use framework-qualified imports when module maps are available.
- Keep Objective-C bridging surfaces narrow.
- Prefer explicit type information when inference becomes expensive.
- Use explicit delegate protocols instead of overly generic delegate types.
- Simplify complex expressions that are hard for the compiler to type-check.
## Apple: Building your project with explicit module dependencies
Source:
- <https://developer.apple.com/documentation/xcode/building-your-project-with-explicit-module-dependencies>
Key takeaways:
- Explicit module builds make module work visible in the build log and improve scheduling.
- Repeated builds of the same module often point to avoidable module variants.
- Inconsistent build options across targets can force duplicate module builds.
- Timing summaries can reveal option drift that prevents module reuse.
## SwiftLee: Build performance analysis for speeding up Xcode builds
Source:
- <https://www.avanderlee.com/optimization/analysing-build-performance-xcode/>
Key takeaways:
- Clean and incremental builds should both be measured because they reveal different problems.
- Build Timeline and Build Timing Summary are practical starting points for build optimization.
- Build scripts often produce large incremental-build wins when guarded correctly.
- `-warn-long-function-bodies` and `-warn-long-expression-type-checking` help surface compile hotspots.
- Typical debug and release build setting mismatches are worth auditing, especially in older projects.
## Apple: Xcode Release Notes -- Compilation Caching
Source:
- Xcode Release Notes (149700201)
Key takeaways:
- Compilation caching is an opt-in feature for Swift and C-family languages.
- It caches prior compilation results and reuses them when the same source inputs are recompiled.
- Branch switching and clean builds benefit the most.
- Can be enabled via the "Enable Compilation Caching" build setting or per-user project settings.
## RocketSim Docs: Build Insights
Sources:
- <https://www.rocketsim.app/docs/features/build-insights/build-insights/>
- <https://www.rocketsim.app/docs/features/build-insights/team-build-insights/>
Key takeaways:
- RocketSim automatically tracks clean vs incremental builds over time without build scripts.
- It reports build counts, duration trends, and percentile-based metrics such as p75 and p95.
- Team Build Insights adds machine, Xcode, and macOS comparisons for cross-team visibility.
- This repository is best positioned as the point-in-time analyze-and-improve toolkit, while RocketSim is the monitor-over-time companion.
+187
View File
@@ -0,0 +1,187 @@
# 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. `wholemodule` recompiles 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-dsym` generates a separate dSYM bundle which adds overhead. Plain `dwarf` embeds 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
## 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:** `-O` or `-Osize`
- **Why:** Produces optimized binaries. `-Osize` trades 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_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.
- **Risk:** Low -- can also be enabled via per-user project settings so it does not need to be committed to the shared project file.
## 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_MODE`
- `SWIFT_OPTIMIZATION_LEVEL`
- `ONLY_ACTIVE_ARCH`
- `DEBUG_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_LEVEL`
- `SWIFT_COMPILATION_MODE`
- `OTHER_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 choice
- `SWIFT_UPCOMING_FEATURE_*` -- language migration choice
- `SWIFT_APPROACHABLE_CONCURRENCY` -- language migration choice
- `SWIFT_ACTIVE_COMPILATION_CONDITIONS` values beyond `DEBUG` (e.g., `WIDGETS`, `APPCLIP`) -- intentional per-target customization
## Checklist Output Format
When reporting results, use this structure:
```markdown
### 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_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
...
```
+70
View File
@@ -0,0 +1,70 @@
# 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:
- `title`
- `category`
- `observed_evidence`
- `estimated_impact`
- `confidence`
- `approval_required`
- `benchmark_verification_status`
## Suggested Optional Fields
- `scope`
- `affected_files`
- `affected_targets`
- `affected_packages`
- `implementation_notes`
- `risk_level`
## JSON Example
```json
{
"recommendations": [
{
"title": "Guard a release-only symbol upload script",
"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. observed evidence
3. estimated impact
4. confidence
5. approval required
6. benchmark verification status
That makes it easier for the developer to approve or reject specific items quickly.
## Verification Status Values
Recommended values:
- `Not yet verified`
- `Queued for verification`
- `Verified improvement`
- `No measurable improvement`
- `Inconclusive due to benchmark noise`
+220
View File
@@ -0,0 +1,220 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Xcode Build Benchmark Artifact",
"type": "object",
"required": [
"schema_version",
"created_at",
"build",
"runs",
"summary"
],
"properties": {
"schema_version": {
"type": "string",
"enum": ["1.0.0", "1.1.0"]
},
"created_at": {
"type": "string",
"format": "date-time"
},
"build": {
"type": "object",
"required": [
"entrypoint",
"scheme",
"configuration",
"destination",
"command"
],
"properties": {
"entrypoint": {
"type": "string",
"enum": [
"project",
"workspace"
]
},
"path": {
"type": "string"
},
"scheme": {
"type": "string"
},
"configuration": {
"type": "string"
},
"destination": {
"type": "string"
},
"derived_data_path": {
"type": "string"
},
"command": {
"type": "string"
}
},
"additionalProperties": true
},
"environment": {
"type": "object",
"properties": {
"host": {
"type": "string"
},
"xcode_version": {
"type": "string"
},
"macos_version": {
"type": "string"
}
},
"additionalProperties": true
},
"runs": {
"type": "object",
"required": [
"clean",
"incremental"
],
"properties": {
"clean": {
"type": "array",
"items": {
"$ref": "#/definitions/run"
}
},
"incremental": {
"type": "array",
"items": {
"$ref": "#/definitions/run"
}
}
},
"additionalProperties": false
},
"summary": {
"type": "object",
"required": [
"clean",
"incremental"
],
"properties": {
"clean": {
"$ref": "#/definitions/stats"
},
"incremental": {
"$ref": "#/definitions/stats"
}
},
"additionalProperties": false
},
"notes": {
"type": "array",
"items": {
"type": "string"
}
}
},
"definitions": {
"run": {
"type": "object",
"required": [
"id",
"build_type",
"duration_seconds",
"success",
"command"
],
"properties": {
"id": {
"type": "string"
},
"build_type": {
"type": "string",
"enum": [
"clean",
"incremental"
]
},
"duration_seconds": {
"type": "number",
"minimum": 0
},
"success": {
"type": "boolean"
},
"command": {
"type": "string"
},
"exit_code": {
"type": "integer"
},
"raw_log_path": {
"type": "string"
},
"timing_summary_categories": {
"type": "array",
"items": {
"$ref": "#/definitions/category"
}
}
},
"additionalProperties": true
},
"category": {
"type": "object",
"required": [
"name",
"seconds"
],
"properties": {
"name": {
"type": "string"
},
"seconds": {
"type": "number",
"minimum": 0
},
"task_count": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": true
},
"stats": {
"type": "object",
"required": [
"count",
"min_seconds",
"max_seconds",
"median_seconds",
"average_seconds"
],
"properties": {
"count": {
"type": "integer",
"minimum": 0
},
"min_seconds": {
"type": "number",
"minimum": 0
},
"max_seconds": {
"type": "number",
"minimum": 0
},
"median_seconds": {
"type": "number",
"minimum": 0
},
"average_seconds": {
"type": "number",
"minimum": 0
}
},
"additionalProperties": true
}
}
}
+224
View File
@@ -0,0 +1,224 @@
#!/usr/bin/env python3
import argparse
import json
import os
import platform
import re
import statistics
import subprocess
import sys
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(
"--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 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
runs = {"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))
for index in range(1, args.repeats + 1):
runs["incremental"].append(
measure_build(base_command, artifact_stem, output_dir, "incremental", index)
)
artifact = {
"schema_version": "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": {
"clean": stats_for(runs["clean"]),
"incremental": stats_for(runs["incremental"]),
},
"notes": [],
}
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")
print(f"Incremental median: {artifact['summary']['incremental']['median_seconds']}s")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+273
View File
@@ -0,0 +1,273 @@
#!/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())
+416
View File
@@ -0,0 +1,416 @@
#!/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", "incremental", "Incremental recompiles only changed files"),
("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"),
]
_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 ("incremental",):
return True
return False
if expected == "-O" and actual in ("-O", '"-O"', '"-Osize"', "-Osize"):
return True
return actual.strip('"') == expected
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
# ---------------------------------------------------------------------------
# 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", {})
incremental = summary.get("incremental", {})
lines = [
"## Baseline Benchmarks\n",
f"| Metric | Clean | Incremental |",
f"|--------|-------|-------------|",
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)} |",
]
for build_type in ("clean", "incremental"):
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)
lines.append(f"\n### {build_type.title()} Build Timing Summary\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### 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 [
("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")
impact = item.get("estimated_impact", "")
risk = item.get("risk_level", "")
lines.append(f"- [ ] **{i}. {title}** -- Impact: {impact} | 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 medians against the baseline to verify improvements.")
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)
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())
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
import argparse
import json
from pathlib import Path
FIELD_LABELS = [
("category", "Category"),
("observed_evidence", "Observed evidence"),
("estimated_impact", "Estimated impact"),
("confidence", "Confidence"),
("approval_required", "Approval required"),
("benchmark_verification_status", "Benchmark verification status"),
("scope", "Scope"),
("risk_level", "Risk level"),
]
def render_recommendation(item: dict, index: int) -> str:
lines = [f"## {index}. {item.get('title', 'Untitled recommendation')}"]
for key, label in FIELD_LABELS:
value = item.get(key)
if value is None:
continue
if isinstance(value, list):
lines.append(f"**{label}:**")
for entry in value:
lines.append(f"- {entry}")
continue
lines.append(f"**{label}:** {value}")
if item.get("implementation_notes"):
lines.append("**Implementation notes:**")
for entry in item["implementation_notes"]:
lines.append(f"- {entry}")
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser(description="Render recommendation JSON as Markdown.")
parser.add_argument("input", help="Path to a recommendation JSON file")
parser.add_argument("--output", help="Optional output Markdown path")
args = parser.parse_args()
payload = json.loads(Path(args.input).read_text())
recommendations = payload.get("recommendations", [])
sections = ["# Xcode Build Recommendations", ""]
for index, item in enumerate(recommendations, start=1):
sections.append(render_recommendation(item, index))
sections.append("")
markdown = "\n".join(sections).rstrip() + "\n"
if args.output:
Path(args.output).write_text(markdown)
else:
print(markdown, end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
import argparse
import json
import re
from pathlib import Path
from typing import Dict, List, Optional
_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 summarize_json(path: Path, top: int) -> str:
payload = json.loads(path.read_text())
sections = []
for build_type in ("clean", "incremental"):
aggregate: Dict[str, float] = {}
for run in payload.get("runs", {}).get(build_type, []):
for category in run.get("timing_summary_categories", []):
aggregate[category["name"]] = aggregate.get(category["name"], 0.0) + category["seconds"]
ranked = sorted(aggregate.items(), key=lambda item: item[1], reverse=True)[:top]
sections.append(f"{build_type.title()} top categories:")
if not ranked:
sections.append(" (no parsed timing summary categories)")
else:
for name, seconds in ranked:
sections.append(f" - {name}: {seconds:.3f}s total")
return "\n".join(sections)
def summarize_log(path: Path, top: int) -> str:
categories = parse_timing_summary(path.read_text())[:top]
if not categories:
return "No timing summary categories detected."
lines = ["Top timing summary categories:"]
for category in categories:
lines.append(f" - {category['name']}: {category['seconds']:.3f}s")
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser(description="Summarize Xcode build timing output.")
parser.add_argument("input", help="Path to a benchmark JSON artifact or raw xcodebuild log")
parser.add_argument("--top", type=int, default=5, help="Number of categories to display")
args = parser.parse_args()
path = Path(args.input)
if path.suffix == ".json":
print(summarize_json(path, args.top))
else:
print(summarize_log(path, args.top))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+54
View File
@@ -0,0 +1,54 @@
---
name: spm-build-analysis
description: Analyze Swift Package Manager dependencies, package plugins, module variants, and CI-oriented build overhead that slow Xcode builds. Use when a developer suspects packages, plugins, or dependency graph shape are hurting clean or incremental build performance, mentions SPM slowness, package resolution time, build plugin overhead, or duplicate module builds from configuration drift.
---
# SPM Build Analysis
Use this skill when package structure, plugins, or dependency configuration are likely contributing to slow Xcode builds.
## Core Rules
- Treat package analysis as evidence gathering first, not a mandate to replace dependencies.
- Separate package-graph issues from project-setting issues.
- Do not rewrite package manifests or dependency sources without explicit approval.
## What To Inspect
- `Package.swift` and `Package.resolved`
- local packages vs remote packages
- package plugin and build-tool usage
- binary target footprint
- dependency layering, repeated imports, and potential cycles
- build logs or timing summaries that show package-related work
## Focus Areas
- package graph shape and how much work changes trigger downstream
- plugin overhead during local development and CI
- checkout or fetch cost signals that show up in clean environments
- configuration drift that forces duplicate module builds
- risks from package targets that use different macros or options while sharing dependencies
## Explicit Module Dependency Angle
When the same module appears multiple times in timing output, investigate whether different package or target options are forcing extra module variants. Uniform options often matter more than shaving a small amount of source code.
## Reporting Format
For each finding, include:
- evidence
- affected package or plugin
- likely clean-build vs incremental-build impact
- CI impact if relevant
- estimated impact
- approval requirement
If the main problem is not package-related, hand off to [`xcode-project-optimizer`](../xcode-project-optimizer/SKILL.md) or [`xcode-code-compilation-optimizer`](../xcode-code-compilation-optimizer/SKILL.md) by reading the target skill's SKILL.md and applying its workflow to the same project context.
## Additional Resources
- For the detailed audit checklist, see [references/spm-analysis-checks.md](references/spm-analysis-checks.md)
- For the shared recommendation structure, see [../../references/recommendation-format.md](../../references/recommendation-format.md)
- For source citations, see [../../references/build-optimization-sources.md](../../references/build-optimization-sources.md)
@@ -0,0 +1,40 @@
# SPM Analysis Checks
Use this reference when package dependencies or package plugins are suspected build bottlenecks.
## Package Graph Checks
- Identify large umbrella packages that trigger widespread rebuilds.
- Look for dependency layering that forces many downstream targets to recompile.
- Flag local package arrangements that cause broad invalidation after small edits.
## Package Plugin Checks
- List build-tool and command plugins involved in the build.
- Measure whether plugins run during incremental builds even when no relevant input changed.
- Call out plugins that return quickly but still add fixed overhead to every build.
## Binary And Remote Dependency Checks
- Note binary target size and extraction overhead for clean environments.
- Highlight remote checkout or fetch costs that matter for CI or fresh machines.
- Compare remote vs local package tradeoffs when iteration speed matters more than distribution convenience.
## Module Variant Checks
- Look for the same dependency module being built with different options.
- Compare macros, language mode, and configuration-sensitive options across dependents.
- Prefer configuration alignment when it reduces repeated module builds safely.
## CI-Specific Checks
- Fresh checkout cost
- plugin invocation cost
- cache hit sensitivity
- redundant package resolution work
## Recommendation Prioritization
- High: package plugins or graph structure repeatedly inflating incremental builds.
- Medium: configuration drift that causes duplicate module variants.
- Low: clean-environment checkout costs that barely affect local iteration.
+80
View File
@@ -0,0 +1,80 @@
---
name: xcode-build-benchmark
description: Benchmark Xcode clean and incremental builds with repeatable inputs, timing summaries, and timestamped `.build-benchmark/` artifacts. Use when a developer wants a baseline, wants to compare before and after changes, asks to measure build performance, mentions build times, build duration, how long builds take, or wants to know if builds got faster or slower.
---
# Xcode Build Benchmark
Use this skill to produce a repeatable Xcode build baseline before anyone tries to optimize build times.
## Core Rules
- Measure before recommending changes.
- Capture clean and incremental builds separately.
- Keep the command, destination, configuration, scheme, and warm-up rules consistent across runs.
- Write a timestamped JSON artifact to `.build-benchmark/`.
- Do not change project files as part of benchmarking.
## Inputs To Collect
Confirm or infer:
- workspace or project path
- scheme
- configuration
- destination
- whether the user wants simulator or device numbers
- whether a custom `DerivedData` path is needed
If the project has both clean-build and incremental-build pain, benchmark both. That is the default.
## Default Workflow
1. Normalize the build command and note every flag that affects caching or module reuse.
2. Run one warm-up build if needed to validate that the command succeeds.
3. Run 3 clean builds.
4. Run 3 incremental builds without source changes between runs unless the developer is testing a specific edit loop.
5. Save the raw results and summary into `.build-benchmark/`.
6. Report medians and spread, not just the single fastest run.
## Preferred Command Path
Use the shared helper when possible:
```bash
python3 scripts/benchmark_builds.py \
--workspace App.xcworkspace \
--scheme MyApp \
--configuration Debug \
--destination "platform=iOS Simulator,name=iPhone 16" \
--output-dir .build-benchmark
```
If you cannot use the helper script, run equivalent `xcodebuild` commands with `-showBuildTimingSummary` and preserve the raw output.
## Required Output
Return:
- clean build median, min, max
- incremental build median, min, max
- biggest timing-summary categories
- environment details that could affect comparisons
- path to the saved artifact
If results are noisy, say so and recommend rerunning under calmer conditions.
## When To Stop
Stop after measurement if the user only asked for benchmarking. If they want optimization guidance, hand off the artifact to the relevant specialist by reading its SKILL.md and applying its workflow to the same project context:
- [`xcode-code-compilation-optimizer`](../xcode-code-compilation-optimizer/SKILL.md)
- [`xcode-project-optimizer`](../xcode-project-optimizer/SKILL.md)
- [`spm-build-analysis`](../spm-build-analysis/SKILL.md)
- [`xcode-build-optimizer`](../xcode-build-optimizer/SKILL.md) for full orchestration
## Additional Resources
- For the benchmark contract, see [references/benchmarking-workflow.md](references/benchmarking-workflow.md)
- For the shared artifact format, see [../../references/benchmark-artifacts.md](../../references/benchmark-artifacts.md)
- For the JSON schema, see [../../schemas/build-benchmark.schema.json](../../schemas/build-benchmark.schema.json)
@@ -0,0 +1,67 @@
# Benchmarking Workflow
Use this reference when you need the full operational contract for collecting Xcode build measurements.
## Goal
Produce a benchmark artifact that another skill can trust without rerunning the same setup discovery.
## Benchmark Contract
- Measure both clean and incremental builds unless the user narrows the scope.
- Use the same scheme, configuration, destination, and command flags for all measured runs.
- Record the exact command and any environment overrides.
- Keep clean and incremental runs in separate arrays in the artifact.
- Save wall-clock timing plus any parsed timing-summary categories.
## Suggested Run Counts
- Clean builds: 3 measured runs
- Incremental builds: 3 measured runs
- Warm-up: 0 to 1 validation run, excluded from the summary unless the user explicitly wants it included
## Clean Build Rules
- Clear build products with `xcodebuild clean` or an equivalent clean-build-folder step before each measured clean run.
- Do not change scheme, destination, or configuration between runs.
- If the command fails, store the failure and stop rather than mixing failed and successful runs.
## Incremental Build Rules
- Use the same build command after a successful baseline build.
- Do not clean between incremental runs.
- If the user wants edit-loop benchmarking, note the file change strategy explicitly in the artifact.
- If there are no source edits between runs, label the result as no-edit incremental timing.
## What To Capture
At minimum, keep:
- timestamp
- host machine info if available
- Xcode version if available
- workspace or project path
- scheme, configuration, destination
- exact `xcodebuild` command
- duration per run
- success or failure
- parsed timing-summary categories
- notes on warm-up behavior or unusual noise
## Reporting Guidance
Use medians for the headline number. Also include:
- min and max
- range
- category totals from the timing summary
- obvious outliers or instability
## Handoff Expectations
The next optimization skill should be able to answer:
- Is the main problem clean, incremental, or both?
- Which build categories dominate time?
- Which command produced the evidence?
- Is the baseline trustworthy enough to compare before and after changes?
+140
View File
@@ -0,0 +1,140 @@
---
name: xcode-build-optimizer
description: Orchestrate Xcode build optimization by benchmarking first, running the specialist skills, prioritizing findings, requesting explicit approval, and re-benchmarking after approved changes. Use when a developer wants an end-to-end build optimization workflow, asks to speed up Xcode builds, wants a full build audit, or needs a recommend-first optimization pass covering compilation, project settings, and packages.
---
# Xcode Build Optimizer
Use this skill as the recommend-first entrypoint for end-to-end Xcode build optimization work.
## Non-Negotiable Rules
- 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 delta.
## Two-Phase Workflow
The orchestration is designed as two distinct phases separated by developer review. Use **plan mode** for the first phase so the agent cannot modify any files. Switch to **agent mode** for the second phase to implement approved changes and verify them.
### Phase 1 -- Analyze (plan mode)
Run this phase in plan mode. The agent benchmarks, analyzes, and produces a reviewable optimization plan without touching any project files.
1. Collect the build target context: workspace or project, scheme, configuration, destination, and current pain point.
2. Run `xcode-build-benchmark` to establish a baseline if no fresh benchmark exists.
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.
4. If `SwiftCompile` or `CompileC` dominate the timing summary, run `diagnose_compilation.py` with the same project inputs to capture type-checking hotspots.
5. Run the specialist analyses that fit the evidence by reading each skill's SKILL.md and applying its workflow:
- [`xcode-code-compilation-optimizer`](../xcode-code-compilation-optimizer/SKILL.md)
- [`xcode-project-optimizer`](../xcode-project-optimizer/SKILL.md)
- [`spm-build-analysis`](../spm-build-analysis/SKILL.md)
6. Merge findings into a single prioritized improvement plan.
7. 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.
8. 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.
9. Read `.build-benchmark/optimization-plan.md` and identify the approved items from the approval checklist.
10. Implement only the approved changes.
11. Re-run the benchmark with the same inputs used in phase 1.
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
Rank items using:
- measured evidence strength
- expected impact on incremental builds
- expected impact on clean builds
- implementation risk
- confidence
Prefer changes that are:
- measurable
- reversible
- low-risk
- likely to improve the most common developer loop first
## Approval Gate
Before implementing anything, present a short approval list that includes:
- recommendation name
- evidence summary
- estimated impact
- affected files or settings
- whether the change is low, medium, or high risk
Wait for explicit developer approval.
## Post-Approval Execution
After approval:
- implement only the approved items
- keep changes scoped
- note any deviations from the original recommendation plan
- re-benchmark with the same benchmark contract
## Final Report
The final report must include:
- baseline clean and incremental medians
- post-change clean and incremental medians
- absolute and percentage deltas
- what changed
- what was intentionally left unchanged
- confidence notes if noise prevents a strong conclusion
- a ready-to-paste community results row and a link to open a PR (see the report template)
## Preferred Command Paths
### Benchmark
```bash
python3 scripts/benchmark_builds.py \
--project App.xcodeproj \
--scheme MyApp \
--configuration Debug \
--destination "platform=iOS Simulator,name=iPhone 16" \
--output-dir .build-benchmark
```
### Compilation Diagnostics
```bash
python3 scripts/diagnose_compilation.py \
--project App.xcodeproj \
--scheme MyApp \
--configuration Debug \
--destination "platform=iOS Simulator,name=iPhone 16" \
--threshold 100 \
--output-dir .build-benchmark
```
### Optimization Report
```bash
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.md
```
## Additional Resources
- For the report template, see [references/orchestration-report-template.md](references/orchestration-report-template.md)
- For benchmark artifact requirements, see [../../references/benchmark-artifacts.md](../../references/benchmark-artifacts.md)
- For the recommendation format, see [../../references/recommendation-format.md](../../references/recommendation-format.md)
- For build settings best practices, see [../../references/build-settings-best-practices.md](../../references/build-settings-best-practices.md)
@@ -0,0 +1,108 @@
# 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.
```markdown
# 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 | Incremental |
|--------|-------|-------------|
| Median | 0.000s | 0.000s |
| Min | 0.000s | 0.000s |
| Max | 0.000s | 0.000s |
| Runs | 3 | 3 |
### Clean Build Timing Summary
| Category | Tasks | Seconds |
|----------|------:|--------:|
| SwiftCompile | 325 | 271.245s |
| SwiftEmitModule | 30 | 23.625s |
| ... | ... | ... |
## Build Settings Audit
### Debug Configuration
- [x] `SWIFT_COMPILATION_MODE`: `(unset)` (recommended: `incremental`)
- [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`)
### 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
**Category:** project
**Evidence:** ...
**Impact:** High
**Confidence:** High
**Risk:** Low
## Approval Checklist
- [ ] **1. Recommendation title** -- Impact: High | Risk: Low
- [ ] **2. Another recommendation** -- Impact: Medium | Risk: Low
## Next Steps
After implementing approved changes, re-benchmark with the same inputs:
...
Compare the new medians against the baseline to verify improvements.
## Verification (post-approval)
- Post-change clean median:
- Post-change incremental median:
- Clean delta:
- Incremental delta:
## 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> | <baseline-incremental> | <post-change-incremental> | <baseline-clean> | <post-change-clean> |
Open a PR: https://github.com/AvdLee/Xcode-Build-Optimization-Agent-Skill/edit/main/README.md
```
## Usage 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_CONCURRENCY` or `SWIFT_UPCOMING_FEATURE_*`.
- The Compilation Diagnostics section is populated by `diagnose_compilation.py`. If not run, note that it was skipped.
@@ -0,0 +1,82 @@
---
name: xcode-code-compilation-optimizer
description: Analyze Swift and mixed-language compile hotspots using build timing summaries and Swift frontend diagnostics, then produce a recommend-first source-level optimization plan. Use when a developer reports slow compilation, type-checking warnings, expensive clean-build compile phases, long CompileSwiftSources tasks, warn-long-function-bodies output, or wants to speed up Swift type checking.
---
# Xcode Code Compilation Optimizer
Use this skill when compile time, not just general project configuration, looks like the bottleneck.
## Core Rules
- Start from evidence, ideally a recent `.build-benchmark/` artifact or raw timing-summary output.
- Prefer analysis-only compiler flags over persistent project edits during investigation.
- Rank findings by expected compile-time impact, not by how easy they are to describe.
- Do not edit source or build settings without explicit developer approval.
## What To Inspect
- `Build Timing Summary` output from a clean build
- long-running `CompileSwiftSources` or per-file compilation tasks
- ad hoc runs with:
- `-Xfrontend -warn-long-expression-type-checking=<ms>`
- `-Xfrontend -warn-long-function-bodies=<ms>`
- deeper diagnostic flags for thorough investigation:
- `-Xfrontend -debug-time-compilation` -- per-file compile times to rank the slowest files
- `-Xfrontend -debug-time-function-bodies` -- per-function compile times (unfiltered, complements the threshold-based warning flags)
- `-Xswiftc -driver-time-compilation` -- driver-level timing to isolate driver overhead
- `-Xfrontend -stats-output-dir <path>` -- detailed compiler statistics (JSON) per compilation unit for root-cause analysis
- mixed Swift and Objective-C surfaces that increase bridging work
## Analysis Workflow
1. Identify whether the main issue is broad compilation volume or a few extreme hotspots.
2. Parse timing-summary categories and rank the biggest compile contributors.
3. Run the diagnostics script to surface type-checking hotspots:
```bash
python3 scripts/diagnose_compilation.py \
--project App.xcodeproj \
--scheme MyApp \
--configuration Debug \
--destination "platform=iOS Simulator,name=iPhone 16" \
--threshold 100 \
--output-dir .build-benchmark
```
This produces a ranked list of functions and expressions that exceed the millisecond threshold. Use the diagnostics artifact alongside source inspection to focus on the most expensive files first.
4. Map the evidence to a concrete recommendation list.
5. Separate code-level suggestions from project-level or module-level suggestions.
## Apple-Derived Checks
Look for these patterns first:
- missing explicit type information in expensive expressions
- complex chained or nested expressions that are hard to type-check
- delegate properties typed as `AnyObject` instead of a concrete protocol
- oversized Objective-C bridging headers or generated Swift-to-Objective-C surfaces
- header imports that skip framework qualification and miss module-cache reuse
## Reporting Format
For each recommendation, include:
- observed evidence
- likely affected file or module
- estimated impact
- confidence
- whether approval is required before applying it
If the evidence points to project configuration instead of source, hand off to [`xcode-project-optimizer`](../xcode-project-optimizer/SKILL.md) by reading its SKILL.md and applying its workflow to the same project context.
## Preferred Tactics
- Suggest ad hoc flag injection through the build command before recommending persistent build-setting changes.
- Prefer narrowing giant view builders, closures, or result-builder expressions into smaller typed units.
- Recommend explicit imports and protocol typing when they reduce compiler search space.
- Call out when mixed-language boundaries are the real issue rather than Swift syntax alone.
## Additional Resources
- For the detailed audit checklist, see [references/code-compilation-checks.md](references/code-compilation-checks.md)
- For the shared recommendation structure, see [../../references/recommendation-format.md](../../references/recommendation-format.md)
- For source citations, see [../../references/build-optimization-sources.md](../../references/build-optimization-sources.md)
@@ -0,0 +1,63 @@
# Code Compilation Checks
Use this reference when a build benchmark shows compilation dominating build time.
## Primary Evidence Sources
- `xcodebuild -showBuildTimingSummary`
- build log compile tasks
- `-warn-long-function-bodies`
- `-warn-long-expression-type-checking`
- `-debug-time-compilation` (per-file compile time ranking)
- `-debug-time-function-bodies` (unfiltered per-function timing)
- `-driver-time-compilation` (driver overhead)
- `-stats-output-dir` (detailed compiler statistics as JSON)
## Triage Questions
1. Is one file or expression dominating compile time?
2. Is the issue mostly Swift type-checking, mixed-language bridging, or header import churn?
3. Are multiple files in the same module paying the same module-setup cost repeatedly?
## Checklist
### Explicit typing
- Add explicit property or local variable types when initialization expressions are complex.
- Prefer intermediate typed variables over one giant inferred expression.
### Expression simplification
- Break long chains into smaller expressions.
- Split complex result-builder code into smaller helpers or subviews.
- Replace nested ternaries or overloaded generic chains with simpler steps.
### Delegate typing
- Avoid `AnyObject?` or overly generic delegate surfaces.
- Prefer a named delegate protocol so the compiler has a narrower lookup space.
### Objective-C and Swift bridging
- Keep the Objective-C bridging header narrow.
- Move internal-only Objective-C declarations out of the bridging surface.
- Mark Swift members `private` when they do not need Objective-C visibility.
### Framework-qualified imports
- Prefer `#import <Framework/Header.h>` or module imports when a module map exists.
- Watch for textual includes that defeat module-cache reuse.
## Recommendation Heuristics
- High impact: repeated type-check warnings in a hot module, giant bridging headers, or a few files dominating compile time.
- Medium impact: several moderate hotspots in result builders or overloaded generic code.
- Low impact: isolated warnings without measurable benchmark impact.
## Escalation Guidance
Hand findings to `xcode-project-optimizer` when:
- build scripts dominate instead of compilation
- module reuse is blocked by project settings
- target structure or explicit-module settings appear to be the real bottleneck
+67
View File
@@ -0,0 +1,67 @@
---
name: xcode-project-optimizer
description: Audit Xcode project configuration, build settings, scheme behavior, and script phases to find build-time improvements with explicit approval gates. Use when a developer wants project-level build optimization, slow incremental builds, guidance on target dependencies, build settings review, run script phase optimization, parallelization improvements, or module-map and DEFINES_MODULE configuration.
---
# Xcode Project Optimizer
Use this skill for project- and target-level build inefficiencies that are unlikely to be solved by source edits alone.
## Core Rules
- Recommendation-first by default.
- Require explicit approval before changing project files, schemes, or build settings.
- Prefer measured findings tied to timing summaries, build logs, or project configuration evidence.
- Distinguish debug-only pain from release-only pain.
## What To Review
- scheme build order and target dependencies
- debug vs release build settings against the [build settings best practices](../../references/build-settings-best-practices.md)
- run script phases and dependency-analysis settings
- derived-data churn or obviously invalidating custom steps
- opportunities for parallelization
- explicit module dependency settings and module-map readiness
## Build Settings Best Practices Audit
Every project audit should include a build settings checklist comparing the project's Debug and Release configurations against the recommended values in [build-settings-best-practices.md](../../references/build-settings-best-practices.md). Present results using checkmark/cross indicators (`[x]`/`[ ]`). The scope is strictly build performance -- do not flag language-migration settings like `SWIFT_STRICT_CONCURRENCY` or `SWIFT_UPCOMING_FEATURE_*`.
## Apple-Derived Checks
Review these items in every audit:
- target dependencies are accurate and not missing or inflated
- schemes build in `Dependency Order`
- run scripts declare inputs and outputs
- `.xcfilelist` files are used when scripts have many inputs or outputs
- `DEFINES_MODULE` is enabled where custom frameworks or libraries should expose module maps
- headers are self-contained enough for module-map use
- explicit module dependency settings are consistent for targets that should share modules
## Typical Wins
- skip debug-time scripts that only matter in release
- add missing script guards or dependency-analysis metadata
- remove accidental serial bottlenecks in schemes
- align build settings that cause unnecessary module variants
- fix stale project structure that forces broader rebuilds than necessary
## Reporting Format
For each issue, include:
- evidence
- likely scope
- why it affects clean builds, incremental builds, or both
- estimated impact
- approval requirement
If the evidence points to package graph or build plugins, hand off to [`spm-build-analysis`](../spm-build-analysis/SKILL.md) by reading its SKILL.md and applying its workflow to the same project context.
## Additional Resources
- For the detailed audit checklist, see [references/project-audit-checks.md](references/project-audit-checks.md)
- For build settings best practices, see [../../references/build-settings-best-practices.md](../../references/build-settings-best-practices.md)
- For the shared recommendation structure, see [../../references/recommendation-format.md](../../references/recommendation-format.md)
- For Apple-aligned source summaries, see [../../references/build-optimization-sources.md](../../references/build-optimization-sources.md)
@@ -0,0 +1,53 @@
# Project Audit Checks
Use this reference when reviewing build-system configuration rather than source-level compile behavior.
## Target And Scheme Checks
- Confirm target dependencies are explicit and accurate.
- Remove dependencies that no longer reflect real build requirements.
- Ensure the scheme builds targets in `Dependency Order`.
- Look for oversized or monolithic targets that block parallel work.
## Build Script Checks
- Does each script need to run during incremental builds?
- Are input and output files declared?
- Should inputs and outputs be moved into `.xcfilelist` files?
- Can the script skip debug builds, simulator builds, or unchanged inputs?
- Would the script become parallelizable if dependency analysis were declared correctly?
## Build Setting Checks
Audit project-level and target-level settings against the [build settings best practices](../../../references/build-settings-best-practices.md). Present results as a checklist with `[x]`/`[ ]` indicators.
Key settings to verify:
- `SWIFT_COMPILATION_MODE` -- `singlefile` for Debug, `wholemodule` for Release
- `SWIFT_OPTIMIZATION_LEVEL` -- `-Onone` for Debug, `-O` or `-Osize` for Release
- `ONLY_ACTIVE_ARCH` -- `YES` for Debug, `NO` for Release
- `DEBUG_INFORMATION_FORMAT` -- `dwarf` for Debug, `dwarf-with-dsym` for Release
- `GCC_OPTIMIZATION_LEVEL` -- `0` for Debug, `s` for Release
- `ENABLE_TESTABILITY` -- `YES` for Debug, `NO` for Release
- `COMPILATION_CACHING` -- recommended `YES` for all configurations; caches repeated compilations during branch switching and clean builds
Do not flag language-migration settings (`SWIFT_STRICT_CONCURRENCY`, `SWIFT_UPCOMING_FEATURE_*`) as build performance issues.
## Module And Header Checks
- `DEFINES_MODULE` is enabled for custom frameworks that should benefit from module maps.
- Public headers are self-contained enough to compile as a module.
- Import statements use framework-qualified imports where available.
- targets that should share built modules use consistent options
## Explicit Module Dependency Checks
- Check whether explicit modules are enabled or expected in the current Xcode version and Swift mode.
- Look for repeated module builds caused by configuration drift.
- Compare preprocessor macros or other build options across sibling targets that import the same modules.
## Recommendation Prioritization
- High: serial script bottlenecks, missing dependency metadata, or configuration drift causing redundant module builds.
- Medium: stale target structure or noncritical scripts running too often.
- Low: settings cleanup without strong evidence of current impact.
+67
View File
@@ -0,0 +1,67 @@
---
name: xcode-code-compilation-optimizer
description: Analyze Swift and mixed-language compile hotspots using build timing summaries and Swift frontend diagnostics, then produce a recommend-first source-level optimization plan. Use when a developer reports slow compilation, type-checking warnings, or expensive clean-build compile phases.
---
# Xcode Code Compilation Optimizer
Use this skill when compile time, not just general project configuration, looks like the bottleneck.
## Core Rules
- Start from evidence, ideally a recent `.build-benchmark/` artifact or raw timing-summary output.
- Prefer analysis-only compiler flags over persistent project edits during investigation.
- Rank findings by expected compile-time impact, not by how easy they are to describe.
- Do not edit source or build settings without explicit developer approval.
## What To Inspect
- `Build Timing Summary` output from a clean build
- long-running `CompileSwiftSources` or per-file compilation tasks
- ad hoc runs with:
- `-Xfrontend -warn-long-expression-type-checking=<ms>`
- `-Xfrontend -warn-long-function-bodies=<ms>`
- mixed Swift and Objective-C surfaces that increase bridging work
## Analysis Workflow
1. Identify whether the main issue is broad compilation volume or a few extreme hotspots.
2. Parse timing-summary categories and rank the biggest compile contributors.
3. Run or inspect long type-check diagnostics when expression complexity is suspected.
4. Map the evidence to a concrete recommendation list.
5. Separate code-level suggestions from project-level or module-level suggestions.
## Apple-Derived Checks
Look for these patterns first:
- missing explicit type information in expensive expressions
- complex chained or nested expressions that are hard to type-check
- delegate properties typed as `AnyObject` instead of a concrete protocol
- oversized Objective-C bridging headers or generated Swift-to-Objective-C surfaces
- header imports that skip framework qualification and miss module-cache reuse
## Reporting Format
For each recommendation, include:
- observed evidence
- likely affected file or module
- estimated impact
- confidence
- whether approval is required before applying it
If the evidence points to project configuration instead of source, hand off to `xcode-project-optimizer`.
## Preferred Tactics
- Suggest ad hoc flag injection through the build command before recommending persistent build-setting changes.
- Prefer narrowing giant view builders, closures, or result-builder expressions into smaller typed units.
- Recommend explicit imports and protocol typing when they reduce compiler search space.
- Call out when mixed-language boundaries are the real issue rather than Swift syntax alone.
## Additional Resources
- For the detailed audit checklist, see [references/code-compilation-checks.md](references/code-compilation-checks.md)
- For the shared recommendation structure, see [../references/recommendation-format.md](../references/recommendation-format.md)
- For source citations, see [../references/build-optimization-sources.md](../references/build-optimization-sources.md)
@@ -0,0 +1,59 @@
# Code Compilation Checks
Use this reference when a build benchmark shows compilation dominating build time.
## Primary Evidence Sources
- `xcodebuild -showBuildTimingSummary`
- build log compile tasks
- `-warn-long-function-bodies`
- `-warn-long-expression-type-checking`
## Triage Questions
1. Is one file or expression dominating compile time?
2. Is the issue mostly Swift type-checking, mixed-language bridging, or header import churn?
3. Are multiple files in the same module paying the same module-setup cost repeatedly?
## Checklist
### Explicit typing
- Add explicit property or local variable types when initialization expressions are complex.
- Prefer intermediate typed variables over one giant inferred expression.
### Expression simplification
- Break long chains into smaller expressions.
- Split complex result-builder code into smaller helpers or subviews.
- Replace nested ternaries or overloaded generic chains with simpler steps.
### Delegate typing
- Avoid `AnyObject?` or overly generic delegate surfaces.
- Prefer a named delegate protocol so the compiler has a narrower lookup space.
### Objective-C and Swift bridging
- Keep the Objective-C bridging header narrow.
- Move internal-only Objective-C declarations out of the bridging surface.
- Mark Swift members `private` when they do not need Objective-C visibility.
### Framework-qualified imports
- Prefer `#import <Framework/Header.h>` or module imports when a module map exists.
- Watch for textual includes that defeat module-cache reuse.
## Recommendation Heuristics
- High impact: repeated type-check warnings in a hot module, giant bridging headers, or a few files dominating compile time.
- Medium impact: several moderate hotspots in result builders or overloaded generic code.
- Low impact: isolated warnings without measurable benchmark impact.
## Escalation Guidance
Hand findings to `xcode-project-optimizer` when:
- build scripts dominate instead of compilation
- module reuse is blocked by project settings
- target structure or explicit-module settings appear to be the real bottleneck
@@ -0,0 +1,44 @@
# Project Audit Checks
Use this reference when reviewing build-system configuration rather than source-level compile behavior.
## Target And Scheme Checks
- Confirm target dependencies are explicit and accurate.
- Remove dependencies that no longer reflect real build requirements.
- Ensure the scheme builds targets in `Dependency Order`.
- Look for oversized or monolithic targets that block parallel work.
## Build Script Checks
- Does each script need to run during incremental builds?
- Are input and output files declared?
- Should inputs and outputs be moved into `.xcfilelist` files?
- Can the script skip debug builds, simulator builds, or unchanged inputs?
- Would the script become parallelizable if dependency analysis were declared correctly?
## Build Setting Checks
- Debug compilation mode should usually favor incremental behavior.
- Release compilation mode should usually favor whole-module optimization behavior.
- `Build Active Architecture Only` should match debug vs release intent.
- `Debug Information Format` should avoid heavier-than-needed debug defaults.
## Module And Header Checks
- `DEFINES_MODULE` is enabled for custom frameworks that should benefit from module maps.
- Public headers are self-contained enough to compile as a module.
- Import statements use framework-qualified imports where available.
- targets that should share built modules use consistent options
## Explicit Module Dependency Checks
- Check whether explicit modules are enabled or expected in the current Xcode version and Swift mode.
- Look for repeated module builds caused by configuration drift.
- Compare preprocessor macros or other build options across sibling targets that import the same modules.
## Recommendation Prioritization
- High: serial script bottlenecks, missing dependency metadata, or configuration drift causing redundant module builds.
- Medium: stale target structure or noncritical scripts running too often.
- Low: settings cleanup without strong evidence of current impact.