115 lines
11 KiB
Markdown
Executable File
115 lines
11 KiB
Markdown
Executable File
# Tech Design: VS Code and Cursor support through an Xcode build server
|
||
|
||
**Author:** Alex M
|
||
**Reviewer:** TBD
|
||
**Stakeholders:** macOS and iOS teams
|
||
**Project:** [Migration to fully VSCode-supported builds&debugging](https://app.asana.com/1/137249556945/project/1203301625297703/task/1209808486380252) / https://app.asana.com/1/137249556945/project/1203301625297703/task/1219079700626622?focus=true
|
||
|
||
## Local implementation status — 2026-10-16
|
||
|
||
The personal [mallexxx/sourcekit-xcode-bsp fork](https://github.com/mallexxx/sourcekit-xcode-bsp) carries the fixes and committed dependency pins at `afd0e3dd026b3ee3619e2271809debee25fec96f`; 58 tests and strict SwiftLint pass. A shared local Git `post-checkout` hook is active, and the setup has been applied and structurally verified in all 97 registered worktrees, including two adjacent checkouts.
|
||
|
||
The bootstrap/templates live at `/Users/admin/DuckDuckGo/apple-browsers.git/.vscode-local/`; generated setup files use shared local exclusions. The old configurations in `exp` and `sweetpad` were backed up and migrated. `exp/buildServer.json` was already tracked, so that one migration is an intentional uncommitted local modification. No app-repository commits, upstream PRs or issues were created. The DuckDuckGo-owned fork and team/repository rollout described below remain proposed.
|
||
|
||
Deployment paths, maintenance commands, verification evidence, publishing limitations and cache research: [[projects/vscode-migration/xcode-build-server]].
|
||
|
||
## Background & Requirements
|
||
|
||
- Some of us (would prefer to) spend much of the day outside Xcode: in VS Code, reviewing changes, working with AI agents, or across several worktrees at once. There, the Swift language server has no build information for our Xcode projects. So there's no direct build&run support, no jump to definition, no completion and no errors as you type, and no debugger.
|
||
- The [previous tech design](https://app.asana.com/1/137249556945/project/1202406491309510/task/1219304032604521) tried to solve this by making the macOS browser code a Swift package. The team has raised some concerns against doing it at this stage. The concerns were:
|
||
- a more custom, more fragile setup;
|
||
- resources that can fail silently at runtime;
|
||
- no good way to have per-configuration dependencies;
|
||
- macOS and iOS drifting apart;
|
||
- an unclear number of users and an unclear owner.
|
||
- The Swift language server (sourcekit-lsp) can get build information from a *build server* instead of a Swift package. [sourcekit-xcode-bsp](https://github.com/slime-studio/sourcekit-xcode-bsp) is an open-source build server that reads `.xcodeproj` / `.xcworkspace` with swift-build, the build engine Apple open-sourced from Xcode.
|
||
- **Requirements:**
|
||
- No change to the Xcode projects, schemes, CI or release, and **nothing changes** for people who only use Xcode.
|
||
- Opt-in and easy to remove.
|
||
- **macOS and iOS**, including tests and packages.
|
||
- People who use both Xcode and VS Code don't build or index everything twice.
|
||
|
||
## Problem Statement
|
||
|
||
Code autocompletion, jump-to-definition and debugging in VS Code and Cursor need build information that today only Xcode has. We want to provide it from the Xcode projects as they are, without restructuring the codebase around it.
|
||
|
||
## Recommended Approach
|
||
|
||
- Add an opt-in VS Code setup to the repository: a workspace file and a few scripts in `scripts/vscode/`. This design doesn't change anything Xcode uses: the projects, schemes, xcconfigs and CI stay as they are.
|
||
- Buildable folders, mentioned below, is separate task.
|
||
|
||
**What a developer gets**
|
||
- Completion, jump-to-definition, references and live errors for app, test and package code, on macOS or iOS. One platform is chosen per worktree and can be switched.
|
||
- Tasks to build any scheme and run unit tests; errors and failed tests show in the Problems panel. launch.json/tasks.json aren‘t committed.
|
||
- Debugging with breakpoints:
|
||
- the macOS app, launched from the editor;
|
||
- the iOS app on a simulator: one launch builds, installs and starts the app, and attaches the debugger.
|
||
- Optional background indexing of worktrees while no editor is open.
|
||
|
||
**How it works**
|
||
- The workspace generates the build server's config on folder open. The config chooses the platform and the targets to index, the same way an Xcode scheme does.
|
||
- Builds and tests go through `xcodebuild` with the same settings Xcode uses.
|
||
- Works with the projects as they are, including buildable folders, which are being adopted separately (agreed in the [previous tech design's discussion](https://app.asana.com/1/137249556945/task/1219304032604521/comment/1219461135882510?focus=true)). With them, adding a file in VS Code needs no project edits, which completes the editor workflow.
|
||
- The build server is installed locally by a script that builds it from source at a pinned commit of our fork (see below).
|
||
|
||
**The build server needed fixes.** On our workspace it gave no results in test files, broke on the DMG/App Store variants and took hours to index. Four small changes fix this (see Appendix), and a worktree now indexes in about 5 minutes. They're general improvements, so we'll open a PR for the upstream. We don't depend on upstream accepting them: we fork the build server into a DuckDuckGo repository, keep our changes there and pull upstream changes manually after reviewing them.
|
||
|
||
**Steps**
|
||
1. Fork sourcekit-xcode-bsp into a DuckDuckGo repository with our changes.
|
||
2. Land the setup in the repository, plus a short README.
|
||
3. Offer the changes upstream as PRs.
|
||
4. Document the setup in the [Apple Documentation](https://app.asana.com/1/137249556945/project/1202500774821704/list/1203980668089483) project and in the onboarding docs.
|
||
|
||
### Pros
|
||
|
||
- **No project changes.** The Xcode projects stay the only source of truth. Removing the setup means deleting `scripts/vscode/` and the workspace file.
|
||
- **Same build as Xcode.** swift-build reads the projects, so per-configuration settings and dependencies, resources, macros and plugins behave like in Xcode.
|
||
- **Nothing to maintain in the app code.** No lint rules or checks to keep the setup working.
|
||
- **Both platforms**, so it doesn't add a gap between macOS and iOS.
|
||
- **No cost for Xcode users.** Builds and the index live in the worktree's usual Xcode DerivedData folder, so those who use both tools share one build and one index: whatever Xcode built or indexed is ready in VS Code, and the other way round.
|
||
|
||
### Cons
|
||
|
||
- **A young third-party tool** (version 0.1.0, "early-stage" by its authors) that we fork and maintain ourselves.
|
||
- **Some rough edges:**
|
||
- one platform per worktree at a time;
|
||
- a new worktree has to be prepared and indexed once, ~5 minutes on an M4 Max. Xcode does the same work on a new worktree (not measured side by side), and since the index is shared, whichever tool does it first saves the other from doing it.
|
||
- **Each build or test from VS Code starts xcodebuild from scratch.** Every call loads the workspace and resolves the package graph before building: a build with nothing to do takes ~25 s, and up to ~1 min when xcodebuild checks remote packages. Xcode keeps the workspace loaded between builds and avoids this. It may need its own treatment later, for example skipping package resolution when `Package.resolved` hasn't changed, or keeping a build service running between calls.
|
||
- **Someone has to own it.** I will; the setup is small (a workspace file and ~6 scripts), and it's documented. Breaking changes can be handled by a single AI agent run that can fully fix and validate the setup.
|
||
|
||
### Risks
|
||
|
||
- **Xcode updates can break the build server**, since it bundles its own swift-build. Mitigation: a pinned version and a check after each Xcode update. The worst case is that the setup stops working for the people who opted in; Xcode and CI aren't affected.
|
||
- **Malicious code in the build server or its dependencies** (supply chain). It runs on developer machines with access to the source code and the network. Mitigations:
|
||
- we build it from source at a pinned commit of our fork; nothing is downloaded prebuilt or updated automatically;
|
||
- its own code is small (~1,700 lines in 13 Swift files upstream, plus ~550 lines of our changes), so each upstream update can be reviewed as a diff before it goes into the fork;
|
||
- its dependencies come from Apple/swiftlang (swift-build, swift-tools-protocols, swift-argument-parser) and are pinned too.
|
||
- **Upstream doesn't accept the changes** or the project is abandoned. Mitigation: the fork already carries our changes, so nothing breaks; we keep it, or drop the setup.
|
||
- **Xcode and the editor share the index.** Both write to Xcode's index store; deleting the DerivedData folder resets it, as today.
|
||
- **The setup breaks Xcode builds or the Xcode setup.** Mitigation: it changes nothing Xcode uses. The projects, schemes, xcconfigs, `Package.resolved` files and CI stay as they are. The setup only adds a workspace file and scripts, and VS Code builds run `xcodebuild` on the same projects with Xcode's settings.
|
||
- **A file added from VS Code works there but is missing in Xcode.** New files have to be added to the Xcode project, which is easy to forget outside Xcode. Mitigations:
|
||
- VS Code builds use the same projects and `xcodebuild` configuration as Xcode, so a file missing from a target fails the VS Code build too; the editor also shows no code intelligence for it;
|
||
- adopting buildable folders, removes the need to edit `project.pbxproj` for new files.
|
||
|
||
## Testing
|
||
|
||
- VSCode compatibility: macOS and iOS app files, app tests, jumping into app and packages code, package tests.
|
||
- CI run validates nothing is broken in the existing setup.
|
||
- The build server changes have unit tests.
|
||
- Before documenting it for the team: two people try it on fresh machines in VS Code, and we check that Xcode picks up a worktree built and indexed from VS Code without rebuilding.
|
||
|
||
## Additional Considerations
|
||
|
||
- **Privacy / Security:** No change to the apps and no user data involved; the setup only runs locally on developer machines. It does add third-party code to those machines, built from our fork at a pinned commit (see Risks). **Question for reviewers:** does a developer tool like this need a privacy/security triage? It's a small open-source package with Apple-only dependencies, so a light review of the fork may be enough.
|
||
- **Site Breakage / Experimentation / Localization:** Not applicable.
|
||
- **Operational:** No CI or release change.
|
||
|
||
## Appendix: changes to the build server
|
||
|
||
1. **Debug configuration by default.** Without one, swift-build used the projects' default configuration, Release, which can't import app modules into tests, so test files had no results.
|
||
2. **Choosing targets.** It configured all ~980 workspace targets in one build, including DMG and App Store variants of the same products, which write to the same paths. It now takes include/exclude patterns, so we configure what a scheme would.
|
||
3. **Caching compiler arguments per target.** It computed them separately for every file, ~1 s each, one at a time, which made indexing take hours. With the cache, a fresh worktree indexes in ~5 minutes on an M4 Max.
|
||
4. **Xcode's DerivedData layout**, so the index and builds are shared with Xcode.
|
||
|
||
Research notes with measurements: [[projects/vscode-migration/xcode-build-server]].
|