# Dependency Management Unidirectional flow, circular-dependency prevention, and platform-specific dependency separation. ### Unidirectional Flow ``` Foundation (SharedModels, AppColors, AppFont) ↓ Design System (AppTheme = AppColors + AppFont) ↓ Infrastructure (ApiClient, ApiServer, Middleware, SharedViews) ↓ Components (Components [core], SharedComponents, AppComponents) ↓ Features (AuthFeature, AppFeature, etc.) ↓ Apps (iosApp, macApp) ``` Imports and manifest dependencies point downward, from a higher-level consumer to a lower-level provider. NEVER import from a higher layer. Build order and dependent propagation run upward and are not the dependency direction described here. ### Circular Dependency Prevention ❌ **DON'T:** ```swift // AuthFeature → AppFeature // AppFeature → AuthFeature // CIRCULAR DEPENDENCY! ``` ✅ **DO:** ```swift // Extract shared code to a new target // Both features depend on: SharedAuthModels ``` ### Platform-Specific Dependencies Separate platform-specific products and targets using `#if os()` conditionals. #### Products Separation ```swift // ---------- Base Products (All Platforms) ---------- let baseProducts: [Product] = [ .singleTargetLibrary("ApiShared"), .singleTargetLibrary("ApiClient"), .singleTargetLibrary("ApiServer"), .singleTargetLibrary("SharedModels"), .singleTargetLibrary("OpenAPICachingMiddleware"), .executable(name: "apiserverapp", targets: ["ApiServerApp"]), ] // ---------- Apple-Only Products (iOS + macOS) ---------- #if os(iOS) || os(macOS) let appleOnlyProducts: [Product] = [ .singleTargetLibrary("AppTheme"), .singleTargetLibrary("SharedViews"), .singleTargetLibrary("AuthFeature"), .singleTargetLibrary("AppFeature"), .singleTargetLibrary("AppFont"), .singleTargetLibrary("BetaSettingsFeature"), .singleTargetLibrary("DemoAppFeature"), .singleTargetLibrary("SharedComponents"), .singleTargetLibrary("Components"), .singleTargetLibrary("BenefitsComponents"), .singleTargetLibrary("AllComponents"), ] #else let appleOnlyProducts: [Product] = [] #endif // ---------- Combine All Products ---------- let allProducts = baseProducts + appleOnlyProducts + [ .singleTargetLibrary("PlaybookFeature"), // Always exposed for Xcode scheme visibility ] ``` #### Targets Separation ```swift let targets: [Target] = { // ---------- Base Targets (All Platforms) ---------- let sharedModelsTarget = Target.target( name: "SharedModels", dependencies: [] ) let apiClientTarget = Target.target( name: "ApiClient", dependencies: ["ApiShared", "SharedModels"] ) let baseTargets = [ sharedModelsTarget, apiClientTarget, ] // ---------- Apple-Only Targets (iOS + macOS) ---------- #if os(iOS) || os(macOS) let appColorsTarget = Target.target( name: "AppColors", dependencies: [] // Foundation: zero dependencies ) let appThemeTarget = Target.target( name: "AppTheme", dependencies: [ "AppColors", // Design system combines colors + fonts "AppFont", ] ) let sharedViewsTarget = Target.target( name: "SharedViews", dependencies: [ "AppColors", "AppFont", .product(name: "Inject", package: "Inject"), ] ) let authFeatureTarget = Target.target( name: "AuthFeature", dependencies: ["SharedModels", "SharedViews", "AppColors", "AppFont"] ) let appleTargets = [ appColorsTarget, appThemeTarget, sharedViewsTarget, authFeatureTarget, ] #else let appleTargets: [Target] = [] #endif // ---------- PlaybookFeature (Always Defined, Conditionally Linked) ---------- let playbookTarget = Target.target( name: "PlaybookFeature", dependencies: [ "Components", "AppComponents", "SharedModels", "ApiClient", .product(name: "Inject", package: "Inject"), .product( name: "Playbook", package: "playbook-ios", condition: .when(platforms: [.iOS]) // ← Platform-specific dependency ), .product( name: "PlaybookUI", package: "playbook-ios", condition: .when(platforms: [.iOS]) ), ] ) return baseTargets + appleTargets + [playbookTarget] }() ``` #### Why Separate Platforms? **Benefits:** - ✅ Server targets build on Linux CI without Apple SDKs - ✅ UI targets only compile on Apple platforms - ✅ Clear separation between backend and frontend code - ✅ Prevents accidental dependencies on Apple frameworks in server code - ✅ Faster CI builds (Linux can skip UI targets) **When to Use:** - UI components, SwiftUI views → `#if os(iOS) || os(macOS)` - Shared models, API contracts → No conditional (base products) - Server code, Vapor endpoints → No conditional (base products) - Apple-only frameworks (UIKit, AppKit) → `#if os(iOS) || os(macOS)` **Use `#if os()` for platform detection in `Package.swift`, never `#if canImport(UIKit)`.** `#if canImport` is evaluated lazily and silently breaks the manifest on Linux CI; `#if os()` is evaluated at manifest-parse time and gives deterministic builds across all platforms. This is the most common SPM landmine on cross-platform packages. ## Acceptance check Conforms when: - **Manifest platform detection uses `#if os()`, never `#if canImport`.** This is the named SPM landmine: `#if canImport(UIKit)` silently breaks the manifest on Linux CI. ```bash MANIFEST=Package.swift [ -f "$MANIFEST" ] || MANIFEST=Packages/Package.swift grep -n '#if canImport' "$MANIFEST" ``` Output MUST be empty; platform splits in the manifest use `#if os(iOS) || os(macOS)`. - **No dependency cycle.** Resolution and build succeed, proving the circular-dependency-prevention rule's floor: ```bash swift package resolve && swift build ``` Both MUST exit 0. - **Observable checklist** for the flow rules, each a clear pass/fail: imports and manifest dependencies go only from apps toward features, components, infrastructure, design system, and foundation, with no lower layer importing a higher one; when two targets would depend on each other, the shared code is extracted to a new lower-level target rather than creating a cycle; Apple-only products and targets sit behind `#if os(iOS) || os(macOS)` while shared models, API contracts, and server code stay unconditional (base); and a platform-specific external package dependency is attached with `condition: .when(platforms:)` rather than duplicated. A server target that builds on Linux CI without Apple SDKs is the observable signal that the separation holds.