Cadmus
Swift/Objective-C code intelligence for VS Code and Cursor — powered by Xcode's build engine.
Cadmus brings Xcode-grade indexing to alternative editors. Jump to definition, autocomplete, diagnostics, find callers, rename symbol, find references, macro expansion, and cross-module navigation work out of the box for any Xcode project, including large monorepos with hundreds of targets.
What it does
Cadmus bridges Xcode's internal build service (SWBBuildService) with sourcekit-lsp through the Build Server Protocol. Instead of reimplementing Swift compilation logic, it speaks Xcode's native protocol to extract the exact compiler arguments your project needs.
Editor (VS Code / Cursor)
└─ sourcekit-lsp
└─ BSP (JSON-RPC)
└─ cadmus-helper
└─ SWBBuildService (Xcode's build engine)
- Every framework, SPM package, and Objective-C bridging header resolves correctly
- Build settings, xcconfig files, and conditional compilation flags are respected
- Swift macros expand correctly (
@Observable, custom macros, etc.)
- Cross-module jump-to-definition, diagnostics, and navigation work across targets
- Background indexing populates the index store for find callers, rename, and find references
- File changes automatically trigger re-indexing of affected and dependent targets
- Projects with 500+ targets work without manual configuration
Quick start
Requirements
- macOS with Xcode 26+ installed
- Swift for VS Code — provides sourcekit-lsp integration. Cadmus supplies the build server; the Swift extension supplies the language server. Installed automatically as a dependency.
Install from release
Download the latest .vsix from Releases, then:
code --install-extension cadmus-*.vsix
# or for Cursor:
cursor --install-extension cadmus-*.vsix
Build from source
git clone https://github.com/gokberkince/cadmus-vscode.git
cd cadmus-vscode
make package
code --install-extension cadmus-*.vsix
What happens on first open
- Extension detects your
.xcworkspace or .xcodeproj
- Writes
buildServer.json so sourcekit-lsp uses Cadmus as its build server
- Spawns
cadmus-helper which connects to Xcode's build service
- Progress notification shows each phase until ready
- Jump-to-definition, autocomplete, and diagnostics start working
Subsequent launches are fast — compiler args are cached on disk. When the SWB bridge is still alive from a previous session and the PIF hasn't changed, session reuse skips PIF transfer, dependency graph computation, and bootstrap builds entirely (under 1 second startup).
How it works
Initialization
- SPM Resolve — Runs
swift package resolve for SPM dependencies
- PIF Generation — Parses xcodeproj/xcworkspace into Xcode's Project Interchange Format
- SWB Session — Connects to SWBBuildService, creates a session, transfers PIF
- Dependency Graph — Computes the full target dependency graph (explicit + implicit framework linking)
- Bootstrap Build — Creates build descriptions — compiler args become available
- Ready — sourcekit-lsp can now request compiler args for any file
- Full Workspace Build (background) — Creates build descriptions for all targets, not just the bootstrap target's transitive dependencies
After step 6, sourcekit-lsp's background indexer takes over immediately: waitForBuildSystemUpdates returns after the bootstrap build description (not the full workspace build), so LSP can start showing "Indexing N / M" progress right away. Targets not covered by bootstrap are handled lazily — when LSP sends buildTarget/prepare for a non-bootstrap target, cadmus-helper returns success immediately and queues it. When the full workspace build completes, buildTarget/didChange is sent only for those deferred targets (and their dependents), causing LSP to re-prepare them with the real full-workspace build description.
Background indexing
Cadmus advertises prepareProvider: true to sourcekit-lsp, which enables its SemanticIndexManager. This is what makes project-wide features work:
- sourcekit-lsp sends
buildTarget/prepare for each target (sorted by dependency depth)
- cadmus-helper runs SWB's
prepareForIndexing — builds all dependency .swiftmodule files
- sourcekit-lsp runs
swiftc -index-file for each source file in the prepared target
- Index store gets populated — find callers, rename, find references all work
Editor operations take priority: when you open a file, sourcekit-lsp cancels any background prepare and triggers a high-priority prepare for that file's target first. cadmus-helper does not run any proactive prepare builds — all preparation is driven by sourcekit-lsp's own background indexing pipeline to avoid "multiple concurrent index build operations" errors in SWB.
File change flow
- sourcekit-lsp detects file changes and notifies cadmus-helper (
workspace/didChangeWatchedFiles)
- For existing files: cadmus-helper maps files to targets, computes reverse dependencies (which targets import the changed targets), and sends
buildTarget/didChange immediately (old build description IDs are still valid for existing files)
- For new source files: cadmus-helper updates the
.pbxproj so the file maps to the correct target — for buildable folders (Xcode 16+) this updates membershipExceptions, for traditional groups it adds a PBXFileReference + PBXBuildFile. Then:
- PIF reload: regenerates project structure (targeted single-project reload when possible, ~1s vs ~15s full)
- CDG + Bootstrap rebuild: produces a new build description ID that includes the new file
buildTarget/didChange sent after the bootstrap rebuild completes (old build descriptions return empty for new files)
- sourcekit-lsp marks affected targets as needing re-preparation and schedules new
buildTarget/prepare requests
- Affected
.swiftmodule files get rebuilt, diagnostics and autocomplete refresh automatically
cadmus-helper does not manage file watching or build scheduling itself — sourcekit-lsp drives everything.
Caching
| Layer |
Storage |
Survives restart |
| In-memory |
Dict in BSPServer actor |
No |
| Disk cache |
cadmus-args-cache.json in DerivedData |
Yes |
| SWB build cache |
SWBBuildService internal state |
Yes |
| SWB bridge |
Unix domain socket daemon |
Yes (across cadmus-helper restarts) |
The disk cache stores compiler args keyed by target GUID with a fingerprint based on project.pbxproj modification times. The bootstrap build description ID is saved to disk immediately after bootstrap (not deferred until full workspace), so if the process dies before the full workspace build completes, the next launch still has a valid cached descID for the session reuse fast path. On restart, cached args are served instantly while SWB initializes in the background.
Cadmus adds a panel to the activity bar showing the current indexing state. From here you can inspect configuration, change the target platform, manage caches, re-trigger indexing, and open log files — all without leaving the editor.
CADMUS
├── Workspace: MyApp.xcworkspace [edit]
├── Destination: iOS Simulator [edit]
├── Helper Binary: cadmus-helper [bundled]
├── DerivedData: .cadmus
├── Cache: present [trash]
└── Logs
├── Extension Output
├── BSP Server Log
└── SWB Bridge Log
Configuration
| Setting |
Default |
Description |
cadmus.helperPath |
Auto-detect |
Path to cadmus-helper binary |
cadmus.platform |
macosx |
Target platform (macosx, iphonesimulator, etc.) |
cadmus.derivedDataPath |
.cadmus/ |
Custom DerivedData path |
Commands
| Command |
Description |
| Cadmus: Select Xcode Workspace |
Choose which workspace/project to index |
| Cadmus: Restart Indexing |
Re-initialize from scratch |
| Cadmus: Change Destination |
Switch target platform (macOS, iOS Simulator, etc.) |
| Cadmus: Clear Cache |
Delete compiler args disk cache |
| Cadmus: Re-trigger Indexing |
"With Cache" (restart only) or "Clean" (delete cache + index data + restart) |
| Cadmus: Open BSP Log |
Open the BSP server log in the editor |
| Cadmus: Open Bridge Log |
Open the SWB bridge daemon log in the editor |
Debugging
# BSP server logs (one per project)
tail -f /tmp/cadmus-bsp-*.log
# SWB bridge daemon logs
tail -f /tmp/cadmus-swb-*.log
# Progress file (JSONL, polled by extension)
cat /tmp/cadmus-progress-*.jsonl
Xcode projects only
Cadmus is designed for Xcode projects (.xcworkspace / .xcodeproj). It does not activate for pure Swift Package Manager packages — sourcekit-lsp already handles those natively.
Detection logic:
- If the workspace root contains
Package.swift but no .xcworkspace or .xcodeproj, Cadmus treats it as an SPM package and stays inactive
- Nested Xcode projects inside SPM checkouts (
.build/, .cadmus/, .swiftpm/) are ignored
- If a stale
buildServer.json from a previous Cadmus session is found in a non-Xcode workspace, it is automatically cleaned up
Process lifecycle
cadmus-helper writes its PID to /tmp/cadmus-bsp-<project>.pid on startup. This enables reliable cleanup:
- stdin EOF: When sourcekit-lsp dies or the editor closes, cadmus-helper detects stdin EOF and force-exits after a 2-second grace period (the actor's TaskGroup may be blocked on in-progress SWB builds that will never complete).
- Extension shutdown: The extension's
deactivate() kills cadmus-helper via the PID file with SIGKILL, ensuring no zombie processes survive editor shutdown.
- LSP restart: Before calling
swift.restartLSPServer, the extension kills the old cadmus-helper via PID file. This breaks the old BSP connection so sourcekit-lsp can stop cleanly instead of blocking on a pending BSP response.
The SWB bridge daemon is unaffected by cadmus-helper restarts — it keeps SWBBuildService alive for session reuse.
Known limitations
- Requires Xcode (uses SWBBuildService)
- First launch for large projects (500+ targets) takes 1-3 minutes
- macOS only
Contributing
The project has two main parts:
- VS Code extension (
src/) — TypeScript, handles activation, UI, and spawning cadmus-helper
- cadmus-helper (
cadmus-helper/) — Swift, the BSP server that talks to SWBBuildService
Getting started
git clone https://github.com/gokberkince/cadmus-vscode.git
cd cadmus-vscode
# Build the Swift backend
cd cadmus-helper && swift build && cd ..
# Install VS Code extension dependencies
npm install
Development workflow
- Make changes to
cadmus-helper/ or src/
- Build:
cd cadmus-helper && swift build
- Test with a real Xcode project — point the extension at your local build via
cadmus.helperPath
- Check logs:
tail -f /tmp/cadmus-bsp-*.log
Architecture
See cadmus-helper/ARCHITECTURE.md for detailed internals — PIF generation, SWB protocol, file change handling, caching, and dependency graph construction.
License
GPL-3.0 — see LICENSE