From c35ecf3ed77f9a63f3404e4235102b41d3b9182b Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 14 Oct 2026 21:20:49 +0600 Subject: [PATCH] [2026-10-14] eagle: work/projects/vscode-migration/tech-design-xcode-build-server.md work/projects/vscode-migration/xcode-build-server.md --- .../tech-design-xcode-build-server.md | 92 ++++++++++++------- .../vscode-migration/xcode-build-server.md | 77 +++++++++------- 2 files changed, 100 insertions(+), 69 deletions(-) diff --git a/work/projects/vscode-migration/tech-design-xcode-build-server.md b/work/projects/vscode-migration/tech-design-xcode-build-server.md index 6dc5a4e3..be2eea1d 100644 --- a/work/projects/vscode-migration/tech-design-xcode-build-server.md +++ b/work/projects/vscode-migration/tech-design-xcode-build-server.md @@ -13,9 +13,10 @@ - [sourcekit-xcode-bsp](https://github.com/slime-studio/sourcekit-xcode-bsp) (Apache 2.0, early-stage, version 0.1.0) is a build server for `.xcodeproj`/`.xcworkspace`. It uses swift-build, the open-source build engine of Xcode, so it reads the projects the way Xcode does. - **Requirements:** - Code intelligence (completion, diagnostics, jump to definition, references) for app, test and package code, macOS and iOS. - - Build, run, test and debug from the editor. + - Build, run, test and debug from the editor, including the iOS simulator. - No change to the Xcode projects, schemes, CI or release. - - Works in many worktrees at once without hours of indexing per worktree. + - Developers who also use Xcode don't pay twice: builds and the index are shared with Xcode. + - Works in many worktrees without hours of indexing per worktree. ## Problem Statement @@ -23,61 +24,77 @@ Out of the box, sourcekit-xcode-bsp 0.1.0 on our workspace: - gives app code intelligence after ~3 minutes; - gives **nothing in test files**; - produces build failures and drops ~250 object files into the repository root; -- needs an estimated **2 hours** to index a worktree. +- needs an estimated **2 hours** to index a worktree; +- keeps its own build and index, separate from Xcode's. ## Recommended Approach -Use sourcekit-xcode-bsp with three changes to it, plus a small setup in the repository. +Use sourcekit-xcode-bsp with four changes to it, plus a small setup in the repository. **Changes to sourcekit-xcode-bsp** (`scripts/vscode/sourcekit-xcode-bsp.patch`, to be upstreamed) 1. **`configuration`, default `Debug`.** Without it swift-build uses the projects' default configuration, Release, which has no testability. Every `@testable import` failed, so test files had no results. -2. **`targets` include/exclude patterns** on `/`, plus a `list-targets` command to check them. The server put all 977 workspace targets into one build: DMG and App Store variants, both platforms, and 3–4 variants of each package library. They build into the same paths, so swift-build dropped tasks (failed links, stray `.o` files). Xcode never builds them together; a scheme picks one variant and package targets come in as dependencies. We configure the same way: 86 targets, which pull in 288. -3. **A per-target cache of Swift compiler arguments.** swift-build computes them for every file from scratch (~0.8 s each, one at a time). Within a Swift target they differ only in the file's `-index-unit-output-path`, which the server already reports. The first request of a target is computed and the rest are answered from the cache. Indexing a fresh worktree went from ~2 hours to **~5.5 minutes** on an M4 Max. +2. **`targets` include/exclude patterns** on `/`, plus a `list-targets` command to check them. The server put all 977 workspace targets into one build: DMG and App Store variants, both platforms, and 3–4 variants of each package library. They build into the same paths, so swift-build dropped tasks. Xcode never builds them together; a scheme picks one variant and package targets come in as dependencies. We configure the same way: 86 targets, which pull in 288. +3. **A per-target cache of Swift compiler arguments.** swift-build computes them for every file from scratch (~0.8 s each, one at a time). Within a Swift target they differ only in the file's `-index-unit-output-path`, which the server already reports. The first request of a target is computed, and concurrent requests wait for it. Indexing a fresh worktree went from ~2 hours to **~5.5 minutes** on an M4 Max. +4. **`derivedDataLayout: xcode`.** The server works in the worktree's Xcode DerivedData folder with Xcode's layout: + - the index store is Xcode's; + - index records name the regular build's object files, so a file indexed or built by Xcode or `xcodebuild` is up to date for the editor, and the other way round; + - preparation builds go into a folder of their own, so the server never shares a build database with an open Xcode. Also added: `buildSettings` overrides. **In the repository** (`DuckDuckGo.code-workspace`, `scripts/vscode/`) -- `setup-build-server.py [macos|ios]` writes `buildServer.json` on folder open: Debug, one platform, and the target filter. The filter keeps the app project and package test products, and drops App Store variants and the other platform's app and local packages. -- Tasks call `xcodebuild.sh`: build a scheme (macOS or iOS Simulator) and run unit tests with `-only-testing`. Output goes through xcbeautify, so errors show in the Problems panel. -- Launch configurations debug the app with lldb-dap: launch, launch without building, and attach. -- **Compile results shared between worktrees.** `xcodebuild.sh` uses Xcode's compilation cache with the worktree and build folder mapped to `/^wt` and `/^dd`. A clean build in a new worktree drops from 179 s to 90–120 s, with no cache misses. Launch configurations map `/^wt` back with `sourceMap`, so breakpoints and sources work. -- **Background indexing.** `index-in-background.py` indexes worktrees headless while no editor uses them, and an editor that opens stops it. `background-indexing.sh install` runs it every 30 minutes while Xcode, VS Code and Cursor are closed (opt-in). -- **Install.** `install-build-server.sh` builds the patched server from a pinned upstream commit into `~/.local/opt`. +- **Language server config.** `setup-build-server.py [macos|ios]` writes `buildServer.json` on folder open. It sets Debug, one platform (kept until switched) and the target filter. The filter keeps the app project and package test products, and drops App Store variants and the other platform's app and local packages. +- **Builds.** `xcodebuild.sh` builds into Xcode's DerivedData folder with Xcode's settings, so Xcode and VS Code don't rebuild after each other. It also feeds the shared index. Tasks: + - build a macOS scheme; + - run unit tests with `-only-testing`; failures show in the Problems panel; + - build and run the iOS app on a simulator. +- **Debugging** uses lldb-dap. + - macOS: launch, with or without building first. + - iOS simulator: `simulator.sh` builds for the chosen simulator, installs the app and launches it waiting for the debugger. The launch configuration then attaches by pid; the process picker lists newest first. +- **Opt-in cache across worktrees.** `DDG_WORKTREE_CACHE=1` builds in the worktree instead. The worktree and build folder are mapped to `/^wt` and `/^dd`, so Xcode's compilation cache is shared between worktrees: a clean build in a new worktree takes 90–120 s instead of 179 s. Launch configurations map the paths back for the debugger. These builds aren't shared with Xcode. +- **Background indexing.** `index-in-background.py` indexes worktrees headless while no editor uses them; an editor that opens stops it. `background-indexing.sh install` runs it every 30 minutes while Xcode, VS Code and Cursor are closed (opt-in). +- **Install.** `install-build-server.sh` builds the patched server from a pinned upstream commit, with pinned dependencies, into `~/.local/opt`. The setup task warns when the patch changes. **Steps** -1. Land the setup and the patch in the repository, behind no flags; Xcode users aren't affected. -2. Open upstream PRs for the three changes; drop the patch when a release has them. -3. iOS simulator debugging (install, launch waiting for the debugger, attach), as a follow-up. +1. Land the setup and the patch in the repository. Xcode users aren't affected. +2. Early adopters on macOS and iOS; then a short how-to in the team docs. +3. Open upstream PRs for the server changes. Drop the patch when a release has them, or move it to a fork under the DuckDuckGo organization. ### Pros - **No project changes.** The Xcode projects, schemes and CI stay the source of truth; nothing to keep in sync. - **Same build settings as Xcode.** swift-build plans the build, so flags, macros, plugins, resources and per-configuration settings match Xcode. Hand-written SwiftPM manifests can drift from them. +- **Works together with Xcode.** One build and one index per worktree, whichever tool made them. - **Both platforms and every target type:** apps, extensions, app tests, package tests. -- **Fast enough for many worktrees:** ~5.5 min first index, background indexing, shared compile cache for builds. +- **Fast enough for many worktrees.** + - ~5.5 min first index, less after a build. + - Background indexing. + - Optional shared compile cache across worktrees. - Complements the SwiftPM package work rather than replacing it: modularization still helps build times and testing. ### Cons - **A young third-party tool** (0.1.0, "early-stage" by its authors) and our patch on top until upstream merges it. -- **One platform per worktree at a time;** switching runs the setup again and reindexes. +- **One platform per worktree at a time;** switching runs the setup again and reindexes the other platform's files. - **Files outside the configured targets have no results,** for example App Store-only files (none today) or packages only the other platform uses. -- **First index of a worktree is ~5.5 minutes,** and preparation is serialized inside swift-build. -- **xcodebuild builds take as long as in Xcode;** VS Code adds nothing there except the shared cache for new worktrees. +- **iOS debugging takes two steps:** run the task, then attach. VS Code resolves the process picker before a pre-launch task, so the two can't be one action. Attaching with a launch-time command doesn't stop at breakpoints with lldb-dap today. +- **The cross-worktree cache and sharing with Xcode exclude each other.** Prefix-mapped paths in debug info need a source map, which we can only set up in VS Code. +- **xcodebuild builds take as long as Xcode's.** Don't build one worktree from both at the same time. ### Risks -- **swift-build changes with Xcode.** The server bundles its own swift-build build, pinned by upstream, while the compiler comes from the selected Xcode. Mitigation: pin a tested commit in `install-build-server.sh`; check after each Xcode update. -- **Upstream doesn't take the patches** or the project stalls. Mitigation: keep a fork under the DuckDuckGo organization; the patch is ~350 lines plus tests. -- **Prefix-mapped debug info** (`/^wt`): a debugger without the source map shows no sources. Mitigation: only `xcodebuild.sh` builds use it, and the launch configurations set `sourceMap`. Xcode builds are unchanged. -- **Background indexing costs CPU and disk** (~5 GB per worktree in `.build/`). Mitigation: it's opt-in, runs only while editors are closed, at low priority, and skips worktrees that haven't changed. +- **swift-build changes with Xcode.** The server bundles its own swift-build, pinned by upstream, while the compiler comes from the selected Xcode. Mitigation: pinned commit and dependencies in `install-build-server.sh`; check after each Xcode update. +- **Upstream doesn't take the patches** or the project stalls. Mitigation: a fork under the DuckDuckGo organization. The patch is ~550 lines of code plus ~450 lines of tests. +- **Shared index store with Xcode.** Xcode and sourcekit-lsp both write records into it; the index store format is designed for several writers, but we haven't seen it under load. Mitigation: the server never shares Xcode's build database, and deleting the DerivedData folder resets both. +- **Background indexing costs CPU and disk.** Mitigation: it's opt-in, runs only while editors are closed, at low priority, and skips worktrees that haven't changed. ## Notes - [1] Measured on an M4 Max with Xcode 27.1 on `main` at 3d850d74dc. - - Full index of a fresh worktree: 323–347 s. That is ~1 min to load the workspace and resolve packages, then ~3.5 min to prepare and index, running up to 11 compilers in parallel. - - Before the arguments cache: 6,000 of ~8,000 index units after 48 min. -- [2] The compilation cache doesn't speed up the language server, because swift-build doesn't use it for index preparation. A cold and a warm cache gave the same time. So the build server config doesn't set it. + - Fresh-worktree index: 323–347 s at low load. That is ~1 min to load the workspace and resolve packages, then ~3.5 min to prepare and index, running up to 11 compilers in parallel. + - Before the arguments cache: 6,000 of ~8,000 index records after 48 min. + - After a `macOS Browser` build into the shared folder, indexing skipped every app file: 2,150 files left instead of 3,972. +- [2] The compilation cache doesn't speed up the language server, because swift-build doesn't use it for index preparation. A cold and a warm cache gave the same time. - [3] Sharing the index between worktrees was rejected. Index records hold absolute paths, and sourcekit-lsp reindexes any file newer than its record, which is every file in a fresh checkout. - [4] Alternatives considered: - **xcode-build-server** (parses Xcode build logs): needs a full Xcode build first and goes stale until the next build. @@ -86,22 +103,29 @@ Also added: `buildSettings` overrides. ## Testing -- Code intelligence checked with a headless LSP client (hover, definition, completion, a diagnostic for an injected type error) on: +- Code intelligence checked with a headless LSP client (hover, definition, completion, a diagnostic for an injected type error), in both layouts, on: - a macOS app file; - a macOS unit test, including a jump into app code; - a package test; - an iOS app file in iOS mode. -- `xcodebuild.sh build "macOS Browser"` builds. lldb stops at a breakpoint set by full path in the cache-mapped build, with sources shown. +- Tasks: + - "Test macOS Unit Tests": 33 tests pass. + - Builds don't rebuild after Xcode-style `xcodebuild` builds: both are no-ops in ~30 s. +- iOS simulator: build, install, launch, then lldb-dap attaches by pid and stops at a breakpoint in `AppDelegate`. +- macOS: lldb stops at a breakpoint set by full path, with and without the cross-worktree path mapping. - Background indexer: - full run; - - an editor taking over mid-run, with no orphaned processes; - - selecting only configured worktrees. -- sourcekit-xcode-bsp: 50 unit tests pass, including new tests for the target filter and the arguments cache (single flight, invalidation, stale results). -- To do before rollout: a pass through the VS Code and Cursor UI (tasks, Problems panel, lldb-dap launch and attach) by two people on fresh machines. + - skipping an unchanged worktree; + - an editor taking over mid-run, with no orphaned processes. +- sourcekit-xcode-bsp: 58 unit tests. They cover the filter, the cache (single flight, failures, purposes, invalidation, stale results), the index-path override and the layouts. +- Independent code review; its findings are fixed (see notes). +- To do before rollout: + - a pass through the VS Code and Cursor UI by two people on fresh machines; + - checking that the Xcode IDE picks up a worktree built and indexed from VS Code without rebuilding. ## Additional Considerations -- **Privacy / Security:** No change to the apps. The install script downloads and builds sourcekit-xcode-bsp from its public repository at a pinned commit. Everything runs locally. +- **Privacy / Security:** No change to the apps. The install script downloads and builds sourcekit-xcode-bsp from its public repository at a pinned commit with pinned dependencies. Everything runs locally. - **Site Breakage / Experimentation:** Not applicable. -- **Operational:** No CI or release change. Opting in means installing the server and opening the workspace file. Xcode users aren't affected. +- **Operational:** No CI or release change. Opting in means running the installer and opening the workspace file. Xcode users aren't affected. - **Localization:** Not applicable. diff --git a/work/projects/vscode-migration/xcode-build-server.md b/work/projects/vscode-migration/xcode-build-server.md index 68056c53..66cd0478 100644 --- a/work/projects/vscode-migration/xcode-build-server.md +++ b/work/projects/vscode-migration/xcode-build-server.md @@ -1,60 +1,67 @@ # VS Code via Xcode build server (sourcekit-xcode-bsp) -Status: **prototype done, uncommitted** · 2026-10-14 +Status: **ready for early adopters, uncommitted** · 2026-10-14 **Parent:** [[projects/vscode-migration/vscode-migration]] **Tech design:** [[projects/vscode-migration/tech-design-xcode-build-server]] An alternative to the [[projects/vscode-migration/tech-design-spm-browser-package|SwiftPM browser package]]: keep the Xcode projects as they are and give VS Code/Cursor what it needs from them. -- Code intelligence: sourcekit-lsp ← `buildServer.json` ← [sourcekit-xcode-bsp](https://github.com/slime-studio/sourcekit-xcode-bsp) (BSP server on top of swift-build, Xcode's build engine; early-stage, Apache 2.0). -- Build/test/debug: `xcodebuild` tasks + lldb-dap launch configs. +- Code intelligence: sourcekit-lsp ← `buildServer.json` ← [sourcekit-xcode-bsp](https://github.com/slime-studio/sourcekit-xcode-bsp) (BSP server on top of swift-build, Xcode's build engine; early-stage 0.1.0, Apache 2.0), patched by us. +- Build/test/debug: `xcodebuild` tasks + lldb-dap launch configs, macOS and iOS simulator. +- Builds and index shared with Xcode through the worktree's DerivedData folder. ## Where things are | What | Where | |---|---| | VS Code setup | worktree `vscode-xcode-bsp`, branch `alex/vscode-xcode-bsp` — `DuckDuckGo.code-workspace`, `scripts/vscode/` (uncommitted) | -| BSP fork | `~/Developer/sourcekit-xcode-bsp`, branch `configuration-name` (uncommitted), base upstream `726ce70`; same changes as `scripts/vscode/sourcekit-xcode-bsp.patch` | -| Installed BSP | `~/.local/opt/sourcekit-xcode-bsp` (by `install-build-server.sh`); brew 0.1.0 also installed | +| BSP fork | `~/Developer/sourcekit-xcode-bsp`, branch `configuration-name` (uncommitted), base upstream `726ce706`; identical to `scripts/vscode/sourcekit-xcode-bsp.patch` | +| Installed BSP | `~/.local/opt/sourcekit-xcode-bsp` (by `install-build-server.sh`, with `patch.sha256` stamp); brew 0.1.0 also installed but unused | ## Files (`scripts/vscode/`) -- `setup-build-server.py [macos|ios]` — writes `buildServer.json`; runs on folder open. Target filter: app project + `*Tests-product`, no App Store variants, no other-platform app/local packages (from `iOS|macOS/LocalPackages`). -- `sourcekit-xcode-bsp.sh` — `argv` of buildServer.json; editor's server kills a running background indexer (pid files in `.build/bsp/`, checks the pid is a sourcekit process). -- `xcodebuild.sh [destination]` — Debug into `.build/xcodebuild`, shared CAS with `/^wt` `/^dd` prefix maps, xcbeautify. -- `index-in-background.py [--all] [--if-idle]` — headless sourcekit-lsp indexing; `background-indexing.sh install` = launchd agent every 30 min. -- `install-build-server.sh` + `sourcekit-xcode-bsp.patch` — build the patched BSP. +- `setup-build-server.py [macos|ios]` — writes `buildServer.json` on folder open. Keeps the current platform without an argument. Build root = the worktree's Xcode DerivedData, `derivedDataLayout: xcode`, `configuration: Debug`, target filter (app project + `*Tests-product`, no App Store variants, no other-platform app/local packages). Warns when the installed BSP was built from an older patch. +- `xcode_derived_data.py` — Xcode's DerivedData folder for the worktree (MD5 → 28 letters; honors a custom location). +- `sourcekit-xcode-bsp.sh` — `argv` of buildServer.json. Editor's server stops a running background indexer and waits for it; the indexer's server refuses to start next to an editor's. Pid files in `.build/bsp/`, checked to be sourcekit processes. +- `xcodebuild.sh [destination]` — Debug into Xcode's DerivedData with Xcode's settings (no `-derivedDataPath`: passing it changes build parameters and makes Xcode and VS Code rebuild after each other). `DDG_WORKTREE_CACHE=1`: `.build/xcodebuild` + shared CAS with `/^wt` `/^dd` maps. `.build/products` → products folder. xcbeautify + raw `path:line: error:` lines for failed tests. +- `simulator.sh [--build-only|--no-build] [device]` — builds iOS Browser for a concrete simulator, installs, `simctl launch --wait-for-debugger`, pid to `.build/simulator.pid`. +- `index-in-background.py [--all] [--if-idle]` — headless indexing; `background-indexing.sh install` = launchd agent every 30 min. +- `install-build-server.sh` + `sourcekit-xcode-bsp.patch` + `sourcekit-xcode-bsp.Package.resolved` — reproducible build of the patched BSP. ## Findings -1. **Works out of the box for app code**, Package.swift not needed. Swift extension 2.16.7 activates on `buildServer.json`. -2. **Tests had no code intelligence.** BSP didn't set a configuration → swift-build used `defaultConfigurationName = Release` → no `ENABLE_TESTABILITY` (+ `-O`) → `@testable import DuckDuckGo_Privacy_Browser` fails → sourcekitd returns nothing. Fix: `configuration: Debug` (patch). -3. **Stray `.o` in the repo root, linker failures, thousands of "duplicate output file".** BSP configured all 977 workspace targets in one build request: DMG + App Store variants, iOS + macOS, and each package library 3–4 times (`X` ×2, `X-product`, `Xdynamic-product`). They build into the same paths; swift-build drops tasks. Xcode never does this (scheme = one variant; package targets come as dependencies). Fix: `targets` include/exclude filter (patch) → 86 roots, 288 targets in the graph, 0 failures, 0 `.o`. -4. **Indexing took ~2 h (estimate).** swift-build computes `textDocument/sourceKitOptions` per file, ~0.8 s each, single-threaded in `SWBBuildServiceBundle` (100% of one core), sending the whole build request every time. Swift args of a target are identical except the trailing `-index-unit-output-path` (= `outputPath` from `buildTarget/sources`). Fix: per-target cache + single flight (patch). **Full index of a fresh worktree: ~5.5 min** (~1 min workspace load + package resolve, ~3.5 min prepare+index, up to 11 parallel swift-frontend on M4 Max). -5. **Critical path now = preparation**: ~250 prepare builds, serialized by `SWBBuildServer.preparationQueue` (~3 min). -6. **Shared CAS between worktrees (xcodebuild):** works only with path mapping. Needed: `SWIFT/CLANG_ENABLE_PREFIX_MAPPING` + `*_OTHER_PREFIX_MAPPINGS` = `=/^dd =/^wt` (`-working-directory` is the worktree root, `ModuleCache.noindex` etc. sit in DerivedData). Clean build 179 s → **90–120 s, 0 cache misses** in a new worktree. - - Xcode's own `*_ENABLE_PROJECT_PREFIX_MAPPING` maps each project/package dir to `/^src` — ambiguous, lldb can't find sources. Our `/^wt` + `sourceMap` in launch configs: breakpoints by full path bind, sources show. - - Without mapping: B build 220 s, slower than cold (all SwiftCompile/CompileC miss). -7. **CAS doesn't help the language server**: prepare-for-index doesn't use compilation caching (empty CAS stayed 60 KB; cold vs warm 347 s vs 344 s). Index units contain real paths (no `/^wt`). -8. Indexes can't be shared between worktrees cheaply: absolute paths (sourcekit-lsp has `index.indexPrefixMap`) **and** sourcekit-lsp compares unit time with file mtime — fresh checkout = everything newer → full reindex. Would need mtime tricks; not pursued. The 5-min background index makes it unnecessary. -9. `workspace/_pollIndex` isn't available in release sourcekit-lsp → indexer waits for `$/progress` "Indexing" end + 60 s quiet. -10. swift-build service runs in its own process group: killing the BSP leaves `SWBBuildServiceBundle` orphaned → kill children first. -11. Unrelated: main's `SharedPackages/BrowserServicesKit/Package.resolved` is stale (C-S-S 17.20 vs `exact: 17.26.0`) — every xcodebuild/Xcode resolve rewrites it. Spawned a separate task. +1. **Works for app code out of the box**, no Package.swift. Swift extension 2.16.7 activates on `buildServer.json`. +2. **Tests: nothing.** BSP had no configuration → swift-build used `defaultConfigurationName = Release` → no testability → `@testable import` fails. Fix: `configuration` (default Debug; swift-build's own index build defaults to Debug too). +3. **Stray `.o`, failed links, duplicate outputs.** BSP configured all 977 targets in one build (DMG + App Store, iOS + macOS, each package library 3–4 times). Fix: `targets` filter → 86 roots, 288 in the graph, 0 failures. +4. **Indexing ~2 h.** swift-build computes `sourceKitOptions` per file (~0.8 s, single thread, whole build request each time). Swift args of a target differ only in the trailing `-index-unit-output-path`. Fix: per-target cache keyed by (target, purpose) + single flight. Fresh worktree full index ~5.5 min (M4 Max, low load). +5. **Preparation** (~250 serialized prepare builds) is now the critical path. +6. **Shared CAS between worktrees (xcodebuild):** needs `SWIFT/CLANG_ENABLE_PREFIX_MAPPING` + `*_OTHER_PREFIX_MAPPINGS` = `=/^dd =/^wt`. Clean build 179 s → 90–120 s, 0 misses in a new worktree. Xcode's own project prefix mapping (`/^src` per project) breaks lldb sources; `/^wt` + `sourceMap` works. Opt-in now, because it can't be shared with Xcode (unconfirmed how Xcode's debugger handles mapped paths). +7. **CAS doesn't help the language server** (prepare-for-index doesn't cache; cold vs warm the same). +8. **Index shared with Xcode works:** with Xcode's DerivedData layout (index store = `Index.noindex/DataStore`, `INDEX_REGULAR_BUILD_*` = `Build/…`), units written by an xcodebuild build count as up to date: after building `macOS Browser`, indexing skipped all app files (2,150 files left vs 3,972). BSP prepares in `Index.noindex/SourceKitBuild` so it never shares a build DB with an open Xcode. +9. **Xcode-compatible builds:** `xcodebuild` without `-derivedDataPath` and the wrapper don't rebuild after each other (no-op 26–31 s). With `-derivedDataPath` pointing to the same folder they do. +10. **iOS simulator:** generic destination fails (`fix_libswiftWebkit_simulator.sh` needs `TARGET_DEVICE_OS_VERSION`) → build for a concrete simulator. Debugging: `simctl launch --wait-for-debugger` + lldb-dap attach by **pid** works (breakpoint in `AppDelegate`). lldb-dap with `attachCommands` or with `program` + pid never stops at breakpoints (CLI lldb does) → launch config uses `${command:pickProcess}` (newest first). VS Code substitutes variables before `preLaunchTask`, so run the task first. +11. Index sharing between worktrees: rejected (absolute paths + mtime-based staleness). +12. `workspace/_pollIndex` isn't in release sourcekit-lsp → indexer waits for "Indexing" progress end + 60 s quiet; gives up after 5 min without progress. +13. swift-build service runs in its own process group → stop children first. +14. Unrelated: main's `SharedPackages/BrowserServicesKit/Package.resolved` is stale (C-S-S 17.20 vs `exact: 17.26.0`) — separate task spawned. -## Verified (2026-10-14, Xcode 27.1) +## Review (independent agent, 2026-10-14) -- Hover / definition / completion / diagnostics: macOS app file, macOS unit test (`@testable`, jumps into app source), package test, iOS app file (ios mode). Cross-module definition goes to source, not `.swiftinterface`, once indexed. -- `xcodebuild.sh build "macOS Browser"`; lldb launch with breakpoint by full path + source map. -- Background indexer: full run, editor takeover, no orphan processes, `--all` picks only configured worktrees. -- BSP: 50 tests pass (added `TargetFilterTests`, `CachingMessageHandlerTests`), SwiftLint clean for new code. +Verdict: "close, not ready" → fixed: iOS platform reset by setup/indexer, unpinned `swift-tools-protocols` (now Package.resolved + `--force-resolved-versions`), purpose not in cache key, empty `include`, sources not generation-checked, test gaps (58 tests now), editor/indexer race (refuse + wait), stop() killing unchecked pids, indexer hanging 1 h, spaces in prefix maps, in-place install over running binaries, stale-install detection, test failures not in Problems, Xcode 26/27 wording, `re.escape`, `as_uri`, editor detection, xcbeautify missing warning, `files.watcherExclude`, lldb-dap pinned, risky `waitFor` attach removed, mismatched scheme/destination task. +Not changed: `Debug` default (documented; matches swift-build's index default). -Not verified: the VS Code UI itself (tasks, lldb-dap launch, problem matchers) — everything was driven headless. +## Verified (Xcode 27.1) + +- Code intelligence: macOS app, macOS unit test (`@testable`, jump into app), package test, iOS app (ios mode) — in both layouts. +- `Test macOS Unit Tests` task (33 tests pass), `Build macOS Browser`, iOS build for simulator, install, launch, lldb-dap attach + breakpoint; macOS launch + breakpoint (CLI lldb). +- Background indexer: full run, skip when unchanged, editor takeover, no orphans. +- Installer from scratch: patch applies, pinned build, stamp. + +Not verified: VS Code/Cursor UI itself; Xcode IDE reading our index / no-rebuild after VS Code builds (xcodebuild was used as Xcode's stand-in). ## Open items -- [ ] Try it in VS Code/Cursor UI end to end. -- [ ] Upstream PRs to slime-studio/sourcekit-xcode-bsp (configuration, targets + list-targets, buildSettings, arguments cache) — public repo, needs OK. -- [ ] Upstream issue: stray `.o` / duplicate outputs when all targets are configured; BSP doesn't exit on stdin EOF while busy. -- [ ] Decide team distribution: patch in repo (now) vs DDG fork + brew tap. -- [ ] iOS debugging on simulator (simctl install/launch --wait-for-debugger + attach) — not done. -- [x] Clean up experiment worktrees and caches (2026-10-14). +- [ ] Walk through VS Code and Cursor UI (tasks, Problems, launch/attach, workspace trust prompt). +- [ ] Check Xcode IDE: open a worktree indexed/built from VS Code — no reindex, no rebuild. +- [ ] Upstream PRs to slime-studio/sourcekit-xcode-bsp (public repo, needs OK); or a DDG fork. +- [ ] Upstream issue: duplicate outputs/stray `.o` when all targets are configured; lldb-dap attach with `program`/`attachCommands` to simulator processes. - [ ] PR (needs Asana task link).