Bring Sparkle updates onto the renamed Mac app
Sparkle was never on main. It lived only on native/sparkle-updates (68fc7b7), built from pre-rename sources and never merged, which is why the published Mac download was still Meet long after the rename landed. This carries those six files forward onto the renamed tree. The Mac app is a NEW lineage, not a continuation. Sparkle keys updates off the bundle identifier, so net.amadan.oficina.calls will never be offered an update published under net.amadan.oficina.meet — an install of the old one goes quiet rather than migrating. Nobody has it installed, so the cost is nil, but it is why this starts at build 1 rather than continuing Meet's build 2: the old numbering would imply a continuity that does not exist. One thing worth recording, because doing it the obvious way breaks the app. bundle-mac.py exists on both sides: the Sparkle branch added the configuration argument, the products path and the resource-bundle copy, while main separately grew the CFBundleURLTypes block for native app links. Taking the Sparkle branch's file wholesale silently drops the URL scheme and the Mac app stops answering oficina-calls:// links. So main's file is the base here and only the Sparkle hunks are applied on top. RastrilloMacUpdates is conditioned on macOS, and the iPhone build was checked to confirm it stays that way: CallsPhone.app embeds CallsBridge and WebRTC and no Sparkle. Verified: GOTOOLCHAIN=go1.26.5 make ci passes, the iOS simulator build succeeds, and the release is notarized, stapled and accepted as "Notarized Developer ID".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6 files changed,
+53
−8
native/apple/Package.resolved+17 −0native/apple/Package.swift+6 −1native/apple/Sources/CallsMac/CallsMac.swift+10 −1native/mac-updates.json+6 −0native/scripts/build-mac.sh+6 −5native/scripts/bundle-mac.py+8 −1
CI passed — run details
diff --git a/native/apple/Package.resolved b/native/apple/Package.resolved| index c68b7f5..d797920 100644 |
| --- a/native/apple/Package.resolved |
| +++ b/native/apple/Package.resolved |
| @@ -1,5 +1,22 @@ |
| { |
| "pins" : [ |
| + { |
| + "identity" : "native", |
| + "kind" : "remoteSourceControl", |
| + "location" : "https://amadan.net/rastrillo/native", |
| + "state" : { |
| + "revision" : "4c61d6283a188c95647e1d0e609e69d29c2e7fa1" |
| + } |
| + }, |
| + { |
| + "identity" : "sparkle", |
| + "kind" : "remoteSourceControl", |
| + "location" : "https://github.com/sparkle-project/Sparkle", |
| + "state" : { |
| + "revision" : "ac2def288cbff5cfc7df3ffef6abdf45b72bcb0a", |
| + "version" : "2.9.6" |
| + } |
| + }, |
| { |
| "identity" : "webrtc", |
| "kind" : "remoteSourceControl", |
diff --git a/native/apple/Package.swift b/native/apple/Package.swift| index 9f3b436..522f2fd 100644 |
| --- a/native/apple/Package.swift |
| +++ b/native/apple/Package.swift |
| @@ -1,5 +1,9 @@ |
| // swift-tools-version: 5.9 |
| import PackageDescription |
| +import Foundation |
| + |
| +let sharedNative: Package.Dependency = ProcessInfo.processInfo.environment["OFICINA_NATIVE_PACKAGE_PATH"].map { .package(path: $0) } |
| + ?? .package(url: "https://amadan.net/rastrillo/native", revision: "4c61d6283a188c95647e1d0e609e69d29c2e7fa1") |
| let package = Package( |
| name: "CallsNative", |
| @@ -10,13 +14,14 @@ let package = Package( |
| .executable(name: "CallsInterop", targets: ["CallsInterop"]), |
| ], |
| dependencies: [ |
| + sharedNative, |
| .package(url: "https://github.com/stasel/WebRTC.git", exact: "152.0.0") |
| ], |
| targets: [ |
| .binaryTarget(name: "CallsBridge", path: "../build/CallsBridge.xcframework"), |
| .target( |
| name: "CallsKit", dependencies: ["CallsBridge", .product(name: "WebRTC", package: "WebRTC")]), |
| - .executableTarget(name: "CallsMac", dependencies: ["CallsKit"], resources: [.copy("Resources/AppIcon.png"), .copy("Resources/MacIcon.png")]), |
| + .executableTarget(name: "CallsMac", dependencies: [.product(name: "RastrilloMacUpdates", package: "native", condition: .when(platforms: [.macOS])), "CallsKit"], resources: [.copy("Resources/AppIcon.png"), .copy("Resources/MacIcon.png")]), |
| .executableTarget(name: "CallsInterop", dependencies: ["CallsKit"]), |
| .testTarget(name: "CallsKitTests", dependencies: ["CallsKit"]), |
| ] |
diff --git a/native/apple/Sources/CallsMac/CallsMac.swift b/native/apple/Sources/CallsMac/CallsMac.swift| index 8252709..c059dfa 100644 |
| --- a/native/apple/Sources/CallsMac/CallsMac.swift |
| +++ b/native/apple/Sources/CallsMac/CallsMac.swift |
| @@ -1,3 +1,6 @@ |
| +#if os(macOS) |
| +import RastrilloMacUpdates |
| +#endif |
| import AppKit |
| @preconcurrency import CallsBridge |
| import CallsKit |
| @@ -5,6 +8,9 @@ import SwiftUI |
| import WebRTC |
| @main struct CallsMacApp: App { |
| + #if os(macOS) |
| + @State private var updater = NativeUpdater() |
| + #endif |
| @NSApplicationDelegateAdaptor(CallsAppDelegate.self) private var appDelegate |
| @StateObject private var model = CallsModel() |
| var body: some Scene { |
| @@ -13,7 +19,10 @@ import WebRTC |
| .onOpenURL { model.openMeetingURL($0) } |
| } |
| .defaultSize(width: 1000, height: 760) |
| - .commands { CommandGroup(replacing: .newItem) {} } |
| + .commands { |
| + CommandGroup(replacing: .newItem) {} |
| + NativeUpdateCommands(updater: updater) |
| + } |
| } |
| } |
diff --git a/native/mac-updates.json b/native/mac-updates.json| new file mode 100644 |
| index 0000000..bad934c |
| --- /dev/null |
| +++ b/native/mac-updates.json |
| @@ -0,0 +1,6 @@ |
| +{ |
| + "bundle_id": "net.amadan.oficina.calls", |
| + "feed_url": "https://raw.amadan.net/oficina/native-releases/main/calls/appcast.xml", |
| + "public_ed_key": "/FxO7cZnM7P5c9ZrUPUJvpCnR65sNmrzte+I5vHarE8=", |
| + "keychain_account": "oficina" |
| +} |
diff --git a/native/scripts/build-mac.sh b/native/scripts/build-mac.sh| index 0e9ad65..12a3634 100755 |
| --- a/native/scripts/build-mac.sh |
| +++ b/native/scripts/build-mac.sh |
| @@ -1,9 +1,10 @@ |
| #!/bin/sh |
| set -eu |
| cd "$(dirname "$0")/.." |
| -./scripts/build-bridge.sh |
| -swift scripts/generate-mac-icon.swift |
| -swift build --package-path apple |
| -python3 scripts/bundle-mac.py |
| -codesign --force --deep --sign - build/Calls.app |
| +if [ "${OFICINA_SKIP_PREPARE:-0}" != 1 ]; then ./scripts/build-bridge.sh; fi |
| +configuration="${OFICINA_BUILD_CONFIGURATION:-debug}" |
| +swift build --package-path apple --configuration "$configuration" --product CallsMac |
| +python3 scripts/bundle-mac.py "$configuration" |
| +shared_native="${OFICINA_NATIVE_PACKAGE_PATH:-apple/.build/checkouts/native}" |
| +python3 "$shared_native/tools/mac_updates.py" build/Calls.app --package apple "$@" |
| printf 'Built %s/build/Calls.app\n' "$PWD" |
diff --git a/native/scripts/bundle-mac.py b/native/scripts/bundle-mac.py| index 3df0828..94c30b7 100644 |
| --- a/native/scripts/bundle-mac.py |
| +++ b/native/scripts/bundle-mac.py |
| @@ -3,13 +3,18 @@ from pathlib import Path |
| import plistlib |
| import shutil |
| import subprocess |
| +import sys |
| +configuration = sys.argv[1] if len(sys.argv) > 1 else 'debug' |
| +if configuration not in ('debug', 'release'): raise ValueError('invalid build configuration') |
| root = Path(__file__).resolve().parent.parent |
| +products = Path(subprocess.check_output(['swift', 'build', '--package-path', str(root / 'apple'), '--configuration', configuration, '--show-bin-path'], text=True).strip()) |
| bundle = root / 'build/Calls.app/Contents' |
| +if bundle.parent.exists(): shutil.rmtree(bundle.parent) |
| (bundle / 'MacOS').mkdir(parents=True, exist_ok=True) |
| (bundle / 'Frameworks').mkdir(parents=True, exist_ok=True) |
| (bundle / 'Resources').mkdir(parents=True, exist_ok=True) |
| -shutil.copy2(root / 'apple/.build/debug/CallsMac', bundle / 'MacOS/CallsMac') |
| +shutil.copy2(products / 'CallsMac', bundle / 'MacOS/CallsMac') |
| subprocess.run(['swift', str(root / 'scripts/generate-mac-icon.swift')], check=True) |
| icon = root / 'apple/Sources/CallsMac/Resources/MacIcon.png' |
| shutil.copy2(icon, bundle / 'Resources/MacIcon.png') |
| @@ -53,4 +58,6 @@ for xc in [root / 'build/CallsBridge.xcframework', *list((root / 'apple/.build/a |
| dest = bundle / 'Frameworks' / source.name |
| if dest.exists(): shutil.rmtree(dest) |
| shutil.copytree(source, dest, symlinks=True) |
| +for resource in products.glob('*.bundle'): |
| + shutil.copytree(resource, bundle / 'Resources' / resource.name, symlinks=True) |
| subprocess.run(['install_name_tool', '-add_rpath', '@executable_path/../Frameworks', str(bundle / 'MacOS/CallsMac')], check=True) |