mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
Support showing multiple architectures
This commit is contained in:
parent
a434d26921
commit
4b9d86b22e
31 changed files with 459 additions and 85 deletions
|
|
@ -48,7 +48,6 @@
|
||||||
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */; };
|
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */; };
|
||||||
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */; };
|
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */; };
|
||||||
CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8652595130600E47BAF /* View+IsHidden.swift */; };
|
CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8652595130600E47BAF /* View+IsHidden.swift */; };
|
||||||
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */ = {isa = PBXBuildFile; productRef = CA9FF86C25951C6E00E47BAF /* XCModel */; };
|
|
||||||
CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF876259528CC00E47BAF /* Version+XcodeReleases.swift */; };
|
CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF876259528CC00E47BAF /* Version+XcodeReleases.swift */; };
|
||||||
CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF87A2595293E00E47BAF /* DataSource.swift */; };
|
CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF87A2595293E00E47BAF /* DataSource.swift */; };
|
||||||
CA9FF88125955C7000E47BAF /* AvailableXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF88025955C7000E47BAF /* AvailableXcode.swift */; };
|
CA9FF88125955C7000E47BAF /* AvailableXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF88025955C7000E47BAF /* AvailableXcode.swift */; };
|
||||||
|
|
@ -363,7 +362,6 @@
|
||||||
CABFA9E42592F08E00380FEE /* Version in Frameworks */,
|
CABFA9E42592F08E00380FEE /* Version in Frameworks */,
|
||||||
CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */,
|
CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */,
|
||||||
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */,
|
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */,
|
||||||
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */,
|
|
||||||
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */,
|
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */,
|
||||||
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */,
|
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */,
|
||||||
E862D43B2CC8B26F00BAA376 /* SRP in Frameworks */,
|
E862D43B2CC8B26F00BAA376 /* SRP in Frameworks */,
|
||||||
|
|
@ -726,7 +724,6 @@
|
||||||
CABFA9ED2592F0CC00380FEE /* SwiftSoup */,
|
CABFA9ED2592F0CC00380FEE /* SwiftSoup */,
|
||||||
CABFA9F72592F0F900380FEE /* KeychainAccess */,
|
CABFA9F72592F0F900380FEE /* KeychainAccess */,
|
||||||
CABFA9FC2592F13300380FEE /* LegibleError */,
|
CABFA9FC2592F13300380FEE /* LegibleError */,
|
||||||
CA9FF86C25951C6E00E47BAF /* XCModel */,
|
|
||||||
CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */,
|
CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */,
|
||||||
E689540225BE8C64000EBCEA /* DockProgress */,
|
E689540225BE8C64000EBCEA /* DockProgress */,
|
||||||
E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
|
E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
|
||||||
|
|
@ -816,7 +813,6 @@
|
||||||
CABFA9EC2592F0CC00380FEE /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
CABFA9EC2592F0CC00380FEE /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||||
CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||||
CABFA9FB2592F13300380FEE /* XCRemoteSwiftPackageReference "LegibleError" */,
|
CABFA9FB2592F13300380FEE /* XCRemoteSwiftPackageReference "LegibleError" */,
|
||||||
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */,
|
|
||||||
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */,
|
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */,
|
||||||
CAC28186259EE27200B8AB0B /* XCRemoteSwiftPackageReference "CombineExpectations" */,
|
CAC28186259EE27200B8AB0B /* XCRemoteSwiftPackageReference "CombineExpectations" */,
|
||||||
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
|
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
|
||||||
|
|
@ -1504,14 +1500,6 @@
|
||||||
minimumVersion = 0.1.4;
|
minimumVersion = 0.1.4;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/xcodereleases/data";
|
|
||||||
requirement = {
|
|
||||||
branch = main;
|
|
||||||
kind = branch;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */ = {
|
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/RobotsAndPencils/ErrorHandling";
|
repositoryURL = "https://github.com/RobotsAndPencils/ErrorHandling";
|
||||||
|
|
@ -1607,11 +1595,6 @@
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = LibFido2Swift;
|
productName = LibFido2Swift;
|
||||||
};
|
};
|
||||||
CA9FF86C25951C6E00E47BAF /* XCModel */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */;
|
|
||||||
productName = XCModel;
|
|
||||||
};
|
|
||||||
CAA1CB2C255A5262003FD669 /* AppleAPI */ = {
|
CAA1CB2C255A5262003FD669 /* AppleAPI */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = AppleAPI;
|
productName = AppleAPI;
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,6 @@
|
||||||
"version": "0.6.0"
|
"version": "0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"package": "XcodeReleases",
|
|
||||||
"repositoryURL": "https://github.com/xcodereleases/data",
|
|
||||||
"state": {
|
|
||||||
"branch": "main",
|
|
||||||
"revision": "a43ad89e536d7a3da525fcc23fb182c37b756ecc",
|
|
||||||
"version": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"package": "DockProgress",
|
"package": "DockProgress",
|
||||||
"repositoryURL": "https://github.com/sindresorhus/DockProgress",
|
"repositoryURL": "https://github.com/sindresorhus/DockProgress",
|
||||||
|
|
|
||||||
|
|
@ -502,7 +502,7 @@ extension AppState {
|
||||||
self.allXcodes[index].installState = .installing(step)
|
self.allXcodes[index].installState = .installing(step)
|
||||||
|
|
||||||
let xcode = self.allXcodes[index]
|
let xcode = self.allXcodes[index]
|
||||||
Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal)
|
Current.notificationManager.scheduleNotification(title: xcode.version.major.description + "." + xcode.version.appleDescription, body: step.description, category: .normal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import Foundation
|
||||||
import Path
|
import Path
|
||||||
import Version
|
import Version
|
||||||
import SwiftSoup
|
import SwiftSoup
|
||||||
import struct XCModel.Xcode
|
|
||||||
import AppleAPI
|
import AppleAPI
|
||||||
import XcodesKit
|
import XcodesKit
|
||||||
|
|
||||||
|
|
@ -211,8 +210,8 @@ extension AppState {
|
||||||
private func xcodeReleases() -> AnyPublisher<[AvailableXcode], Error> {
|
private func xcodeReleases() -> AnyPublisher<[AvailableXcode], Error> {
|
||||||
Current.network.dataTask(with: URLRequest(url: URL(string: "https://xcodereleases.com/data.json")!))
|
Current.network.dataTask(with: URLRequest(url: URL(string: "https://xcodereleases.com/data.json")!))
|
||||||
.map(\.data)
|
.map(\.data)
|
||||||
.decode(type: [XCModel.Xcode].self, decoder: JSONDecoder())
|
.decode(type: [XcodeRelease].self, decoder: JSONDecoder())
|
||||||
.map { xcReleasesXcodes in
|
.map { xcReleasesXcodes in
|
||||||
let xcodes = xcReleasesXcodes.compactMap { xcReleasesXcode -> AvailableXcode? in
|
let xcodes = xcReleasesXcodes.compactMap { xcReleasesXcode -> AvailableXcode? in
|
||||||
guard
|
guard
|
||||||
let downloadURL = xcReleasesXcode.links?.download?.url,
|
let downloadURL = xcReleasesXcode.links?.download?.url,
|
||||||
|
|
@ -233,7 +232,8 @@ extension AppState {
|
||||||
requiredMacOSVersion: xcReleasesXcode.requires,
|
requiredMacOSVersion: xcReleasesXcode.requires,
|
||||||
releaseNotesURL: xcReleasesXcode.links?.notes?.url,
|
releaseNotesURL: xcReleasesXcode.links?.notes?.url,
|
||||||
sdks: xcReleasesXcode.sdks,
|
sdks: xcReleasesXcode.sdks,
|
||||||
compilers: xcReleasesXcode.compilers
|
compilers: xcReleasesXcode.compilers,
|
||||||
|
architectures: xcReleasesXcode.architectures
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return xcodes
|
return xcodes
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ enum PreferenceKey: String {
|
||||||
case xcodeListCategory
|
case xcodeListCategory
|
||||||
case allowedMajorVersions
|
case allowedMajorVersions
|
||||||
case hideSupportXcodes
|
case hideSupportXcodes
|
||||||
|
case xcodeListArchitectures
|
||||||
|
|
||||||
func isManaged() -> Bool { UserDefaults.standard.objectIsForced(forKey: self.rawValue) }
|
func isManaged() -> Bool { UserDefaults.standard.objectIsForced(forKey: self.rawValue) }
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +147,7 @@ class AppState: ObservableObject {
|
||||||
// MARK: - Publisher Cancellables
|
// MARK: - Publisher Cancellables
|
||||||
|
|
||||||
var cancellables = Set<AnyCancellable>()
|
var cancellables = Set<AnyCancellable>()
|
||||||
private var installationPublishers: [Version: AnyCancellable] = [:]
|
private var installationPublishers: [XcodeID: AnyCancellable] = [:]
|
||||||
internal var runtimePublishers: [String: Task<(), any Error>] = [:]
|
internal var runtimePublishers: [String: Task<(), any Error>] = [:]
|
||||||
private var selectPublisher: AnyCancellable?
|
private var selectPublisher: AnyCancellable?
|
||||||
private var uninstallPublisher: AnyCancellable?
|
private var uninstallPublisher: AnyCancellable?
|
||||||
|
|
@ -523,8 +524,8 @@ class AppState: ObservableObject {
|
||||||
|
|
||||||
// MARK: - Install
|
// MARK: - Install
|
||||||
|
|
||||||
func checkMinVersionAndInstall(id: Xcode.ID) {
|
func checkMinVersionAndInstall(id: XcodeID) {
|
||||||
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
|
guard let availableXcode = availableXcodes.first(where: { $0.version == id.version }) else { return }
|
||||||
|
|
||||||
// Check to see if users macOS is supported
|
// Check to see if users macOS is supported
|
||||||
if let requiredMacOSVersion = availableXcode.requiredMacOSVersion {
|
if let requiredMacOSVersion = availableXcode.requiredMacOSVersion {
|
||||||
|
|
@ -550,8 +551,8 @@ class AppState: ObservableObject {
|
||||||
return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion)
|
return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func install(id: Xcode.ID) {
|
func install(id: XcodeID) {
|
||||||
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
|
guard let availableXcode = availableXcodes.first(where: { $0.version == id.version }) else { return }
|
||||||
|
|
||||||
installationPublishers[id] = signInIfNeeded()
|
installationPublishers[id] = signInIfNeeded()
|
||||||
.handleEvents(
|
.handleEvents(
|
||||||
|
|
@ -626,7 +627,7 @@ class AppState: ObservableObject {
|
||||||
/// Skips using the username/password to log in to Apple, and simply gets a Auth Cookie used in downloading
|
/// Skips using the username/password to log in to Apple, and simply gets a Auth Cookie used in downloading
|
||||||
/// As of Nov 2022 this was returning a 403 forbidden
|
/// As of Nov 2022 this was returning a 403 forbidden
|
||||||
func installWithoutLogin(id: Xcode.ID) {
|
func installWithoutLogin(id: Xcode.ID) {
|
||||||
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
|
guard let availableXcode = availableXcodes.first(where: { $0.version == id.version }) else { return }
|
||||||
|
|
||||||
installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
|
installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
|
@ -649,7 +650,7 @@ class AppState: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelInstall(id: Xcode.ID) {
|
func cancelInstall(id: Xcode.ID) {
|
||||||
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
|
guard let availableXcode = availableXcodes.first(where: { $0.version == id.version }) else { return }
|
||||||
|
|
||||||
// Cancel the publisher
|
// Cancel the publisher
|
||||||
installationPublishers[id] = nil
|
installationPublishers[id] = nil
|
||||||
|
|
@ -767,7 +768,7 @@ class AppState: ObservableObject {
|
||||||
config.allowsRunningApplicationSubstitution = false
|
config.allowsRunningApplicationSubstitution = false
|
||||||
NSWorkspace.shared.openApplication(at: path.url, configuration: config)
|
NSWorkspace.shared.openApplication(at: path.url, configuration: config)
|
||||||
default:
|
default:
|
||||||
Logger.appState.error("\(xcode.id) is not installed")
|
Logger.appState.error("\(xcode.id.version) is not installed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -863,7 +864,7 @@ class AppState: ObservableObject {
|
||||||
// If build metadata matches exactly, replace the available version with the installed version.
|
// If build metadata matches exactly, replace the available version with the installed version.
|
||||||
// This should handle Apple versions from /downloads/more which don't have build metadata identifiers.
|
// This should handle Apple versions from /downloads/more which don't have build metadata identifiers.
|
||||||
if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) {
|
if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) {
|
||||||
adjustedAvailableXcodes[index].version = installedXcode.version
|
adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
|
||||||
}
|
}
|
||||||
// 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
|
// 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
|
||||||
// Not all prerelease Apple versions available online include build metadata
|
// Not all prerelease Apple versions available online include build metadata
|
||||||
|
|
@ -871,7 +872,7 @@ class AppState: ObservableObject {
|
||||||
availableXcode.version.isEquivalent(to: installedXcode.version) &&
|
availableXcode.version.isEquivalent(to: installedXcode.version) &&
|
||||||
availableXcode.version.buildMetadataIdentifiers.isEmpty
|
availableXcode.version.buildMetadataIdentifiers.isEmpty
|
||||||
}) {
|
}) {
|
||||||
adjustedAvailableXcodes[index].version = installedXcode.version
|
adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -888,14 +889,15 @@ class AppState: ObservableObject {
|
||||||
// Include this version if there's only one with this build identifier
|
// Include this version if there's only one with this build identifier
|
||||||
return availableXcodesWithIdenticalBuildIdentifiers.count == 1 ||
|
return availableXcodesWithIdenticalBuildIdentifiers.count == 1 ||
|
||||||
// Or if there's more than one with this build identifier and this is the release version
|
// Or if there's more than one with this build identifier and this is the release version
|
||||||
availableXcodesWithIdenticalBuildIdentifiers.count > 1 && availableXcode.version.prereleaseIdentifiers.isEmpty
|
|
||||||
}
|
availableXcodesWithIdenticalBuildIdentifiers.count > 1 && (availableXcode.version.prereleaseIdentifiers.isEmpty || availableXcode.architectures?.count ?? 0 != 0)
|
||||||
|
}
|
||||||
.map { availableXcode -> Xcode in
|
.map { availableXcode -> Xcode in
|
||||||
let installedXcode = installedXcodes.first(where: { installedXcode in
|
let installedXcode = installedXcodes.first(where: { installedXcode in
|
||||||
availableXcode.version.isEquivalent(to: installedXcode.version)
|
availableXcode.version.isEquivalent(to: installedXcode.version)
|
||||||
})
|
})
|
||||||
|
|
||||||
let identicalBuilds: [Version]
|
let identicalBuilds: [XcodeID]
|
||||||
let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes
|
let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes
|
||||||
.filter {
|
.filter {
|
||||||
return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers &&
|
return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers &&
|
||||||
|
|
@ -905,7 +907,7 @@ class AppState: ObservableObject {
|
||||||
}
|
}
|
||||||
// If this is the release version, add the identical builds to it
|
// If this is the release version, add the identical builds to it
|
||||||
if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty {
|
if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty {
|
||||||
identicalBuilds = [availableXcode.version] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.version)
|
identicalBuilds = [availableXcode.xcodeID] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.xcodeID)
|
||||||
} else {
|
} else {
|
||||||
identicalBuilds = []
|
identicalBuilds = []
|
||||||
}
|
}
|
||||||
|
|
@ -926,7 +928,8 @@ class AppState: ObservableObject {
|
||||||
releaseDate: availableXcode.releaseDate,
|
releaseDate: availableXcode.releaseDate,
|
||||||
sdks: availableXcode.sdks,
|
sdks: availableXcode.sdks,
|
||||||
compilers: availableXcode.compilers,
|
compilers: availableXcode.compilers,
|
||||||
downloadFileSize: availableXcode.fileSize
|
downloadFileSize: availableXcode.fileSize,
|
||||||
|
architectures: availableXcode.architectures
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Version
|
import Version
|
||||||
import struct XCModel.SDKs
|
import XcodesKit
|
||||||
import struct XCModel.Compilers
|
|
||||||
|
|
||||||
/// A version of Xcode that's available for installation
|
/// A version of Xcode that's available for installation
|
||||||
public struct AvailableXcode: Codable {
|
public struct AvailableXcode: Codable {
|
||||||
public var version: Version
|
public var version: Version {
|
||||||
|
return xcodeID.version
|
||||||
|
}
|
||||||
public let url: URL
|
public let url: URL
|
||||||
public let filename: String
|
public let filename: String
|
||||||
public let releaseDate: Date?
|
public let releaseDate: Date?
|
||||||
|
|
@ -14,9 +15,11 @@ public struct AvailableXcode: Codable {
|
||||||
public let sdks: SDKs?
|
public let sdks: SDKs?
|
||||||
public let compilers: Compilers?
|
public let compilers: Compilers?
|
||||||
public let fileSize: Int64?
|
public let fileSize: Int64?
|
||||||
|
public let architectures: [Architecture]?
|
||||||
public var downloadPath: String {
|
public var downloadPath: String {
|
||||||
return url.path
|
return url.path
|
||||||
}
|
}
|
||||||
|
public var xcodeID: XcodeID
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
version: Version,
|
version: Version,
|
||||||
|
|
@ -27,9 +30,9 @@ public struct AvailableXcode: Codable {
|
||||||
releaseNotesURL: URL? = nil,
|
releaseNotesURL: URL? = nil,
|
||||||
sdks: SDKs? = nil,
|
sdks: SDKs? = nil,
|
||||||
compilers: Compilers? = nil,
|
compilers: Compilers? = nil,
|
||||||
fileSize: Int64? = nil
|
fileSize: Int64? = nil,
|
||||||
|
architectures: [Architecture]? = nil
|
||||||
) {
|
) {
|
||||||
self.version = version
|
|
||||||
self.url = url
|
self.url = url
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.releaseDate = releaseDate
|
self.releaseDate = releaseDate
|
||||||
|
|
@ -38,5 +41,7 @@ public struct AvailableXcode: Codable {
|
||||||
self.sdks = sdks
|
self.sdks = sdks
|
||||||
self.compilers = compilers
|
self.compilers = compilers
|
||||||
self.fileSize = fileSize
|
self.fileSize = fileSize
|
||||||
|
self.architectures = architectures
|
||||||
|
self.xcodeID = XcodeID(version: version, architectures: architectures)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@ import Path
|
||||||
/// A version of Xcode that's already installed
|
/// A version of Xcode that's already installed
|
||||||
public struct InstalledXcode: Equatable {
|
public struct InstalledXcode: Equatable {
|
||||||
public let path: Path
|
public let path: Path
|
||||||
|
public let xcodeID: XcodeID
|
||||||
|
|
||||||
/// Composed of the bundle short version from Info.plist and the product build version from version.plist
|
/// Composed of the bundle short version from Info.plist and the product build version from version.plist
|
||||||
public let version: Version
|
public var version: Version {
|
||||||
|
return xcodeID.version
|
||||||
|
}
|
||||||
|
|
||||||
public init?(path: Path) {
|
public init?(path: Path) {
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
@ -32,11 +36,13 @@ public struct InstalledXcode: Equatable {
|
||||||
prereleaseIdentifiers = ["beta"]
|
prereleaseIdentifiers = ["beta"]
|
||||||
}
|
}
|
||||||
|
|
||||||
self.version = Version(major: bundleVersion.major,
|
let version = Version(major: bundleVersion.major,
|
||||||
minor: bundleVersion.minor,
|
minor: bundleVersion.minor,
|
||||||
patch: bundleVersion.patch,
|
patch: bundleVersion.patch,
|
||||||
prereleaseIdentifiers: prereleaseIdentifiers,
|
prereleaseIdentifiers: prereleaseIdentifiers,
|
||||||
buildMetadataIdentifiers: [versionPlist.productBuildVersion].compactMap { $0 })
|
buildMetadataIdentifiers: [versionPlist.productBuildVersion].compactMap { $0 })
|
||||||
|
|
||||||
|
self.xcodeID = XcodeID(version: version, architectures: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import struct XCModel.SDKs
|
|
||||||
import XcodesKit
|
import XcodesKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import Version
|
import Version
|
||||||
import struct XCModel.Xcode
|
import XcodesKit
|
||||||
|
|
||||||
extension Version {
|
extension Version {
|
||||||
/// Initialize a Version from an XcodeReleases' XCModel.Xcode
|
/// Initialize a Version from an XcodeReleases' XCModel.Xcode
|
||||||
///
|
///
|
||||||
/// This is kinda quick-and-dirty, and it would probably be better for us to adopt something closer to XCModel.Xcode under the hood and map the scraped data to it instead.
|
/// This is kinda quick-and-dirty, and it would probably be better for us to adopt something closer to XCModel.Xcode under the hood and map the scraped data to it instead.
|
||||||
init?(xcReleasesXcode: XCModel.Xcode) {
|
init?(xcReleasesXcode: XcodeRelease) {
|
||||||
var versionString = xcReleasesXcode.version.number ?? ""
|
var versionString = xcReleasesXcode.version.number ?? ""
|
||||||
|
|
||||||
// Append trailing ".0" in order to get a fully-specified version string
|
// Append trailing ".0" in order to get a fully-specified version string
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,30 @@
|
||||||
import AppKit
|
import AppKit
|
||||||
import Foundation
|
import Foundation
|
||||||
import Version
|
import Version
|
||||||
import struct XCModel.SDKs
|
|
||||||
import struct XCModel.Compilers
|
|
||||||
import Path
|
import Path
|
||||||
|
import XcodesKit
|
||||||
|
|
||||||
|
public struct XcodeID: Codable, Hashable, Identifiable {
|
||||||
|
public let version: Version
|
||||||
|
public let architectures: [Architecture]?
|
||||||
|
|
||||||
|
public var id: String {
|
||||||
|
let architectures = architectures?.map { $0.rawValue}.joined() ?? ""
|
||||||
|
return version.description + architectures
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(version: Version, architectures: [Architecture]? = nil) {
|
||||||
|
self.version = version
|
||||||
|
self.architectures = architectures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Xcode: Identifiable, CustomStringConvertible {
|
struct Xcode: Identifiable, CustomStringConvertible {
|
||||||
let version: Version
|
var version: Version {
|
||||||
|
return id.version
|
||||||
|
}
|
||||||
/// Other Xcode versions that have the same build identifier
|
/// Other Xcode versions that have the same build identifier
|
||||||
let identicalBuilds: [Version]
|
let identicalBuilds: [XcodeID]
|
||||||
var installState: XcodeInstallState
|
var installState: XcodeInstallState
|
||||||
let selected: Bool
|
let selected: Bool
|
||||||
let icon: NSImage?
|
let icon: NSImage?
|
||||||
|
|
@ -18,10 +34,12 @@ struct Xcode: Identifiable, CustomStringConvertible {
|
||||||
let sdks: SDKs?
|
let sdks: SDKs?
|
||||||
let compilers: Compilers?
|
let compilers: Compilers?
|
||||||
let downloadFileSize: Int64?
|
let downloadFileSize: Int64?
|
||||||
|
let architectures: [Architecture]?
|
||||||
|
let id: XcodeID
|
||||||
|
|
||||||
init(
|
init(
|
||||||
version: Version,
|
version: Version,
|
||||||
identicalBuilds: [Version] = [],
|
identicalBuilds: [XcodeID] = [],
|
||||||
installState: XcodeInstallState,
|
installState: XcodeInstallState,
|
||||||
selected: Bool,
|
selected: Bool,
|
||||||
icon: NSImage?,
|
icon: NSImage?,
|
||||||
|
|
@ -30,9 +48,9 @@ struct Xcode: Identifiable, CustomStringConvertible {
|
||||||
releaseDate: Date? = nil,
|
releaseDate: Date? = nil,
|
||||||
sdks: SDKs? = nil,
|
sdks: SDKs? = nil,
|
||||||
compilers: Compilers? = nil,
|
compilers: Compilers? = nil,
|
||||||
downloadFileSize: Int64? = nil
|
downloadFileSize: Int64? = nil,
|
||||||
|
architectures: [Architecture]? = nil
|
||||||
) {
|
) {
|
||||||
self.version = version
|
|
||||||
self.identicalBuilds = identicalBuilds
|
self.identicalBuilds = identicalBuilds
|
||||||
self.installState = installState
|
self.installState = installState
|
||||||
self.selected = selected
|
self.selected = selected
|
||||||
|
|
@ -43,10 +61,10 @@ struct Xcode: Identifiable, CustomStringConvertible {
|
||||||
self.sdks = sdks
|
self.sdks = sdks
|
||||||
self.compilers = compilers
|
self.compilers = compilers
|
||||||
self.downloadFileSize = downloadFileSize
|
self.downloadFileSize = downloadFileSize
|
||||||
|
self.architectures = architectures
|
||||||
|
self.id = XcodeID(version: version, architectures: architectures)
|
||||||
}
|
}
|
||||||
|
|
||||||
var id: Version { version }
|
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
version.appleDescription
|
version.appleDescription
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import struct XCModel.Compilers
|
import XcodesKit
|
||||||
|
|
||||||
struct CompilersView: View {
|
struct CompilersView: View {
|
||||||
let compilers: Compilers?
|
let compilers: Compilers?
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ import XcodesKit
|
||||||
import Path
|
import Path
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Version
|
import Version
|
||||||
import struct XCModel.Compilers
|
|
||||||
import struct XCModel.SDKs
|
|
||||||
|
|
||||||
struct InfoPane: View {
|
struct InfoPane: View {
|
||||||
let xcode: Xcode
|
let xcode: Xcode
|
||||||
|
|
@ -42,7 +40,7 @@ struct InfoPane: View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
ReleaseDateView(date: xcode.releaseDate, url: xcode.releaseNotesURL)
|
ReleaseDateView(date: xcode.releaseDate, url: xcode.releaseNotesURL)
|
||||||
CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion)
|
CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion)
|
||||||
IdenticalBuildsView(builds: xcode.identicalBuilds)
|
IdenticalBuildsView(builds: xcode.identicalBuilds.map { $0.version })
|
||||||
SDKandCompilers
|
SDKandCompilers
|
||||||
}
|
}
|
||||||
.frame(width: 200)
|
.frame(width: 200)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Version
|
import Version
|
||||||
import XCModel
|
import XcodesKit
|
||||||
import Path
|
import Path
|
||||||
|
|
||||||
struct InstalledStateButtons: View {
|
struct InstalledStateButtons: View {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import Version
|
||||||
|
|
||||||
struct NotInstalledStateButtons: View {
|
struct NotInstalledStateButtons: View {
|
||||||
let downloadFileSizeString: String?
|
let downloadFileSizeString: String?
|
||||||
let id: Version
|
let id: XcodeID
|
||||||
|
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ struct NotInstalledStateButtons: View {
|
||||||
#Preview {
|
#Preview {
|
||||||
NotInstalledStateButtons(
|
NotInstalledStateButtons(
|
||||||
downloadFileSizeString: "1,19 GB",
|
downloadFileSizeString: "1,19 GB",
|
||||||
id: Version(major: 12, minor: 3, patch: 0)
|
id: XcodeID(version: Version(major: 12, minor: 3, patch: 0), architectures: nil)
|
||||||
)
|
)
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import struct XCModel.SDKs
|
import XcodesKit
|
||||||
|
|
||||||
struct SDKsView: View {
|
struct SDKsView: View {
|
||||||
let content: String
|
let content: String
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,12 @@ struct MainWindow: View {
|
||||||
// FB8979533 SceneStorage doesn't restore value after app is quit by user
|
// FB8979533 SceneStorage doesn't restore value after app is quit by user
|
||||||
@AppStorage("isShowingInfoPane") private var isShowingInfoPane = false
|
@AppStorage("isShowingInfoPane") private var isShowingInfoPane = false
|
||||||
@AppStorage("xcodeListCategory") private var category: XcodeListCategory = .all
|
@AppStorage("xcodeListCategory") private var category: XcodeListCategory = .all
|
||||||
|
@AppStorage("xcodeListArchitecture") private var architecture: XcodeListArchitecture = .universal
|
||||||
@AppStorage("isInstalledOnly") private var isInstalledOnly = false
|
@AppStorage("isInstalledOnly") private var isInstalledOnly = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationSplitViewWrapper {
|
NavigationSplitViewWrapper {
|
||||||
XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly)
|
XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly, architecture: architecture)
|
||||||
.layoutPriority(1)
|
.layoutPriority(1)
|
||||||
.alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in
|
.alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in
|
||||||
Alert(title: Text(String(format: localizeString("Alert.Uninstall.Title"), xcode.description)),
|
Alert(title: Text(String(format: localizeString("Alert.Uninstall.Title"), xcode.description)),
|
||||||
|
|
@ -31,7 +32,8 @@ struct MainWindow: View {
|
||||||
.mainToolbar(
|
.mainToolbar(
|
||||||
category: $category,
|
category: $category,
|
||||||
isInstalledOnly: $isInstalledOnly,
|
isInstalledOnly: $isInstalledOnly,
|
||||||
isShowingInfoPane: $isShowingInfoPane
|
isShowingInfoPane: $isShowingInfoPane,
|
||||||
|
architecture: $architecture
|
||||||
)
|
)
|
||||||
} detail: {
|
} detail: {
|
||||||
Group {
|
Group {
|
||||||
|
|
@ -191,11 +193,11 @@ struct MainWindow: View {
|
||||||
case let .checkMinSupportedVersion(xcode, deviceVersion):
|
case let .checkMinSupportedVersion(xcode, deviceVersion):
|
||||||
return Alert(
|
return Alert(
|
||||||
title: Text("Alert.MinSupported.Title"),
|
title: Text("Alert.MinSupported.Title"),
|
||||||
message: Text(String(format: localizeString("Alert.MinSupported.Message"), xcode.version.descriptionWithoutBuildMetadata, xcode.requiredMacOSVersion ?? "", deviceVersion)),
|
message: Text(String(format: localizeString("Alert.MinSupported.Message"), xcode.xcodeID.version.descriptionWithoutBuildMetadata, xcode.requiredMacOSVersion ?? "", deviceVersion)),
|
||||||
primaryButton: .default(
|
primaryButton: .default(
|
||||||
Text("Install"),
|
Text("Install"),
|
||||||
action: {
|
action: {
|
||||||
self.appState.install(id: xcode.version)
|
self.appState.install(id: xcode.xcodeID)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
secondaryButton: .cancel(Text("Cancel"))
|
secondaryButton: .cancel(Text("Cancel"))
|
||||||
|
|
@ -223,7 +225,7 @@ struct MainWindow_Previews: PreviewProvider {
|
||||||
MainWindow().environmentObject({ () -> AppState in
|
MainWindow().environmentObject({ () -> AppState in
|
||||||
let a = AppState()
|
let a = AppState()
|
||||||
a.allXcodes = [
|
a.allXcodes = [
|
||||||
Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0+1234A")!, Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
|
Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0+1234A")!), XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
|
||||||
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
|
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
|
||||||
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
|
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
|
||||||
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),
|
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ struct MainToolbarModifier: ViewModifier {
|
||||||
@Binding var category: XcodeListCategory
|
@Binding var category: XcodeListCategory
|
||||||
@Binding var isInstalledOnly: Bool
|
@Binding var isInstalledOnly: Bool
|
||||||
@Binding var isShowingInfoPane: Bool
|
@Binding var isShowingInfoPane: Bool
|
||||||
|
@Binding var architectures: XcodeListArchitecture
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
|
|
@ -23,6 +24,24 @@ struct MainToolbarModifier: ViewModifier {
|
||||||
.help("RefreshDescription")
|
.help("RefreshDescription")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
switch architectures {
|
||||||
|
case .universal: architectures = .appleSilicon
|
||||||
|
case .appleSilicon: architectures = .universal
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
switch architectures {
|
||||||
|
case .universal:
|
||||||
|
Label("Universal", systemImage: "cpu.fill")
|
||||||
|
case .appleSilicon:
|
||||||
|
Label("Apple Silicon", systemImage: "m4.button.horizontal")
|
||||||
|
.labelStyle(.trailingIcon)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.help("FilterAvailableDescription")
|
||||||
|
.disabled(architectures.isManaged)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
switch category {
|
switch category {
|
||||||
case .all: category = .release
|
case .all: category = .release
|
||||||
|
|
@ -65,13 +84,15 @@ extension View {
|
||||||
func mainToolbar(
|
func mainToolbar(
|
||||||
category: Binding<XcodeListCategory>,
|
category: Binding<XcodeListCategory>,
|
||||||
isInstalledOnly: Binding<Bool>,
|
isInstalledOnly: Binding<Bool>,
|
||||||
isShowingInfoPane: Binding<Bool>
|
isShowingInfoPane: Binding<Bool>,
|
||||||
|
architecture: Binding<XcodeListArchitecture>
|
||||||
) -> some View {
|
) -> some View {
|
||||||
modifier(
|
modifier(
|
||||||
MainToolbarModifier(
|
MainToolbarModifier(
|
||||||
category: category,
|
category: category,
|
||||||
isInstalledOnly: isInstalledOnly,
|
isInstalledOnly: isInstalledOnly,
|
||||||
isShowingInfoPane: isShowingInfoPane
|
isShowingInfoPane: isShowingInfoPane,
|
||||||
|
architectures: architecture
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import XcodesKit
|
||||||
|
|
||||||
enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConvertible {
|
enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConvertible {
|
||||||
case all
|
case all
|
||||||
|
|
@ -17,3 +18,19 @@ enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConverti
|
||||||
|
|
||||||
var isManaged: Bool { PreferenceKey.xcodeListCategory.isManaged() }
|
var isManaged: Bool { PreferenceKey.xcodeListCategory.isManaged() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum XcodeListArchitecture: String, CaseIterable, Identifiable, CustomStringConvertible {
|
||||||
|
case universal
|
||||||
|
case appleSilicon
|
||||||
|
|
||||||
|
var id: Self { self }
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .universal: return localizeString("Universal")
|
||||||
|
case .appleSilicon: return localizeString("Apple Silicon")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isManaged: Bool { PreferenceKey.xcodeListCategory.isManaged() }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,16 @@ struct XcodeListView: View {
|
||||||
@Binding var selectedXcodeID: Xcode.ID?
|
@Binding var selectedXcodeID: Xcode.ID?
|
||||||
private let searchText: String
|
private let searchText: String
|
||||||
private let category: XcodeListCategory
|
private let category: XcodeListCategory
|
||||||
|
private let architecture: XcodeListArchitecture
|
||||||
private let isInstalledOnly: Bool
|
private let isInstalledOnly: Bool
|
||||||
@AppStorage(PreferenceKey.allowedMajorVersions.rawValue) private var allowedMajorVersions = Int.max
|
@AppStorage(PreferenceKey.allowedMajorVersions.rawValue) private var allowedMajorVersions = Int.max
|
||||||
|
|
||||||
init(selectedXcodeID: Binding<Xcode.ID?>, searchText: String, category: XcodeListCategory, isInstalledOnly: Bool) {
|
init(selectedXcodeID: Binding<Xcode.ID?>, searchText: String, category: XcodeListCategory, isInstalledOnly: Bool, architecture: XcodeListArchitecture) {
|
||||||
self._selectedXcodeID = selectedXcodeID
|
self._selectedXcodeID = selectedXcodeID
|
||||||
self.searchText = searchText
|
self.searchText = searchText
|
||||||
self.category = category
|
self.category = category
|
||||||
self.isInstalledOnly = isInstalledOnly
|
self.isInstalledOnly = isInstalledOnly
|
||||||
|
self.architecture = architecture
|
||||||
}
|
}
|
||||||
|
|
||||||
var visibleXcodes: [Xcode] {
|
var visibleXcodes: [Xcode] {
|
||||||
|
|
@ -28,6 +30,20 @@ struct XcodeListView: View {
|
||||||
xcodes = appState.allXcodes.filter { $0.version.isPrerelease }
|
xcodes = appState.allXcodes.filter { $0.version.isPrerelease }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch architecture {
|
||||||
|
case .appleSilicon:
|
||||||
|
xcodes = xcodes.filter { $0.architectures == [.arm64] }
|
||||||
|
case .universal:
|
||||||
|
xcodes = xcodes.filter {
|
||||||
|
if let architectures = $0.architectures {
|
||||||
|
return architectures.contains(.x86_64) // we're assuming that if architectures contains x86 then it's universal
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let latestMajor = xcodes.sorted(\.version)
|
let latestMajor = xcodes.sorted(\.version)
|
||||||
.filter { $0.version.isNotPrerelease }
|
.filter { $0.version.isNotPrerelease }
|
||||||
.last?
|
.last?
|
||||||
|
|
@ -95,11 +111,11 @@ struct PlatformsPocket: View {
|
||||||
struct XcodeListView_Previews: PreviewProvider {
|
struct XcodeListView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
Group {
|
Group {
|
||||||
XcodeListView(selectedXcodeID: .constant(nil), searchText: "", category: .all, isInstalledOnly: false)
|
XcodeListView(selectedXcodeID: .constant(nil), searchText: "", category: .all, isInstalledOnly: false, architecture: .appleSilicon)
|
||||||
.environmentObject({ () -> AppState in
|
.environmentObject({ () -> AppState in
|
||||||
let a = AppState()
|
let a = AppState()
|
||||||
a.allXcodes = [
|
a.allXcodes = [
|
||||||
Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0+1234A")!, Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
|
Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0+1234A")!), XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
|
||||||
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
|
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
|
||||||
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
|
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
|
||||||
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),
|
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ struct XcodeListViewRow: View {
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.accessibility(label: Text("IdenticalBuilds"))
|
.accessibility(label: Text("IdenticalBuilds"))
|
||||||
.accessibility(value: Text(xcode.identicalBuilds.map(\.appleDescription).joined(separator: ", ")))
|
.accessibility(value: Text(xcode.identicalBuilds.map(\.version.appleDescription).joined(separator: ", ")))
|
||||||
.help("IdenticalBuilds.help")
|
.help("IdenticalBuilds.help")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ struct XcodeListViewRow_Previews: PreviewProvider {
|
||||||
)
|
)
|
||||||
|
|
||||||
XcodeListViewRow(
|
XcodeListViewRow(
|
||||||
xcode: Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
|
xcode: Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
|
||||||
selected: false,
|
selected: false,
|
||||||
appState: AppState()
|
appState: AppState()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
6
Xcodes/Resources/Assets.xcassets/Icons/Contents.json
Normal file
6
Xcodes/Resources/Assets.xcassets/Icons/Contents.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4446,6 +4446,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"AppleSilicon" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"AppUpdates" : {
|
"AppUpdates" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -22102,6 +22105,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Universal" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"UnxipExperiment" : {
|
"UnxipExperiment" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// Architecture.swift
|
||||||
|
// XcodesKit
|
||||||
|
//
|
||||||
|
// Created by Matt Kiazyk on 2025-08-23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// The name of an Architecture.
|
||||||
|
public enum Architecture: String, Codable, Equatable, Hashable {
|
||||||
|
/// The Arm64 architecture (Apple Silicon)
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// Checksums.swift
|
||||||
|
// xcodereleases
|
||||||
|
//
|
||||||
|
// Created by Xcode Releases on 9/17/20.
|
||||||
|
// Copyright © 2020 Xcode Releases. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Checksums: Codable {
|
||||||
|
|
||||||
|
public let sha1: String?
|
||||||
|
|
||||||
|
public init(sha1: String? = nil) {
|
||||||
|
self.sha1 = sha1
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
//
|
||||||
|
// Compiler.swift
|
||||||
|
// xcodereleases
|
||||||
|
//
|
||||||
|
// Created by Xcode Releases on 4/4/18.
|
||||||
|
// Copyright © 2018 Xcode Releases. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Compilers: Codable {
|
||||||
|
public let gcc: Array<XcodeVersion>?
|
||||||
|
public let llvm_gcc: Array<XcodeVersion>?
|
||||||
|
public let llvm: Array<XcodeVersion>?
|
||||||
|
public let clang: Array<XcodeVersion>?
|
||||||
|
public let swift: Array<XcodeVersion>?
|
||||||
|
|
||||||
|
public init(gcc: XcodeVersion? = nil, llvm_gcc: XcodeVersion? = nil, llvm: XcodeVersion? = nil, clang: XcodeVersion? = nil, swift: XcodeVersion? = nil) {
|
||||||
|
self.gcc = gcc.map { [$0] }
|
||||||
|
self.llvm_gcc = llvm_gcc.map { [$0] }
|
||||||
|
self.llvm = llvm.map { [$0] }
|
||||||
|
self.clang = clang.map { [$0] }
|
||||||
|
self.swift = swift.map { [$0] }
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(gcc: Array<XcodeVersion>?, llvm_gcc: Array<XcodeVersion>?, llvm: Array<XcodeVersion>?, clang: Array<XcodeVersion>?, swift: Array<XcodeVersion>?) {
|
||||||
|
self.gcc = gcc?.isEmpty == true ? nil : gcc
|
||||||
|
self.llvm_gcc = llvm_gcc?.isEmpty == true ? nil : llvm_gcc
|
||||||
|
self.llvm = llvm?.isEmpty == true ? nil : llvm
|
||||||
|
self.clang = clang?.isEmpty == true ? nil : clang
|
||||||
|
self.swift = swift?.isEmpty == true ? nil : swift
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// Link.swift
|
||||||
|
// xcodereleases
|
||||||
|
//
|
||||||
|
// Created by Xcode Releases on 4/5/18.
|
||||||
|
// Copyright © 2018 Xcode Releases. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Link: Codable {
|
||||||
|
public let url: URL
|
||||||
|
public let sizeMB: Int?
|
||||||
|
/// The platforms supported by this link, if applicable.
|
||||||
|
public var architectures: [Architecture]?
|
||||||
|
|
||||||
|
// public init(_ string: String, _ size: Int? = nil, _ architectures: [Architecture]? = nil) {
|
||||||
|
// self.url = URL(string: string)!
|
||||||
|
// self.sizeMB = size
|
||||||
|
// self.architectures = architectures
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Links: Codable {
|
||||||
|
public let download: Link?
|
||||||
|
public let notes: Link?
|
||||||
|
|
||||||
|
public init(download: Link? = nil, notes: Link? = nil) {
|
||||||
|
self.download = download
|
||||||
|
self.notes = notes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
//
|
||||||
|
// Release.swift
|
||||||
|
// xcodereleases
|
||||||
|
//
|
||||||
|
// Created by Xcode Releases on 4/4/18.
|
||||||
|
// Copyright © 2018 Xcode Releases. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum Release: Codable {
|
||||||
|
|
||||||
|
public enum CodingKeys: String, CodingKey {
|
||||||
|
case gm, gmSeed, rc, beta, dp, release
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isGM: Bool {
|
||||||
|
guard case .gm = self else { return false }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
case gm
|
||||||
|
case gmSeed(Int)
|
||||||
|
case rc(Int)
|
||||||
|
case beta(Int)
|
||||||
|
case dp(Int)
|
||||||
|
case release
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
if let _ = try container.decodeIfPresent(Bool.self, forKey: .gm) {
|
||||||
|
self = .gm
|
||||||
|
} else if let v = try container.decodeIfPresent(Int.self, forKey: .gmSeed) {
|
||||||
|
self = .gmSeed(v)
|
||||||
|
} else if let v = try container.decodeIfPresent(Int.self, forKey: .rc) {
|
||||||
|
self = .rc(v)
|
||||||
|
} else if let v = try container.decodeIfPresent(Int.self, forKey: .beta) {
|
||||||
|
self = .beta(v)
|
||||||
|
} else if let v = try container.decodeIfPresent(Int.self, forKey: .dp) {
|
||||||
|
self = .dp(v)
|
||||||
|
} else if let _ = try container.decodeIfPresent(Bool.self, forKey: .release) {
|
||||||
|
self = .release
|
||||||
|
} else {
|
||||||
|
fatalError("Unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
switch self {
|
||||||
|
case .gm: try container.encode(true, forKey: .gm)
|
||||||
|
case .gmSeed(let v): try container.encode(v, forKey: .gmSeed)
|
||||||
|
case .rc(let v): try container.encode(v, forKey: .rc)
|
||||||
|
case .beta(let v): try container.encode(v, forKey: .beta)
|
||||||
|
case .dp(let v): try container.encode(v, forKey: .dp)
|
||||||
|
case .release: try container.encode(true, forKey: .release)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
//
|
||||||
|
// SDKs.swift
|
||||||
|
// xcodereleases
|
||||||
|
//
|
||||||
|
// Created by Xcode Releases on 4/4/18.
|
||||||
|
// Copyright © 2018 Xcode Releases. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct SDKs: Codable {
|
||||||
|
public let macOS: Array<XcodeVersion>?
|
||||||
|
public let iOS: Array<XcodeVersion>?
|
||||||
|
public let watchOS: Array<XcodeVersion>?
|
||||||
|
public let tvOS: Array<XcodeVersion>?
|
||||||
|
public let visionOS: Array<XcodeVersion>?
|
||||||
|
|
||||||
|
public init(macOS: XcodeVersion? = nil, iOS: XcodeVersion? = nil, watchOS: XcodeVersion? = nil, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
|
||||||
|
self.macOS = macOS.map { [$0] }
|
||||||
|
self.iOS = iOS.map { [$0] }
|
||||||
|
self.watchOS = watchOS.map { [$0] }
|
||||||
|
self.tvOS = tvOS.map { [$0] }
|
||||||
|
self.visionOS = visionOS.map { [$0] }
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(macOS: Array<XcodeVersion>?, iOS: XcodeVersion? = nil, watchOS: XcodeVersion? = nil, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
|
||||||
|
self.macOS = macOS?.isEmpty == true ? nil : macOS
|
||||||
|
self.iOS = iOS.map { [$0] }
|
||||||
|
self.watchOS = watchOS.map { [$0] }
|
||||||
|
self.tvOS = tvOS.map { [$0] }
|
||||||
|
self.visionOS = visionOS.map { [$0] }
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(macOS: Array<XcodeVersion>?, iOS: Array<XcodeVersion>?, watchOS: XcodeVersion? = nil, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
|
||||||
|
self.macOS = macOS?.isEmpty == true ? nil : macOS
|
||||||
|
self.iOS = iOS?.isEmpty == true ? nil : iOS
|
||||||
|
self.watchOS = watchOS.map { [$0] }
|
||||||
|
self.tvOS = tvOS.map { [$0] }
|
||||||
|
self.visionOS = visionOS.map { [$0] }
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(macOS: Array<XcodeVersion>?, iOS: Array<XcodeVersion>?, watchOS: Array<XcodeVersion>?, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
|
||||||
|
self.macOS = macOS?.isEmpty == true ? nil : macOS
|
||||||
|
self.iOS = iOS?.isEmpty == true ? nil : iOS
|
||||||
|
self.watchOS = watchOS?.isEmpty == true ? nil : watchOS
|
||||||
|
self.tvOS = tvOS.map { [$0] }
|
||||||
|
self.visionOS = visionOS.map { [$0] }
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(macOS: Array<XcodeVersion>?, iOS: Array<XcodeVersion>?, watchOS: Array<XcodeVersion>?, tvOS: Array<XcodeVersion>?, visionOS: Array<XcodeVersion>?) {
|
||||||
|
self.macOS = macOS?.isEmpty == true ? nil : macOS
|
||||||
|
self.iOS = iOS?.isEmpty == true ? nil : iOS
|
||||||
|
self.watchOS = watchOS?.isEmpty == true ? nil : watchOS
|
||||||
|
self.tvOS = tvOS?.isEmpty == true ? nil : tvOS
|
||||||
|
self.visionOS = visionOS?.isEmpty == true ? nil : visionOS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// Xcode.swift
|
||||||
|
// xcodereleases
|
||||||
|
//
|
||||||
|
// Created by Xcode Releases on 4/3/18.
|
||||||
|
// Copyright © 2018 Xcode Releases. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct XcodeRelease: Codable {
|
||||||
|
public let name: String
|
||||||
|
public let version: XcodeVersion
|
||||||
|
public let date: YMD
|
||||||
|
public let requires: String
|
||||||
|
public let sdks: SDKs?
|
||||||
|
public let compilers: Compilers?
|
||||||
|
public let links: Links?
|
||||||
|
public let checksums: Checksums?
|
||||||
|
|
||||||
|
public var architectures: [Architecture]? {
|
||||||
|
return links.flatMap { $0.download?.architectures }
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(name: String = "Xcode", version: XcodeVersion, date: (Int, Int, Int), requires: String, sdks: SDKs? = nil, compilers: Compilers? = nil, links: Links? = nil, checksums: Checksums? = nil) {
|
||||||
|
self.name = name
|
||||||
|
self.version = version;
|
||||||
|
self.date = YMD(date);
|
||||||
|
self.requires = requires;
|
||||||
|
self.sdks = sdks;
|
||||||
|
self.compilers = compilers
|
||||||
|
self.links = links
|
||||||
|
self.checksums = checksums
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// Version.swift
|
||||||
|
// xcodereleases
|
||||||
|
//
|
||||||
|
// Created by Xcode Releases on 4/4/18.
|
||||||
|
// Copyright © 2018 Xcode Releases. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public typealias V = XcodeVersion
|
||||||
|
public struct XcodeVersion: Codable {
|
||||||
|
public let number: String?
|
||||||
|
public let build: String?
|
||||||
|
public let release: Release
|
||||||
|
|
||||||
|
public init(_ build: String, _ number: String? = nil, _ release: Release = .release) {
|
||||||
|
self.number = number; self.build = build; self.release = release
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(number: String, _ build: String? = nil, _ release: Release = .release) {
|
||||||
|
self.number = number; self.build = build; self.release = release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// YMD.swift
|
||||||
|
// xcodereleases
|
||||||
|
//
|
||||||
|
// Created by Xcode Releases on 4/4/18.
|
||||||
|
// Copyright © 2018 Xcode Releases. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct YMD: Codable {
|
||||||
|
public let year: Int
|
||||||
|
public let month: Int
|
||||||
|
public let day: Int
|
||||||
|
|
||||||
|
public init(_ ymd: (Int, Int, Int)) {
|
||||||
|
self.year = ymd.0; self.month = ymd.1; self.day = ymd.2
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(_ year: Int, _ month: Int, _ day: Int) {
|
||||||
|
self.year = year; self.month = month; self.day = day
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue