diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 6c75ddd..93f4952 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -139,7 +139,6 @@ E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DA461025FAF7FB002E85EF /* NotificationsView.swift */; }; E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; }; E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; }; - E8EE58C02E1CC2A50003FA9F /* RuntimeArchitecture.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8EE58BF2E1CC2A50003FA9F /* RuntimeArchitecture.swift */; }; E8F44A1E296B4CD7002D6592 /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = E8F44A1D296B4CD7002D6592 /* Path */; }; E8FA00542B5B109800769CE0 /* com.xcodesorg.xcodesapp.Helper in Copy Helper */ = {isa = PBXBuildFile; fileRef = CA9FF8AE2595967A00E47BAF /* com.xcodesorg.xcodesapp.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */ = {isa = PBXBuildFile; productRef = E8FD5726291EE4AC001E004C /* AsyncNetworkService */; }; @@ -342,7 +341,6 @@ E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.swift; sourceTree = ""; }; E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = ""; }; - E8EE58BF2E1CC2A50003FA9F /* RuntimeArchitecture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeArchitecture.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -660,7 +658,6 @@ E8E98A9425D863B100EC89A0 /* InfoPane */ = { isa = PBXGroup; children = ( - E8EE58BF2E1CC2A50003FA9F /* RuntimeArchitecture.swift */, B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */, B0403CF32AD9381D00137C09 /* SDKsView.swift */, B0403CF52AD9849E00137C09 /* CompilersView.swift */, @@ -938,7 +935,6 @@ 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */, 332807412CA5EA820036F691 /* SignInSecurityKeyTouchView.swift in Sources */, CA61A6E0259835580008926E /* Xcode.swift in Sources */, - E8EE58C02E1CC2A50003FA9F /* RuntimeArchitecture.swift in Sources */, CAE4247F259A666100B8B246 /* MainWindow.swift in Sources */, CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */, B0403CF02AD92D7B00137C09 /* ReleaseNotesView.swift in Sources */, diff --git a/Xcodes/Backend/AppState+Runtimes.swift b/Xcodes/Backend/AppState+Runtimes.swift index e2b1149..f9a9864 100644 --- a/Xcodes/Backend/AppState+Runtimes.swift +++ b/Xcodes/Backend/AppState+Runtimes.swift @@ -61,7 +61,22 @@ extension AppState { // only selected xcodes > 16.1 beta 3 can download runtimes via a xcodebuild -downloadPlatform version // only Runtimes coming from cryptexDiskImage can be downloaded via xcodebuild if selectedXcode.version > Version(major: 16, minor: 0, patch: 0) { - downloadRuntimeViaXcodeBuild(runtime: runtime) + + if runtime.architectures?.isAppleSilicon ?? false { + if selectedXcode.version > Version(major: 26, minor: 0, patch: 0) { + downloadRuntimeViaXcodeBuild(runtime: runtime) + } else { + // not supported + Logger.appState.error("Trying to download a runtime we can't download") + DispatchQueue.main.async { + self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: localizeString("Alert.Install.Error.Need.Xcode26")) + } + return + } + + } else { + downloadRuntimeViaXcodeBuild(runtime: runtime) + } } else { // not supported Logger.appState.error("Trying to download a runtime we can't download") @@ -77,7 +92,8 @@ extension AppState { func downloadRuntimeViaXcodeBuild(runtime: DownloadableRuntime) { - let downloadRuntimeTask = Current.shell.downloadRuntime(runtime.platform.shortName, runtime.simulatorVersion.buildUpdate) + let downloadRuntimeTask = Current.shell.downloadRuntime(runtime.platform.shortName, runtime.simulatorVersion.buildUpdate, runtime.architectures?.isAppleSilicon ?? false ? Architecture.arm64.rawValue : nil) + runtimePublishers[runtime.identifier] = Task { [weak self] in guard let self = self else { return } do { @@ -258,7 +274,10 @@ extension AppState { } func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? { - return installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first + return installedRuntimes.filter({ + $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate && + ((runtime.architectures ?? []).isEmpty ? true : + $0.runtimeInfo.supportedArchitectures == runtime.architectures )}).first } func deleteRuntime(runtime: DownloadableRuntime) async throws { diff --git a/Xcodes/Backend/Environment.swift b/Xcodes/Backend/Environment.swift index 61574ce..b515e11 100644 --- a/Xcodes/Backend/Environment.swift +++ b/Xcodes/Backend/Environment.swift @@ -196,7 +196,7 @@ public struct Shell { return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"]) } - public var downloadRuntime: (String, String) -> AsyncThrowingStream = { platform, version in + public var downloadRuntime: (String, String, String?) -> AsyncThrowingStream = { platform, version, architecture in return AsyncThrowingStream { continuation in Task { // Assume progress will not have data races, so we manually opt-out isolation checks. @@ -204,7 +204,7 @@ public struct Shell { progress.kind = .file progress.fileOperationKind = .downloading - let process = Process() + var process = Process() let xcodeBuildPath = Path.root.usr.bin.join("xcodebuild").url process.executableURL = xcodeBuildPath @@ -215,6 +215,13 @@ public struct Shell { "\(version)" ] + if let architecture { + process.arguments?.append(contentsOf: [ + "-architectureVariant", + "\(architecture)" + ]) + } + let stdOutPipe = Pipe() process.standardOutput = stdOutPipe let stdErrPipe = Pipe() diff --git a/Xcodes/Frontend/InfoPane/PlatformsView.swift b/Xcodes/Frontend/InfoPane/PlatformsView.swift index 6e0b9a1..cf69c7e 100644 --- a/Xcodes/Frontend/InfoPane/PlatformsView.swift +++ b/Xcodes/Frontend/InfoPane/PlatformsView.swift @@ -11,7 +11,7 @@ import XcodesKit struct PlatformsView: View { @EnvironmentObject var appState: AppState - @AppStorage("selectedRuntimeArchitecture") private var selectedRuntimeArchitecture: RuntimeArchitecture = .arm64 + @AppStorage("selectedRuntimeArchitecture") private var selectedRuntimeArchitecture: Architecture = .arm64 let xcode: Xcode @@ -22,7 +22,7 @@ struct PlatformsView: View { appState.downloadableRuntimes.filter { $0.sdkBuildUpdate?.contains(sdkBuild) ?? false && ($0.architectures?.isEmpty ?? true || - $0.architectures?.contains(selectedRuntimeArchitecture.rawValue) ?? false) + $0.architectures?.contains(selectedRuntimeArchitecture) ?? false) } } @@ -43,10 +43,10 @@ struct PlatformsView: View { } label: { switch selectedRuntimeArchitecture { case .arm64: - Label(selectedRuntimeArchitecture.displayValue, systemImage: "m4.button.horizontal") + Label(selectedRuntimeArchitecture.rawValue, systemImage: "m4.button.horizontal") .labelStyle(.trailingIcon) case .x86_64: - Label(selectedRuntimeArchitecture.displayValue, systemImage: "cpu.fill") + Label(selectedRuntimeArchitecture.rawValue, systemImage: "cpu.fill") .labelStyle(.trailingIcon) } } @@ -74,21 +74,21 @@ struct PlatformsView: View { Text("\(runtime.visibleIdentifier)") .font(.headline) ForEach(runtime.architectures ?? [], id: \.self) { architecture in - TagView(text: architecture) + TagView(text: architecture.rawValue) } pathIfAvailable(xcode: xcode, runtime: runtime) - - if runtime.installState == .notInstalled { - // TODO: Update the downloadableRuntimes with the appropriate installState so we don't have to check path awkwardly - if appState.runtimeInstallPath(xcode: xcode, runtime: runtime) != nil { - EmptyView() - } else { - HStack { - Spacer() - DownloadRuntimeButton(runtime: runtime) - } - } - } + + if runtime.installState == .notInstalled { + // TODO: Update the downloadableRuntimes with the appropriate installState so we don't have to check path awkwardly + if appState.runtimeInstallPath(xcode: xcode, runtime: runtime) != nil { + EmptyView() + } else { + HStack { + Spacer() + DownloadRuntimeButton(runtime: runtime) + } + } + } Spacer() Text(runtime.downloadFileSizeString) diff --git a/Xcodes/Frontend/InfoPane/RuntimeArchitecture.swift b/Xcodes/Frontend/InfoPane/RuntimeArchitecture.swift deleted file mode 100644 index abbdf45..0000000 --- a/Xcodes/Frontend/InfoPane/RuntimeArchitecture.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// RuntimeArchitecture.swift -// Xcodes -// -// Created by Matt Kiazyk on 2025-07-07. -// - -enum RuntimeArchitecture: String, CaseIterable, Identifiable { - case arm64 - case x86_64 - - var id: Self { self } - - var displayValue: String { - return rawValue - } -} diff --git a/Xcodes/Resources/Localizable.xcstrings b/Xcodes/Resources/Localizable.xcstrings index 1f6e687..93196fa 100644 --- a/Xcodes/Resources/Localizable.xcstrings +++ b/Xcodes/Resources/Localizable.xcstrings @@ -2083,6 +2083,17 @@ } } }, + "Alert.Install.Error.Need.Xcode26" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apple supports downloading Apple Silicon runtimes only when Xcode 26+ is selected. Please Select and try downloading again or download the universal build." + } + } + } + }, "Alert.Install.Error.Title" : { "comment" : "Install", "extractionState" : "manual", diff --git a/Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/CoreSimulatorImage.swift b/Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/CoreSimulatorImage.swift index cf537d4..3ea12db 100644 --- a/Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/CoreSimulatorImage.swift +++ b/Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/CoreSimulatorImage.swift @@ -37,8 +37,10 @@ public struct CoreSimulatorImage: Decodable, Identifiable, Equatable { public struct CoreSimulatorRuntimeInfo: Decodable { public let build: String + public let supportedArchitectures: [Architecture]? - public init(build: String) { + public init(build: String, supportedArchitectures: [Architecture]? = nil) { self.build = build + self.supportedArchitectures = supportedArchitectures } } diff --git a/Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/Runtimes.swift b/Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/Runtimes.swift index 357fa23..581b6c0 100644 --- a/Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/Runtimes.swift +++ b/Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/Runtimes.swift @@ -12,7 +12,7 @@ public struct DownloadableRuntime: Codable, Identifiable, Hashable { public let category: Category public let simulatorVersion: SimulatorVersion public let source: String? - public let architectures: [String]? + public let architectures: [Architecture]? public let dictionaryVersion: Int public let contentType: ContentType public let platform: Platform @@ -170,6 +170,7 @@ public struct InstalledRuntime: Decodable { let state: String let version: String let sizeBytes: Int? + let supportedArchitectures: [Architecture]? } extension InstalledRuntime { diff --git a/Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Architecture.swift b/Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Architecture.swift index 2721bc6..78ac5e2 100644 --- a/Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Architecture.swift +++ b/Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Architecture.swift @@ -15,10 +15,6 @@ public enum Architecture: String, Codable, Equatable, Hashable, Identifiable { case arm64 = "arm64" /// The X86\_64 architecture (64-bit Intel) case x86_64 = "x86_64" - /// The i386 architecture (32-bit Intel) - case i386 = "i386" - /// The PowerPC architecture (Motorola) - case powerPC = "ppc" } extension Array where Element == Architecture {