Skip to content

Commit debc41f

Browse files
committed
support downloading individual xcode architecture versions
1 parent 4b9d86b commit debc41f

8 files changed

Lines changed: 52 additions & 17 deletions

File tree

Xcodes/Backend/AppState.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ class AppState: ObservableObject {
525525
// MARK: - Install
526526

527527
func checkMinVersionAndInstall(id: XcodeID) {
528-
guard let availableXcode = availableXcodes.first(where: { $0.version == id.version }) else { return }
528+
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
529529

530530
// Check to see if users macOS is supported
531531
if let requiredMacOSVersion = availableXcode.requiredMacOSVersion {
@@ -552,7 +552,7 @@ class AppState: ObservableObject {
552552
}
553553

554554
func install(id: XcodeID) {
555-
guard let availableXcode = availableXcodes.first(where: { $0.version == id.version }) else { return }
555+
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
556556

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

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

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

655655
// Cancel the publisher
656656
installationPublishers[id] = nil
@@ -894,7 +894,7 @@ class AppState: ObservableObject {
894894
}
895895
.map { availableXcode -> Xcode in
896896
let installedXcode = installedXcodes.first(where: { installedXcode in
897-
availableXcode.version.isEquivalent(to: installedXcode.version)
897+
availableXcode.version.isEquivalent(to: installedXcode.version)
898898
})
899899

900900
let identicalBuilds: [XcodeID]

Xcodes/Backend/InstalledXcode.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public struct InstalledXcode: Equatable {
3535
else if infoPlist.bundleIconName == "XcodeBeta", !prereleaseIdentifiers.contains("beta") {
3636
prereleaseIdentifiers = ["beta"]
3737
}
38+
39+
// need:
40+
// lipo -archs /Applications/Xcode-26.0.0-Beta.3.app/Contents/MacOS/Xcode
3841

3942
let version = Version(major: bundleVersion.major,
4043
minor: bundleVersion.minor,

Xcodes/Backend/Xcode.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ public struct XcodeID: Codable, Hashable, Identifiable {
1717
self.version = version
1818
self.architectures = architectures
1919
}
20+
21+
public var architectureString: String {
22+
switch architectures {
23+
case .some(let architectures):
24+
if architectures.isAppleSilicon {
25+
return "Apple Silicon"
26+
} else {
27+
return "Universal"
28+
}
29+
default: return "Universal"
30+
}
31+
}
2032
}
2133

2234
struct Xcode: Identifiable, CustomStringConvertible {

Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ struct NotInstalledStateButtons: View {
2020
Button {
2121
appState.checkMinVersionAndInstall(id: id)
2222
} label: {
23-
Text("Install") .help("Install")
23+
if id.architectures?.isAppleSilicon ?? false {
24+
Text("Install Apple Silicon").help("Install")
25+
} else {
26+
Text("Install Universal").help("Install")
27+
}
2428
}
2529

2630
if let size = downloadFileSizeString {

Xcodes/Frontend/XcodeList/XcodeListView.swift

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,8 @@ struct XcodeListView: View {
3030
xcodes = appState.allXcodes.filter { $0.version.isPrerelease }
3131
}
3232

33-
switch architecture {
34-
case .appleSilicon:
33+
if architecture == .appleSilicon {
3534
xcodes = xcodes.filter { $0.architectures == [.arm64] }
36-
case .universal:
37-
xcodes = xcodes.filter {
38-
if let architectures = $0.architectures {
39-
return architectures.contains(.x86_64) // we're assuming that if architectures contains x86 then it's universal
40-
} else {
41-
return true
42-
}
43-
}
4435
}
4536

4637

Xcodes/Frontend/XcodeList/XcodeListViewRow.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ struct XcodeListViewRow: View {
2424
.accessibility(value: Text(xcode.identicalBuilds.map(\.version.appleDescription).joined(separator: ", ")))
2525
.help("IdenticalBuilds.help")
2626
}
27+
28+
if xcode.architectures?.isAppleSilicon ?? false {
29+
Image(systemName: "m4.button.horizontal")
30+
.font(.subheadline)
31+
.foregroundColor(.secondary)
32+
.accessibility(label: Text("AppleSilicon"))
33+
.help("AppleSilicon.help")
34+
}
2735
}
2836

2937
if case let .installed(path) = xcode.installState {

Xcodes/Resources/Localizable.xcstrings

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4449,6 +4449,9 @@
44494449
},
44504450
"AppleSilicon" : {
44514451

4452+
},
4453+
"AppleSilicon.help" : {
4454+
44524455
},
44534456
"AppUpdates" : {
44544457
"localizations" : {
@@ -10480,6 +10483,12 @@
1048010483
}
1048110484
}
1048210485
}
10486+
},
10487+
"Install Apple Silicon" : {
10488+
10489+
},
10490+
"Install Universal" : {
10491+
1048310492
},
1048410493
"InstallationError.CodesignVerifyFailed" : {
1048510494
"extractionState" : "manual",

Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Architecture.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import Foundation
99

1010
/// The name of an Architecture.
11-
public enum Architecture: String, Codable, Equatable, Hashable {
11+
public enum Architecture: String, Codable, Equatable, Hashable, Identifiable {
12+
public var id: Self { self }
13+
1214
/// The Arm64 architecture (Apple Silicon)
1315
case arm64 = "arm64"
1416
/// The X86\_64 architecture (64-bit Intel)
@@ -18,3 +20,9 @@ public enum Architecture: String, Codable, Equatable, Hashable {
1820
/// The PowerPC architecture (Motorola)
1921
case powerPC = "ppc"
2022
}
23+
24+
extension Array where Element == Architecture {
25+
public var isAppleSilicon: Bool {
26+
self == [.arm64]
27+
}
28+
}

0 commit comments

Comments
 (0)