project.yml

name: Fader
options:
  bundleIdPrefix: dev.pantafive
  deploymentTarget:
    macOS: "15.0"
  createIntermediateGroups: true

packages:
  Sparkle:
    url: https://github.com/sparkle-project/Sparkle
    exactVersion: 2.9.2

settings:
  base:
    SWIFT_VERSION: "6.0"
    SWIFT_STRICT_CONCURRENCY: complete
    MARKETING_VERSION: 0.15.0
    # Sparkle compares CFBundleVersion, not the marketing string — tying them
    # together means the CI's MARKETING_VERSION override feeds both, and a
    # static build number can never silently disable updates.
    CURRENT_PROJECT_VERSION: $(MARKETING_VERSION)
    CODE_SIGN_STYLE: Automatic
    ENABLE_HARDENED_RUNTIME: true
    # The local-build marker script edits the built Info.plist.
    ENABLE_USER_SCRIPT_SANDBOXING: false
    DEAD_CODE_STRIPPING: true
  configs:
    Release:
      # Size-tuned: the only hot path is trivial float math in the IO proc,
      # -Osize costs it nothing. Strip runs inside xcodebuild before signing;
      # release.yml verifies the symbol table is actually gone.
      SWIFT_OPTIMIZATION_LEVEL: -Osize
      DEPLOYMENT_POSTPROCESSING: true
      STRIP_INSTALLED_PRODUCT: true
      STRIP_STYLE: all

targets:
  Fader:
    type: application
    platform: macOS
    sources: [Fader]
    settings:
      base:
        PRODUCT_BUNDLE_IDENTIFIER: dev.pantafive.fader
        INFOPLIST_FILE: Fader/Resources/Info.plist
        CODE_SIGN_ENTITLEMENTS: Fader/Resources/Fader.entitlements
        ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
    dependencies:
      - package: Sparkle
    # Local builds show "local" in the menu header instead of the release
    # number they were forked from. Stamps only the display string — the
    # numeric CFBundleVersion stays, so Sparkle's comparison is untouched.
    # CI (GitHub Actions sets CI=true) is left alone.
    postBuildScripts:
      - name: Mark local build
        # Same path as input and output marks this a mutation of the built
        # plist: ordered after Info.plist processing, before code signing.
        # Either alone breaks — input-only let the stamp land post-signing
        # (invalid seal), output-only let plist processing overwrite it.
        inputFiles:
          - $(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)
        outputFiles:
          - $(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)
          # A mutating task must also produce a non-mutable node, or the
          # build graph rejects it ("no other virtual output node").
          - $(DERIVED_FILE_DIR)/local-build-mark
        script: |
          if [ "${CI:-}" != "true" ]; then
            /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString local" \
              "$TARGET_BUILD_DIR/$INFOPLIST_PATH"
          fi
          touch "$DERIVED_FILE_DIR/local-build-mark"
    entitlements:
      path: Fader/Resources/Fader.entitlements
      properties:
        com.apple.security.app-sandbox: true
        com.apple.security.device.audio-input: true
        com.apple.security.device.bluetooth: true
        com.apple.security.network.client: true
        # Sparkle's sandboxed-install path: the app talks to its Installer
        # XPC service over these mach names. Literal bundle id, not
        # $(PRODUCT_BUNDLE_IDENTIFIER) — release.yml re-signs with raw
        # codesign, which does not expand build settings.
        com.apple.security.temporary-exception.mach-lookup.global-name:
          - dev.pantafive.fader-spks
          - dev.pantafive.fader-spki

  # Hostless unit tests: pure-logic sources are compiled into the bundle
  # directly, so no app process needs to launch (LSUIElement hosts hang
  # the test runner).
  FaderTests:
    type: bundle.unit-test
    platform: macOS
    sources:
      # Hand-listed on purpose (a glob would silently pull HAL-importing
      # files in) — extracting a new pure-logic source? Add it here too.
      - Tests
      - Fader/Audio/VolumeStore.swift
      - Fader/Audio/DeviceUsageStore.swift
      - Fader/Audio/DevicePriorityStore.swift
      - Fader/Audio/JSONDefaults.swift
      - Fader/Audio/DeviceSymbol.swift
      - Fader/Audio/AudioDevice.swift
      - Fader/Audio/DevicePriorityPolicy.swift
      - Fader/Audio/MultiOutputPolicy.swift
      - Fader/Audio/VolumeTaper.swift
      # Property wrappers and AudioDirection; nothing in the tests touches
      # a live HAL.
      - Fader/Audio/HALProperty.swift
    settings:
      base:
        GENERATE_INFOPLIST_FILE: true

schemes:
  Fader:
    build:
      targets:
        Fader: all
    test:
      targets:
        - FaderTests