damus icon indicating copy to clipboard operation
damus copied to clipboard

reduce xcode compile/build time

Open alltheseas opened this issue 1 month ago • 2 comments

@danieldaquino sent a screenshot recently - it's getting slower

alltheseas avatar Oct 24 '25 16:10 alltheseas

IOS-2133

linear[bot] avatar Oct 24 '25 16:10 linear[bot]

 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.


alltheseas avatar Oct 25 '25 14:10 alltheseas