# XcodeGen spec for the native SwiftUI VniDrop app (iOS/iPadOS/macOS). # Regenerate the project with: xcodegen generate (run from apple/) # Requires two generated inputs first (both gitignored), before xcodegen: # - Rust core: apple/scripts/build-core.sh debug # - Localization: (cd localization && bun run src/cli.ts generate) # -> VniDrop/Resources/Localizable.xcstrings, VniDrop/Generated/L10n.swift name: VniDrop options: bundleIdPrefix: com.vnidrop deploymentTarget: iOS: "18.2" macOS: "15.0" createIntermediateGroups: true # Build configurations. Declaring `configs` replaces XcodeGen's Debug/Release # defaults, so both are re-listed here. `Release-Direct` is a release-type config # used only by the VniDropDirect (notarized DMG + Sparkle) target; the App Store # `VniDrop` target ships under plain `Release`. configs: Debug: debug Release: release Release-Direct: release # Project-wide build settings (applied to every target/config). settings: base: # Strip unreachable code from release binaries. DEAD_CODE_STRIPPING: YES # Flag user-facing strings that aren't localized (the app ships 9 languages). CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED: YES packages: VnidropCore: path: VnidropCore SFSafeSymbols: url: https://github.com/SFSafeSymbols/SFSafeSymbols from: "5.3.0" # Sparkle powers in-app auto-updates for the direct-download (.dmg) build only. # It is linked exclusively by the VniDropDirect target — SwiftPM links products # per target, not per config, so keeping it off the App Store target is what # guarantees the store binary never bundles a self-updater (App Store forbids it). Sparkle: url: https://github.com/sparkle-project/Sparkle from: "2.9.4" # Shared definition for the two shipping app targets. `VniDrop` (App Store / # TestFlight) and `VniDropDirect` (notarized DMG + Sparkle) build the exact same # sources; only their destinations, extra dependencies, and the DIRECT_DISTRIBUTION # compile flag differ (set per target below). targetTemplates: AppBase: type: application configFiles: Debug: Signing.xcconfig Release: Signing.xcconfig Release-Direct: Signing.xcconfig sources: - path: VniDrop excludes: - "Resources/Info.plist" - "Resources/VniDrop.entitlements" - "Resources/VniDropDirect.entitlements" - "Resources/**/.DS_Store" settings: base: PRODUCT_NAME: VniDrop PRODUCT_BUNDLE_IDENTIFIER: com.vnidrop.app MARKETING_VERSION: "0.1.0" # Placeholder only — the real CFBundleVersion is stamped at build time as a # UTC YYMMDD.HHMM timestamp by the "Stamp build number" phase below, so every # build is monotonic and self-describing (shown as "MARKETING_VERSION (build)"). CURRENT_PROJECT_VERSION: "1" GENERATE_INFOPLIST_FILE: NO INFOPLIST_FILE: VniDrop/Resources/Info.plist CODE_SIGN_ENTITLEMENTS: VniDrop/Resources/VniDrop.entitlements # Mirror the Info.plist identity so Xcode's Identity editor shows it too # (the editor reads these build settings, not the manual plist). INFOPLIST_KEY_CFBundleDisplayName: VniDrop INFOPLIST_KEY_LSApplicationCategoryType: public.app-category.utilities SWIFT_VERSION: "6.0" SWIFT_STRICT_CONCURRENCY: complete ENABLE_USER_SCRIPT_SANDBOXING: NO ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon # App-wide accent (macOS sidebar selection, default control tints). The # AccentColor asset mirrors VniDropColors.brandPurple — keep them in sync. ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor configs: release: # Produce a dSYM in the archive so symbol upload succeeds. DEBUG_INFORMATION_FORMAT: dwarf-with-dsym release-direct: DEBUG_INFORMATION_FORMAT: dwarf-with-dsym dependencies: - package: VnidropCore - package: SFSafeSymbols - sdk: SystemConfiguration.framework - sdk: Security.framework - sdk: libresolv.tbd preBuildScripts: # Enforce the typed-resources convention (see .swiftlint.yml). Required: fails # the build if SwiftLint is missing so the rules can't be silently bypassed. - name: SwiftLint (typed resources) basedOnDependencyAnalysis: false script: | # Xcode runs build phases with a minimal PATH that omits Homebrew, so add # the common Homebrew bin dirs (Apple Silicon + Intel) before resolving it. export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" if which swiftlint >/dev/null; then swiftlint lint --config "${SRCROOT}/.swiftlint.yml" else echo "error: SwiftLint not installed — run 'brew install swiftlint'" exit 1 fi postBuildScripts: # Stamp CFBundleVersion as a UTC YYMMDD.HHMM timestamp into the built # Info.plist before code signing. Runs for every build (Xcode GUI archive and # CLI alike), so both the App Store and direct-download channels get a # monotonic, meaningful build id. CI/reproducible builds can pin it via the # VNIDROP_BUILD env var. MARKETING_VERSION stays the human X.Y.Z version. - name: Stamp build number (UTC timestamp) basedOnDependencyAnalysis: false script: | build="${VNIDROP_BUILD:-$(date -u +%y%m%d.%H%M)}" plist="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}" if [ -f "$plist" ]; then /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $build" "$plist" echo "Stamped CFBundleVersion = $build" else echo "warning: Info.plist not found at $plist; CFBundleVersion not stamped" fi targets: # App Store / TestFlight target. iOS + macOS, sandboxed, no self-updater. VniDrop: templates: [AppBase] supportedDestinations: [iOS, macOS] # Direct-download macOS target: Developer ID signed, notarized, ships in a .dmg # and self-updates via Sparkle. DIRECT_DISTRIBUTION gates all Sparkle code so the # App Store target above never compiles or links it. VniDropDirect: templates: [AppBase] supportedDestinations: [macOS] settings: base: SWIFT_ACTIVE_COMPILATION_CONDITIONS: "$(inherited) DIRECT_DISTRIBUTION" # Notarization requires the hardened runtime. ENABLE_HARDENED_RUNTIME: YES # Non-sandboxed entitlements: a sandboxed Developer ID app needs a # provisioning profile, which direct distribution avoids. (App Store target # keeps VniDrop.entitlements with the sandbox.) CODE_SIGN_ENTITLEMENTS: VniDrop/Resources/VniDropDirect.entitlements # The Rust core's macOS slice (vnidrop.xcframework) is arm64-only # (build-core.sh builds aarch64-apple-darwin only), so the direct build is # Apple-Silicon-only. Pin ARCHS so the Release-Direct (universal-by-default) # link doesn't fail looking for x86_64 symbols. ARCHS: arm64 dependencies: - package: Sparkle VniDropTests: type: bundle.unit-test supportedDestinations: [iOS, macOS] configFiles: Debug: Signing.xcconfig Release: Signing.xcconfig Release-Direct: Signing.xcconfig sources: - path: Tests settings: base: GENERATE_INFOPLIST_FILE: YES SWIFT_VERSION: "6.0" SWIFT_STRICT_CONCURRENCY: complete dependencies: - target: VniDrop schemes: VniDrop: build: targets: VniDrop: all run: config: Debug test: config: Debug targets: - VniDropTests # TestFlight ships the Release build; make the Archive/Profile actions explicit # so Product → Archive can never pick up a Debug configuration. profile: config: Release archive: config: Release # Direct-download build: always the Release-Direct config (Sparkle + notarization). VniDropDirect: build: targets: VniDropDirect: all run: config: Release-Direct profile: config: Release-Direct archive: config: Release-Direct