From 87813b3b190ae644f50880786f2b365bc5e98200 Mon Sep 17 00:00:00 2001 From: Ancil Maxwell Hoffman Date: Tue, 29 Sep 2026 17:04:29 +0200 Subject: [PATCH 1/5] Use the two-parameter onChange(of:) on macOS 14 The single-parameter variant is deprecated since macOS 14.0, and the app targets 14.6. Co-Authored-By: Claude Opus 5.5 --- Xcodes/Frontend/Preferences/PlatformsListView.swift | 2 +- Xcodes/XcodesApp.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Xcodes/Frontend/Preferences/PlatformsListView.swift b/Xcodes/Frontend/Preferences/PlatformsListView.swift index d789e8a5..d2bd04ed 100644 --- a/Xcodes/Frontend/Preferences/PlatformsListView.swift +++ b/Xcodes/Frontend/Preferences/PlatformsListView.swift @@ -55,7 +55,7 @@ struct PlatformsListView: View { .task { loadRuntimes() } - .onChange(of: appState.installedRuntimes) { _ in + .onChange(of: appState.installedRuntimes) { loadRuntimes() } } diff --git a/Xcodes/XcodesApp.swift b/Xcodes/XcodesApp.swift index 0d46447a..88a27830 100644 --- a/Xcodes/XcodesApp.swift +++ b/Xcodes/XcodesApp.swift @@ -20,7 +20,7 @@ struct XcodesApp: App { // so that it's triggered when an individual window's phase changes instead of all window phases. // When used on a View it's also invoked on launch, which doesn't occur with a WindowGroup. // FB8954581 ScenePhase read from App doesn't return a value on launch - .onChange(of: scenePhase) { newScenePhase in + .onChange(of: scenePhase) { _, newScenePhase in guard !isTesting else { return } if case .active = newScenePhase { appState.updateIfNeeded() From 569199b43edb852b093ee3084f10a75d190d674d Mon Sep 17 00:00:00 2001 From: Ancil Maxwell Hoffman Date: Tue, 29 Sep 2026 17:04:29 +0200 Subject: [PATCH 2/5] Fix Swift concurrency warnings - UpdatesPreferencePane: read the observed value from the KVO change instead of the updater's main actor-isolated properties inside the Sendable change handler. - AppState+Runtimes: capture self weakly in the progress closure too, so the inner [weak self] doesn't differ from an implicit strong capture. - NotificationManager: drop awaits on calls that are already on the main actor. Co-Authored-By: Claude Opus 5.5 --- Xcodes/Backend/AppState+Runtimes.swift | 2 +- Xcodes/Backend/NotificationManager.swift | 16 ++++++++-------- .../Preferences/UpdatesPreferencePane.swift | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Xcodes/Backend/AppState+Runtimes.swift b/Xcodes/Backend/AppState+Runtimes.swift index 78cf486e..72a9f498 100644 --- a/Xcodes/Backend/AppState+Runtimes.swift +++ b/Xcodes/Backend/AppState+Runtimes.swift @@ -188,7 +188,7 @@ extension AppState { for: runtime, destinationDirectory: .xcodesApplicationSupport, downloader: downloader - ) { progress in + ) { [weak self] progress in let expectedTaskID = taskID Task { @MainActor [weak self] in if let expectedTaskID, self?.runtimeTaskIDs[runtime.identifier] != expectedTaskID { diff --git a/Xcodes/Backend/NotificationManager.swift b/Xcodes/Backend/NotificationManager.swift index 4b5fb8c3..ca1bfdd9 100644 --- a/Xcodes/Backend/NotificationManager.swift +++ b/Xcodes/Backend/NotificationManager.swift @@ -60,13 +60,13 @@ public final class NotificationManager: NSObject, UNUserNotificationCenterDelega notificationStatusTask = Task { [weak self] in let settings = await UNUserNotificationCenter.current().notificationSettings() guard !Task.isCancelled else { - await self?.clearNotificationStatusTask(id: taskID) + self?.clearNotificationStatusTask(id: taskID) return } let status = NotificationManager.systemPromptStatusFromSettings(settings) - await self?.setNotificationStatus(status, ifNotificationStatusTaskID: taskID) - await self?.clearNotificationStatusTask(id: taskID) + self?.setNotificationStatus(status, ifNotificationStatusTaskID: taskID) + self?.clearNotificationStatusTask(id: taskID) } } @@ -94,14 +94,14 @@ public final class NotificationManager: NSObject, UNUserNotificationCenterDelega do { let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) guard !Task.isCancelled else { - await self?.clearRequestAccessTask(id: taskID) + self?.clearRequestAccessTask(id: taskID) return } Logger.appState.log("User has \(granted ? "Granted" : "NOT GRANTED") notification permission") } catch { guard !Task.isCancelled else { - await self?.clearRequestAccessTask(id: taskID) + self?.clearRequestAccessTask(id: taskID) return } @@ -110,13 +110,13 @@ public final class NotificationManager: NSObject, UNUserNotificationCenterDelega let settings = await UNUserNotificationCenter.current().notificationSettings() guard !Task.isCancelled else { - await self?.clearRequestAccessTask(id: taskID) + self?.clearRequestAccessTask(id: taskID) return } let status = NotificationManager.systemPromptStatusFromSettings(settings) - await self?.setNotificationStatus(status, ifRequestAccessTaskID: taskID) - await self?.clearRequestAccessTask(id: taskID) + self?.setNotificationStatus(status, ifRequestAccessTaskID: taskID) + self?.clearRequestAccessTask(id: taskID) } } diff --git a/Xcodes/Frontend/Preferences/UpdatesPreferencePane.swift b/Xcodes/Frontend/Preferences/UpdatesPreferencePane.swift index de9ab1f1..eba9ee7c 100644 --- a/Xcodes/Frontend/Preferences/UpdatesPreferencePane.swift +++ b/Xcodes/Frontend/Preferences/UpdatesPreferencePane.swift @@ -110,9 +110,9 @@ class ObservableUpdater: ObservableObject { automaticallyChecksForUpdatesObservation = updater.observe( \.automaticallyChecksForUpdates, options: [.initial, .new, .old], - changeHandler: { [weak self] updater, change in - guard change.newValue != change.oldValue else { return } - let automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates + changeHandler: { [weak self] _, change in + // Use the observed value: the updater's properties are main actor-isolated and this handler isn't + guard change.newValue != change.oldValue, let automaticallyChecksForUpdates = change.newValue else { return } Task { @MainActor [weak self] in self?.automaticallyChecksForUpdates = automaticallyChecksForUpdates } @@ -121,8 +121,8 @@ class ObservableUpdater: ObservableObject { lastUpdateCheckDateObservation = updater.observe( \.lastUpdateCheckDate, options: [.initial, .new, .old], - changeHandler: { [weak self] updater, change in - let lastUpdateCheckDate = updater.lastUpdateCheckDate + changeHandler: { [weak self] _, change in + let lastUpdateCheckDate = change.newValue ?? nil Task { @MainActor [weak self] in self?.lastUpdateCheckDate = lastUpdateCheckDate } From 3581e0ed197d02ec6dadfad146d4d6eb1fc2cfae Mon Sep 17 00:00:00 2001 From: Ancil Maxwell Hoffman Date: Tue, 29 Sep 2026 17:04:29 +0200 Subject: [PATCH 3/5] Isolate the deprecated SMJobBless call SMJobBless is deprecated in favor of SMAppService, which uses a different install and approval model and is a separate migration. Keep the one legacy call in a small, clearly marked shim so the known deprecation doesn't warn on every build. Co-Authored-By: Claude Opus 5.5 --- Xcodes/Backend/HelperClient.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Xcodes/Backend/HelperClient.swift b/Xcodes/Backend/HelperClient.swift index 1fb73d8b..f71d8f52 100644 --- a/Xcodes/Backend/HelperClient.swift +++ b/Xcodes/Backend/HelperClient.swift @@ -234,7 +234,7 @@ final class HelperClient { do { let authRef = try authorizationRef(&authRights, nil, [.interactionAllowed, .extendRights, .preAuthorize]) var cfError: Unmanaged? - SMJobBless(kSMDomainSystemLaunchd, machServiceName as CFString, authRef, &cfError) + (SMJobBlessInstaller() as PrivilegedHelperBlessing).bless(label: machServiceName, authorization: authRef, error: &cfError) if let error = cfError?.takeRetainedValue() { throw error } self.connection?.invalidate() @@ -268,6 +268,23 @@ final class HelperClient { } } +/// Installs the privileged helper with SMJobBless, which macOS 13 deprecated in favor of SMAppService. +/// SMAppService uses a different install and approval model (a bundled launch daemon plist, approved in +/// System Settings), so the move is a separate change. Until then the one legacy call lives here, and is +/// reached through a protocol so the known deprecation doesn't warn on every build. +private protocol PrivilegedHelperBlessing { + @discardableResult + func bless(label: String, authorization: AuthorizationRef?, error: inout Unmanaged?) -> Bool +} + +private struct SMJobBlessInstaller: PrivilegedHelperBlessing { + @available(macOS, deprecated: 13.0, message: "Move the privileged helper to SMAppService") + @discardableResult + func bless(label: String, authorization: AuthorizationRef?, error: inout Unmanaged?) -> Bool { + SMJobBless(kSMDomainSystemLaunchd, label as CFString, authorization, &error) + } +} + enum HelperClientError: LocalizedError { case failedToCreateRemoteObjectProxy case message(String) From bc64bdeb402d3b425a20ee35eec38128bdeabdce Mon Sep 17 00:00:00 2001 From: Ancil Maxwell Hoffman Date: Tue, 29 Sep 2026 17:04:29 +0200 Subject: [PATCH 4/5] Run the libfido2 fix script on every build explicitly The script phase has no outputs, so Xcode warned that it runs on every build. That is intended; mark it alwaysOutOfDate ("Based on dependency analysis" unchecked) to say so. Co-Authored-By: Claude Opus 5.5 --- Xcodes.xcodeproj/project.pbxproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index e8e0ad6a..e0375350 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -847,6 +847,7 @@ }; D971F84C2E79102E005F84C9 /* Fix libfido2 structure */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); From 9b572d4fb51898378869e4a436af7e3e3c9a4d22 Mon Sep 17 00:00:00 2001 From: Ancil Maxwell Hoffman Date: Tue, 29 Sep 2026 18:42:17 +0200 Subject: [PATCH 5/5] Document the remaining known build warnings in the README Co-Authored-By: Claude Opus 5.5 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 55fdbec8..9c95112f 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,13 @@ Xcodes.app and CLI is updated, maintained with contributors like yourself. Even You'll need macOS 15.6 Ventura and Xcode 26 in order to build and run Xcodes.app. +### Known build warnings + +The project builds without Swift warnings. Two known items remain: + +- `ld: building for macOS-14.6, but linking with dylib '@rpath/libcbor.0.11.0.dylib' which was built for newer version 15.0`: the prebuilt `libcbor` in LibFido2Swift targets macOS 15 while the app targets 14.6. Resolving it means raising the app's deployment target or rebuilding that dependency for 14.6. +- `SMJobBless` is deprecated in favor of `SMAppService`. The one legacy call is isolated in a marked shim in `HelperClient.swift` until the privileged helper moves to `SMAppService`, which uses a different install and approval model. + `Unxip` and `aria2` must be compiled as a universal binary ``` # compile for Intel