Skip to content

Commit 08738d6

Browse files
authored
Merge pull request #743 from XcodesOrg/XcodeArchitectures
Support Showing and Downloading Multiple Xcode Architectures.
2 parents a434d26 + 472e36e commit 08738d6

40 files changed

+1031
-138
lines changed

Xcodes.xcodeproj/project.pbxproj

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */; };
4949
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */; };
5050
CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8652595130600E47BAF /* View+IsHidden.swift */; };
51-
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */ = {isa = PBXBuildFile; productRef = CA9FF86C25951C6E00E47BAF /* XCModel */; };
5251
CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF876259528CC00E47BAF /* Version+XcodeReleases.swift */; };
5352
CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF87A2595293E00E47BAF /* DataSource.swift */; };
5453
CA9FF88125955C7000E47BAF /* AvailableXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF88025955C7000E47BAF /* AvailableXcode.swift */; };
@@ -140,7 +139,6 @@
140139
E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DA461025FAF7FB002E85EF /* NotificationsView.swift */; };
141140
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; };
142141
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; };
143-
E8EE58C02E1CC2A50003FA9F /* RuntimeArchitecture.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8EE58BF2E1CC2A50003FA9F /* RuntimeArchitecture.swift */; };
144142
E8F44A1E296B4CD7002D6592 /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = E8F44A1D296B4CD7002D6592 /* Path */; };
145143
E8FA00542B5B109800769CE0 /* com.xcodesorg.xcodesapp.Helper in Copy Helper */ = {isa = PBXBuildFile; fileRef = CA9FF8AE2595967A00E47BAF /* com.xcodesorg.xcodesapp.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
146144
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */ = {isa = PBXBuildFile; productRef = E8FD5726291EE4AC001E004C /* AsyncNetworkService */; };
@@ -343,7 +341,6 @@
343341
E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.swift; sourceTree = "<group>"; };
344342
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
345343
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; };
346-
E8EE58BF2E1CC2A50003FA9F /* RuntimeArchitecture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeArchitecture.swift; sourceTree = "<group>"; };
347344
/* End PBXFileReference section */
348345

349346
/* Begin PBXFrameworksBuildPhase section */
@@ -363,7 +360,6 @@
363360
CABFA9E42592F08E00380FEE /* Version in Frameworks */,
364361
CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */,
365362
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */,
366-
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */,
367363
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */,
368364
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */,
369365
E862D43B2CC8B26F00BAA376 /* SRP in Frameworks */,
@@ -662,7 +658,6 @@
662658
E8E98A9425D863B100EC89A0 /* InfoPane */ = {
663659
isa = PBXGroup;
664660
children = (
665-
E8EE58BF2E1CC2A50003FA9F /* RuntimeArchitecture.swift */,
666661
B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */,
667662
B0403CF32AD9381D00137C09 /* SDKsView.swift */,
668663
B0403CF52AD9849E00137C09 /* CompilersView.swift */,
@@ -726,7 +721,6 @@
726721
CABFA9ED2592F0CC00380FEE /* SwiftSoup */,
727722
CABFA9F72592F0F900380FEE /* KeychainAccess */,
728723
CABFA9FC2592F13300380FEE /* LegibleError */,
729-
CA9FF86C25951C6E00E47BAF /* XCModel */,
730724
CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */,
731725
E689540225BE8C64000EBCEA /* DockProgress */,
732726
E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
@@ -816,7 +810,6 @@
816810
CABFA9EC2592F0CC00380FEE /* XCRemoteSwiftPackageReference "SwiftSoup" */,
817811
CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */,
818812
CABFA9FB2592F13300380FEE /* XCRemoteSwiftPackageReference "LegibleError" */,
819-
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */,
820813
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */,
821814
CAC28186259EE27200B8AB0B /* XCRemoteSwiftPackageReference "CombineExpectations" */,
822815
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
@@ -942,7 +935,6 @@
942935
53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */,
943936
332807412CA5EA820036F691 /* SignInSecurityKeyTouchView.swift in Sources */,
944937
CA61A6E0259835580008926E /* Xcode.swift in Sources */,
945-
E8EE58C02E1CC2A50003FA9F /* RuntimeArchitecture.swift in Sources */,
946938
CAE4247F259A666100B8B246 /* MainWindow.swift in Sources */,
947939
CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */,
948940
B0403CF02AD92D7B00137C09 /* ReleaseNotesView.swift in Sources */,
@@ -1504,14 +1496,6 @@
15041496
minimumVersion = 0.1.4;
15051497
};
15061498
};
1507-
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */ = {
1508-
isa = XCRemoteSwiftPackageReference;
1509-
repositoryURL = "https://github.com/xcodereleases/data";
1510-
requirement = {
1511-
branch = main;
1512-
kind = branch;
1513-
};
1514-
};
15151499
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */ = {
15161500
isa = XCRemoteSwiftPackageReference;
15171501
repositoryURL = "https://github.com/RobotsAndPencils/ErrorHandling";
@@ -1607,11 +1591,6 @@
16071591
isa = XCSwiftPackageProductDependency;
16081592
productName = LibFido2Swift;
16091593
};
1610-
CA9FF86C25951C6E00E47BAF /* XCModel */ = {
1611-
isa = XCSwiftPackageProductDependency;
1612-
package = CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */;
1613-
productName = XCModel;
1614-
};
16151594
CAA1CB2C255A5262003FD669 /* AppleAPI */ = {
16161595
isa = XCSwiftPackageProductDependency;
16171596
productName = AppleAPI;

Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 0 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Xcodes/Backend/AppState+Install.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ extension AppState {
502502
self.allXcodes[index].installState = .installing(step)
503503

504504
let xcode = self.allXcodes[index]
505-
Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal)
505+
Current.notificationManager.scheduleNotification(title: xcode.version.major.description + "." + xcode.version.appleDescription, body: step.description, category: .normal)
506506
}
507507
}
508508

Xcodes/Backend/AppState+Runtimes.swift

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,22 @@ extension AppState {
6161
// only selected xcodes > 16.1 beta 3 can download runtimes via a xcodebuild -downloadPlatform version
6262
// only Runtimes coming from cryptexDiskImage can be downloaded via xcodebuild
6363
if selectedXcode.version > Version(major: 16, minor: 0, patch: 0) {
64-
downloadRuntimeViaXcodeBuild(runtime: runtime)
64+
65+
if runtime.architectures?.isAppleSilicon ?? false {
66+
if selectedXcode.version > Version(major: 26, minor: 0, patch: 0) {
67+
downloadRuntimeViaXcodeBuild(runtime: runtime)
68+
} else {
69+
// not supported
70+
Logger.appState.error("Trying to download a runtime we can't download")
71+
DispatchQueue.main.async {
72+
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: localizeString("Alert.Install.Error.Need.Xcode26"))
73+
}
74+
return
75+
}
76+
77+
} else {
78+
downloadRuntimeViaXcodeBuild(runtime: runtime)
79+
}
6580
} else {
6681
// not supported
6782
Logger.appState.error("Trying to download a runtime we can't download")
@@ -77,7 +92,8 @@ extension AppState {
7792

7893
func downloadRuntimeViaXcodeBuild(runtime: DownloadableRuntime) {
7994

80-
let downloadRuntimeTask = Current.shell.downloadRuntime(runtime.platform.shortName, runtime.simulatorVersion.buildUpdate)
95+
let downloadRuntimeTask = Current.shell.downloadRuntime(runtime.platform.shortName, runtime.simulatorVersion.buildUpdate, runtime.architectures?.isAppleSilicon ?? false ? Architecture.arm64.rawValue : nil)
96+
8197
runtimePublishers[runtime.identifier] = Task { [weak self] in
8298
guard let self = self else { return }
8399
do {
@@ -258,7 +274,10 @@ extension AppState {
258274
}
259275

260276
func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? {
261-
return installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first
277+
return installedRuntimes.filter({
278+
$0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate &&
279+
((runtime.architectures ?? []).isEmpty ? true :
280+
$0.runtimeInfo.supportedArchitectures == runtime.architectures )}).first
262281
}
263282

264283
func deleteRuntime(runtime: DownloadableRuntime) async throws {

Xcodes/Backend/AppState+Update.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import Foundation
33
import Path
44
import Version
55
import SwiftSoup
6-
import struct XCModel.Xcode
76
import AppleAPI
87
import XcodesKit
98

@@ -211,8 +210,8 @@ extension AppState {
211210
private func xcodeReleases() -> AnyPublisher<[AvailableXcode], Error> {
212211
Current.network.dataTask(with: URLRequest(url: URL(string: "https://xcodereleases.com/data.json")!))
213212
.map(\.data)
214-
.decode(type: [XCModel.Xcode].self, decoder: JSONDecoder())
215-
.map { xcReleasesXcodes in
213+
.decode(type: [XcodeRelease].self, decoder: JSONDecoder())
214+
.map { xcReleasesXcodes in
216215
let xcodes = xcReleasesXcodes.compactMap { xcReleasesXcode -> AvailableXcode? in
217216
guard
218217
let downloadURL = xcReleasesXcode.links?.download?.url,
@@ -233,7 +232,8 @@ extension AppState {
233232
requiredMacOSVersion: xcReleasesXcode.requires,
234233
releaseNotesURL: xcReleasesXcode.links?.notes?.url,
235234
sdks: xcReleasesXcode.sdks,
236-
compilers: xcReleasesXcode.compilers
235+
compilers: xcReleasesXcode.compilers,
236+
architectures: xcReleasesXcode.architectures
237237
)
238238
}
239239
return xcodes

Xcodes/Backend/AppState.swift

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ enum PreferenceKey: String {
2626
case xcodeListCategory
2727
case allowedMajorVersions
2828
case hideSupportXcodes
29+
case xcodeListArchitectures
2930

3031
func isManaged() -> Bool { UserDefaults.standard.objectIsForced(forKey: self.rawValue) }
3132
}
@@ -146,7 +147,7 @@ class AppState: ObservableObject {
146147
// MARK: - Publisher Cancellables
147148

148149
var cancellables = Set<AnyCancellable>()
149-
private var installationPublishers: [Version: AnyCancellable] = [:]
150+
private var installationPublishers: [XcodeID: AnyCancellable] = [:]
150151
internal var runtimePublishers: [String: Task<(), any Error>] = [:]
151152
private var selectPublisher: AnyCancellable?
152153
private var uninstallPublisher: AnyCancellable?
@@ -523,8 +524,8 @@ class AppState: ObservableObject {
523524

524525
// MARK: - Install
525526

526-
func checkMinVersionAndInstall(id: Xcode.ID) {
527-
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
527+
func checkMinVersionAndInstall(id: XcodeID) {
528+
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
528529

529530
// Check to see if users macOS is supported
530531
if let requiredMacOSVersion = availableXcode.requiredMacOSVersion {
@@ -550,8 +551,8 @@ class AppState: ObservableObject {
550551
return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion)
551552
}
552553

553-
func install(id: Xcode.ID) {
554-
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
554+
func install(id: XcodeID) {
555+
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
555556

556557
installationPublishers[id] = signInIfNeeded()
557558
.handleEvents(
@@ -626,7 +627,7 @@ class AppState: ObservableObject {
626627
/// Skips using the username/password to log in to Apple, and simply gets a Auth Cookie used in downloading
627628
/// As of Nov 2022 this was returning a 403 forbidden
628629
func installWithoutLogin(id: Xcode.ID) {
629-
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
630+
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
630631

631632
installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
632633
.receive(on: DispatchQueue.main)
@@ -649,7 +650,7 @@ class AppState: ObservableObject {
649650
}
650651

651652
func cancelInstall(id: Xcode.ID) {
652-
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
653+
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
653654

654655
// Cancel the publisher
655656
installationPublishers[id] = nil
@@ -767,7 +768,7 @@ class AppState: ObservableObject {
767768
config.allowsRunningApplicationSubstitution = false
768769
NSWorkspace.shared.openApplication(at: path.url, configuration: config)
769770
default:
770-
Logger.appState.error("\(xcode.id) is not installed")
771+
Logger.appState.error("\(xcode.id.version) is not installed")
771772
return
772773
}
773774
}
@@ -863,15 +864,15 @@ class AppState: ObservableObject {
863864
// If build metadata matches exactly, replace the available version with the installed version.
864865
// This should handle Apple versions from /downloads/more which don't have build metadata identifiers.
865866
if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) {
866-
adjustedAvailableXcodes[index].version = installedXcode.version
867+
adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
867868
}
868869
// If an installed version is the same as one that's listed online which doesn't have build metadata, replace it with the installed version
869870
// Not all prerelease Apple versions available online include build metadata
870871
else if let index = adjustedAvailableXcodes.firstIndex(where: { availableXcode in
871872
availableXcode.version.isEquivalent(to: installedXcode.version) &&
872873
availableXcode.version.buildMetadataIdentifiers.isEmpty
873874
}) {
874-
adjustedAvailableXcodes[index].version = installedXcode.version
875+
adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
875876
}
876877
}
877878
}
@@ -888,14 +889,21 @@ class AppState: ObservableObject {
888889
// Include this version if there's only one with this build identifier
889890
return availableXcodesWithIdenticalBuildIdentifiers.count == 1 ||
890891
// Or if there's more than one with this build identifier and this is the release version
891-
availableXcodesWithIdenticalBuildIdentifiers.count > 1 && availableXcode.version.prereleaseIdentifiers.isEmpty
892-
}
892+
893+
availableXcodesWithIdenticalBuildIdentifiers.count > 1 && (availableXcode.version.prereleaseIdentifiers.isEmpty || availableXcode.architectures?.count ?? 0 != 0)
894+
}
893895
.map { availableXcode -> Xcode in
894896
let installedXcode = installedXcodes.first(where: { installedXcode in
895-
availableXcode.version.isEquivalent(to: installedXcode.version)
897+
// if we want to have only specific Xcodes as selected instead of the Architecture Equivalent.
898+
// if availableXcode.architectures == nil {
899+
// return availableXcode.version.isEquivalent(to: installedXcode.version)
900+
// } else {
901+
// return availableXcode.xcodeID == installedXcode.xcodeID
902+
// }
903+
return availableXcode.version.isEquivalent(to: installedXcode.version)
896904
})
897905

898-
let identicalBuilds: [Version]
906+
let identicalBuilds: [XcodeID]
899907
let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes
900908
.filter {
901909
return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers &&
@@ -905,13 +913,13 @@ class AppState: ObservableObject {
905913
}
906914
// If this is the release version, add the identical builds to it
907915
if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty {
908-
identicalBuilds = [availableXcode.version] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.version)
916+
identicalBuilds = [availableXcode.xcodeID] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.xcodeID)
909917
} else {
910918
identicalBuilds = []
911919
}
912920

913921
// If the existing install state is "installing", keep it
914-
let existingXcodeInstallState = allXcodes.first { $0.version == availableXcode.version && $0.installState.installing }?.installState
922+
let existingXcodeInstallState = allXcodes.first { $0.id == availableXcode.xcodeID && $0.installState.installing }?.installState
915923
// Otherwise, determine it from whether there's an installed Xcode
916924
let defaultXcodeInstallState: XcodeInstallState = installedXcode.map { .installed($0.path) } ?? .notInstalled
917925

@@ -926,7 +934,8 @@ class AppState: ObservableObject {
926934
releaseDate: availableXcode.releaseDate,
927935
sdks: availableXcode.sdks,
928936
compilers: availableXcode.compilers,
929-
downloadFileSize: availableXcode.fileSize
937+
downloadFileSize: availableXcode.fileSize,
938+
architectures: availableXcode.architectures
930939
)
931940
}
932941

0 commit comments

Comments
 (0)