damus
damus copied to clipboard
reduce xcode compile/build time
@danieldaquino sent a screenshot recently - it's getting slower
Compile Pain Points
- Single target holds 581 Swift files and several are 500–1,200 lines
each (e.g., damus/Features/Timeline/Models/HomeModel.swift:1, damus/
ContentView.swift:1, damus/Features/Posting/Views/PostView.swift:1, damus/
Core/Nostr/NostrEvent.swift:1), which greatly reduces incremental-build
reuse because any edit near shared types forces most of the graph to
rebuild.
- All low-level C/flatbuffers sources are pulled straight into every target
via the bridging header (damus-c/damus-Bridging-Header.h:5-11) and wide
header search paths (damus.xcodeproj/project.pbxproj:7580-7585), so the
LMDB/secp256k1/nostrdb stack is recompiled multiple times per build instead
of being linked as a prebuilt artifact.
- Release-style settings are applied even when iterating locally: Release
uses SWIFT_COMPILATION_MODE = wholemodule and -O (damus.xcodeproj/
project.pbxproj:7588-7595) while Debug inherits defaults but still compiles
every architecture and extension tied to the scheme, so turnaround stays
high.
- Build entry points (justfile:1-5) always invoke xcodebuild from scratch with
no shared DerivedData, no -skipMacroValidation, and no separation between
“app-only debug” vs “ship everything,” so incremental caching is frequently
invalidated.
- SwiftPM resolves and builds 15 external packages (see damus.xcodeproj/
project.xcworkspace/xcshareddata/swiftpm/Package.resolved:1) on every clean
build, many of which are UI-only and could be vendored/binary to avoid
repeated Swift compilation.
High-Impact Fixes
1. Modularize the Swift codebase. The existing Package.swift already defines
a single library target (Package.swift:6-32). Split it into smaller Swift
packages or local frameworks (CoreNostr, SharedUI, Features/Timeline, etc.)
that mirror the folder layout (damus/Core, damus/Shared, damus/Features).
This lets Xcode cache compiled intermediates per module, enables parallel
builds, and avoids re-typechecking giant files when editing unrelated
views.
2. Prebuild the nostrdb/flatbuffers stack. Use the supplied Makefile targets
(nostrdb/Makefile:1-35) to emit libnostrdb.a (or an .xcframework) and link
it into every target instead of compiling dozens of .c files each time.
Once it’s a binary target or vendored static library, the bridging header
can shrink to just the public umbrella header, dramatically cutting C/
Objective-C compile time for the app, extensions, and tests.
3. Break up the mega Swift files. Files such as HomeModel.swift (1,228
lines), ContentView.swift (1,147 lines), PostView.swift (977 lines),
EditPictureControl.swift (759 lines), and CameraService.swift (693 lines)
each gate a large chunk of the dependency graph. Extract subviews, feature-
specific models, and services into smaller files or even micro-modules so
incremental builds can short-circuit when you touch a localized area.
4. Adopt faster debug settings and schemes. For day-to-day work, force
SWIFT_COMPILATION_MODE = incremental and SWIFT_OPTIMIZATION_LEVEL =
-Onone in the Debug configuration, enable ONLY_ACTIVE_ARCH=YES, and create
an “AppOnly-Debug” scheme that excludes the notification/share/action-
extension targets. Doing so keeps the release settings (wholemodule, -O)
solely for CI/TestFlight, while developers rebuild only what they touch.
5. Cache SwiftPM + DerivedData artifacts. Point the just recipes to
a shared DerivedData path (e.g., add -derivedDataPath .derived and
-skipMacroValidation) and call xcodebuild -resolvePackageDependencies once
per CI run to reuse compiled package products. Low-churn packages like
EmojiKit, EmojiPicker, or SwipeActions can also be shipped as prebuilt
binaries to skip Swift compilation entirely.
I couldn’t run xcodebuild -list/-showBuildSettings because
CoreSimulatorService isn’t accessible inside this sandboxed session; if
you need confirmation of targets or current build times, run those commands
locally. Natural next steps: 1) choose the first module boundary to extract
(e.g., damus/Core/Nostr), 2) script nostrdb’s .xcframework build and update
the bridging header, 3) tweak the debug scheme/justfile flags so we can
measure the delta.