mirror of
https://github.com/AvdLee/Xcode-Build-Optimization-Agent-Skill.git
synced 2026-09-14 13:59:59 +08:00
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:
@@ -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": "./"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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`
|
||||
@@ -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();
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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
|
||||
...
|
||||
```
|
||||
@@ -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`
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+224
@@ -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())
|
||||
Executable
+273
@@ -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())
|
||||
@@ -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())
|
||||
Executable
+61
@@ -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())
|
||||
Executable
+99
@@ -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())
|
||||
@@ -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.
|
||||
@@ -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?
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user