mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
Merge pull request #743 from XcodesOrg/XcodeArchitectures
Support Showing and Downloading Multiple Xcode Architectures.
This commit is contained in:
commit
08738d6912
40 changed files with 1031 additions and 138 deletions
|
|
@ -48,7 +48,6 @@
|
|||
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.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 */; };
|
||||
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */ = {isa = PBXBuildFile; productRef = CA9FF86C25951C6E00E47BAF /* XCModel */; };
|
||||
CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF876259528CC00E47BAF /* Version+XcodeReleases.swift */; };
|
||||
CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF87A2595293E00E47BAF /* DataSource.swift */; };
|
||||
CA9FF88125955C7000E47BAF /* AvailableXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF88025955C7000E47BAF /* AvailableXcode.swift */; };
|
||||
|
|
@ -140,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 */; };
|
||||
|
|
@ -343,7 +341,6 @@
|
|||
E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.swift; sourceTree = "<group>"; };
|
||||
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; };
|
||||
E8EE58BF2E1CC2A50003FA9F /* RuntimeArchitecture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeArchitecture.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -363,7 +360,6 @@
|
|||
CABFA9E42592F08E00380FEE /* Version in Frameworks */,
|
||||
CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */,
|
||||
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */,
|
||||
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */,
|
||||
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */,
|
||||
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */,
|
||||
E862D43B2CC8B26F00BAA376 /* SRP in Frameworks */,
|
||||
|
|
@ -662,7 +658,6 @@
|
|||
E8E98A9425D863B100EC89A0 /* InfoPane */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8EE58BF2E1CC2A50003FA9F /* RuntimeArchitecture.swift */,
|
||||
B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */,
|
||||
B0403CF32AD9381D00137C09 /* SDKsView.swift */,
|
||||
B0403CF52AD9849E00137C09 /* CompilersView.swift */,
|
||||
|
|
@ -726,7 +721,6 @@
|
|||
CABFA9ED2592F0CC00380FEE /* SwiftSoup */,
|
||||
CABFA9F72592F0F900380FEE /* KeychainAccess */,
|
||||
CABFA9FC2592F13300380FEE /* LegibleError */,
|
||||
CA9FF86C25951C6E00E47BAF /* XCModel */,
|
||||
CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */,
|
||||
E689540225BE8C64000EBCEA /* DockProgress */,
|
||||
E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
|
||||
|
|
@ -816,7 +810,6 @@
|
|||
CABFA9EC2592F0CC00380FEE /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||
CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||
CABFA9FB2592F13300380FEE /* XCRemoteSwiftPackageReference "LegibleError" */,
|
||||
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */,
|
||||
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */,
|
||||
CAC28186259EE27200B8AB0B /* XCRemoteSwiftPackageReference "CombineExpectations" */,
|
||||
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
|
||||
|
|
@ -942,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 */,
|
||||
|
|
@ -1504,14 +1496,6 @@
|
|||
minimumVersion = 0.1.4;
|
||||
};
|
||||
};
|
||||
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/xcodereleases/data";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/RobotsAndPencils/ErrorHandling";
|
||||
|
|
@ -1607,11 +1591,6 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
productName = LibFido2Swift;
|
||||
};
|
||||
CA9FF86C25951C6E00E47BAF /* XCModel */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */;
|
||||
productName = XCModel;
|
||||
};
|
||||
CAA1CB2C255A5262003FD669 /* AppleAPI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = AppleAPI;
|
||||
|
|
|
|||
|
|
@ -28,15 +28,6 @@
|
|||
"version": "0.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "XcodeReleases",
|
||||
"repositoryURL": "https://github.com/xcodereleases/data",
|
||||
"state": {
|
||||
"branch": "main",
|
||||
"revision": "a43ad89e536d7a3da525fcc23fb182c37b756ecc",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "DockProgress",
|
||||
"repositoryURL": "https://github.com/sindresorhus/DockProgress",
|
||||
|
|
|
|||
|
|
@ -502,7 +502,7 @@ extension AppState {
|
|||
self.allXcodes[index].installState = .installing(step)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import Foundation
|
|||
import Path
|
||||
import Version
|
||||
import SwiftSoup
|
||||
import struct XCModel.Xcode
|
||||
import AppleAPI
|
||||
import XcodesKit
|
||||
|
||||
|
|
@ -211,8 +210,8 @@ extension AppState {
|
|||
private func xcodeReleases() -> AnyPublisher<[AvailableXcode], Error> {
|
||||
Current.network.dataTask(with: URLRequest(url: URL(string: "https://xcodereleases.com/data.json")!))
|
||||
.map(\.data)
|
||||
.decode(type: [XCModel.Xcode].self, decoder: JSONDecoder())
|
||||
.map { xcReleasesXcodes in
|
||||
.decode(type: [XcodeRelease].self, decoder: JSONDecoder())
|
||||
.map { xcReleasesXcodes in
|
||||
let xcodes = xcReleasesXcodes.compactMap { xcReleasesXcode -> AvailableXcode? in
|
||||
guard
|
||||
let downloadURL = xcReleasesXcode.links?.download?.url,
|
||||
|
|
@ -233,7 +232,8 @@ extension AppState {
|
|||
requiredMacOSVersion: xcReleasesXcode.requires,
|
||||
releaseNotesURL: xcReleasesXcode.links?.notes?.url,
|
||||
sdks: xcReleasesXcode.sdks,
|
||||
compilers: xcReleasesXcode.compilers
|
||||
compilers: xcReleasesXcode.compilers,
|
||||
architectures: xcReleasesXcode.architectures
|
||||
)
|
||||
}
|
||||
return xcodes
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ enum PreferenceKey: String {
|
|||
case xcodeListCategory
|
||||
case allowedMajorVersions
|
||||
case hideSupportXcodes
|
||||
case xcodeListArchitectures
|
||||
|
||||
func isManaged() -> Bool { UserDefaults.standard.objectIsForced(forKey: self.rawValue) }
|
||||
}
|
||||
|
|
@ -146,7 +147,7 @@ class AppState: ObservableObject {
|
|||
// MARK: - Publisher Cancellables
|
||||
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
private var installationPublishers: [Version: AnyCancellable] = [:]
|
||||
private var installationPublishers: [XcodeID: AnyCancellable] = [:]
|
||||
internal var runtimePublishers: [String: Task<(), any Error>] = [:]
|
||||
private var selectPublisher: AnyCancellable?
|
||||
private var uninstallPublisher: AnyCancellable?
|
||||
|
|
@ -523,8 +524,8 @@ class AppState: ObservableObject {
|
|||
|
||||
// MARK: - Install
|
||||
|
||||
func checkMinVersionAndInstall(id: Xcode.ID) {
|
||||
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
|
||||
func checkMinVersionAndInstall(id: XcodeID) {
|
||||
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
|
||||
|
||||
// Check to see if users macOS is supported
|
||||
if let requiredMacOSVersion = availableXcode.requiredMacOSVersion {
|
||||
|
|
@ -550,8 +551,8 @@ class AppState: ObservableObject {
|
|||
return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion)
|
||||
}
|
||||
|
||||
func install(id: Xcode.ID) {
|
||||
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
|
||||
func install(id: XcodeID) {
|
||||
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
|
||||
|
||||
installationPublishers[id] = signInIfNeeded()
|
||||
.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
|
||||
/// As of Nov 2022 this was returning a 403 forbidden
|
||||
func installWithoutLogin(id: Xcode.ID) {
|
||||
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
|
||||
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
|
||||
|
||||
installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
|
||||
.receive(on: DispatchQueue.main)
|
||||
|
|
@ -649,7 +650,7 @@ class AppState: ObservableObject {
|
|||
}
|
||||
|
||||
func cancelInstall(id: Xcode.ID) {
|
||||
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }
|
||||
guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
|
||||
|
||||
// Cancel the publisher
|
||||
installationPublishers[id] = nil
|
||||
|
|
@ -767,7 +768,7 @@ class AppState: ObservableObject {
|
|||
config.allowsRunningApplicationSubstitution = false
|
||||
NSWorkspace.shared.openApplication(at: path.url, configuration: config)
|
||||
default:
|
||||
Logger.appState.error("\(xcode.id) is not installed")
|
||||
Logger.appState.error("\(xcode.id.version) is not installed")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -863,7 +864,7 @@ class AppState: ObservableObject {
|
|||
// 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.
|
||||
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
|
||||
// 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.buildMetadataIdentifiers.isEmpty
|
||||
}) {
|
||||
adjustedAvailableXcodes[index].version = installedXcode.version
|
||||
adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -888,14 +889,21 @@ class AppState: ObservableObject {
|
|||
// Include this version if there's only one with this build identifier
|
||||
return availableXcodesWithIdenticalBuildIdentifiers.count == 1 ||
|
||||
// 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
|
||||
let installedXcode = installedXcodes.first(where: { installedXcode in
|
||||
availableXcode.version.isEquivalent(to: installedXcode.version)
|
||||
// if we want to have only specific Xcodes as selected instead of the Architecture Equivalent.
|
||||
// if availableXcode.architectures == nil {
|
||||
// return availableXcode.version.isEquivalent(to: installedXcode.version)
|
||||
// } else {
|
||||
// return availableXcode.xcodeID == installedXcode.xcodeID
|
||||
// }
|
||||
return availableXcode.version.isEquivalent(to: installedXcode.version)
|
||||
})
|
||||
|
||||
let identicalBuilds: [Version]
|
||||
let identicalBuilds: [XcodeID]
|
||||
let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes
|
||||
.filter {
|
||||
return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers &&
|
||||
|
|
@ -905,13 +913,13 @@ class AppState: ObservableObject {
|
|||
}
|
||||
// If this is the release version, add the identical builds to it
|
||||
if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty {
|
||||
identicalBuilds = [availableXcode.version] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.version)
|
||||
identicalBuilds = [availableXcode.xcodeID] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.xcodeID)
|
||||
} else {
|
||||
identicalBuilds = []
|
||||
}
|
||||
|
||||
// If the existing install state is "installing", keep it
|
||||
let existingXcodeInstallState = allXcodes.first { $0.version == availableXcode.version && $0.installState.installing }?.installState
|
||||
let existingXcodeInstallState = allXcodes.first { $0.id == availableXcode.xcodeID && $0.installState.installing }?.installState
|
||||
// Otherwise, determine it from whether there's an installed Xcode
|
||||
let defaultXcodeInstallState: XcodeInstallState = installedXcode.map { .installed($0.path) } ?? .notInstalled
|
||||
|
||||
|
|
@ -926,7 +934,8 @@ class AppState: ObservableObject {
|
|||
releaseDate: availableXcode.releaseDate,
|
||||
sdks: availableXcode.sdks,
|
||||
compilers: availableXcode.compilers,
|
||||
downloadFileSize: availableXcode.fileSize
|
||||
downloadFileSize: availableXcode.fileSize,
|
||||
architectures: availableXcode.architectures
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import Foundation
|
||||
import Version
|
||||
import struct XCModel.SDKs
|
||||
import struct XCModel.Compilers
|
||||
import XcodesKit
|
||||
|
||||
/// A version of Xcode that's available for installation
|
||||
public struct AvailableXcode: Codable {
|
||||
public var version: Version
|
||||
public var version: Version {
|
||||
return xcodeID.version
|
||||
}
|
||||
public let url: URL
|
||||
public let filename: String
|
||||
public let releaseDate: Date?
|
||||
|
|
@ -14,9 +15,11 @@ public struct AvailableXcode: Codable {
|
|||
public let sdks: SDKs?
|
||||
public let compilers: Compilers?
|
||||
public let fileSize: Int64?
|
||||
public let architectures: [Architecture]?
|
||||
public var downloadPath: String {
|
||||
return url.path
|
||||
}
|
||||
public var xcodeID: XcodeID
|
||||
|
||||
public init(
|
||||
version: Version,
|
||||
|
|
@ -27,9 +30,9 @@ public struct AvailableXcode: Codable {
|
|||
releaseNotesURL: URL? = nil,
|
||||
sdks: SDKs? = nil,
|
||||
compilers: Compilers? = nil,
|
||||
fileSize: Int64? = nil
|
||||
fileSize: Int64? = nil,
|
||||
architectures: [Architecture]? = nil
|
||||
) {
|
||||
self.version = version
|
||||
self.url = url
|
||||
self.filename = filename
|
||||
self.releaseDate = releaseDate
|
||||
|
|
@ -38,5 +41,7 @@ public struct AvailableXcode: Codable {
|
|||
self.sdks = sdks
|
||||
self.compilers = compilers
|
||||
self.fileSize = fileSize
|
||||
self.architectures = architectures
|
||||
self.xcodeID = XcodeID(version: version, architectures: architectures)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ public struct Shell {
|
|||
return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"])
|
||||
}
|
||||
|
||||
public var downloadRuntime: (String, String) -> AsyncThrowingStream<Progress, Error> = { platform, version in
|
||||
public var downloadRuntime: (String, String, String?) -> AsyncThrowingStream<Progress, Error> = { platform, version, architecture in
|
||||
return AsyncThrowingStream<Progress, Error> { 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()
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
import Foundation
|
||||
import Version
|
||||
import Path
|
||||
import XcodesKit
|
||||
|
||||
/// A version of Xcode that's already installed
|
||||
public struct InstalledXcode: Equatable {
|
||||
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
|
||||
public let version: Version
|
||||
public var version: Version {
|
||||
return xcodeID.version
|
||||
}
|
||||
|
||||
public init?(path: Path) {
|
||||
self.path = path
|
||||
|
|
@ -31,12 +36,21 @@ public struct InstalledXcode: Equatable {
|
|||
else if infoPlist.bundleIconName == "XcodeBeta", !prereleaseIdentifiers.contains("beta") {
|
||||
prereleaseIdentifiers = ["beta"]
|
||||
}
|
||||
|
||||
self.version = Version(major: bundleVersion.major,
|
||||
|
||||
let archsString = try? XcodesKit.Current.shell.archs(path.url.appending(path: "Contents/MacOS/Xcode")).out
|
||||
|
||||
let architectures = archsString?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.split(separator: " ")
|
||||
.compactMap { Architecture(rawValue: String($0)) }
|
||||
|
||||
let version = Version(major: bundleVersion.major,
|
||||
minor: bundleVersion.minor,
|
||||
patch: bundleVersion.patch,
|
||||
prereleaseIdentifiers: prereleaseIdentifiers,
|
||||
buildMetadataIdentifiers: [versionPlist.productBuildVersion].compactMap { $0 })
|
||||
|
||||
self.xcodeID = XcodeID(version: version, architectures: architectures)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import struct XCModel.SDKs
|
||||
import XcodesKit
|
||||
import SwiftUI
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import Version
|
||||
import struct XCModel.Xcode
|
||||
import XcodesKit
|
||||
|
||||
extension Version {
|
||||
/// 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.
|
||||
init?(xcReleasesXcode: XCModel.Xcode) {
|
||||
init?(xcReleasesXcode: XcodeRelease) {
|
||||
var versionString = xcReleasesXcode.version.number ?? ""
|
||||
|
||||
// Append trailing ".0" in order to get a fully-specified version string
|
||||
|
|
|
|||
|
|
@ -1,14 +1,30 @@
|
|||
import AppKit
|
||||
import Foundation
|
||||
import Version
|
||||
import struct XCModel.SDKs
|
||||
import struct XCModel.Compilers
|
||||
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 {
|
||||
let version: Version
|
||||
var version: Version {
|
||||
return id.version
|
||||
}
|
||||
/// Other Xcode versions that have the same build identifier
|
||||
let identicalBuilds: [Version]
|
||||
let identicalBuilds: [XcodeID]
|
||||
var installState: XcodeInstallState
|
||||
let selected: Bool
|
||||
let icon: NSImage?
|
||||
|
|
@ -18,10 +34,12 @@ struct Xcode: Identifiable, CustomStringConvertible {
|
|||
let sdks: SDKs?
|
||||
let compilers: Compilers?
|
||||
let downloadFileSize: Int64?
|
||||
let architectures: [Architecture]?
|
||||
let id: XcodeID
|
||||
|
||||
init(
|
||||
version: Version,
|
||||
identicalBuilds: [Version] = [],
|
||||
identicalBuilds: [XcodeID] = [],
|
||||
installState: XcodeInstallState,
|
||||
selected: Bool,
|
||||
icon: NSImage?,
|
||||
|
|
@ -30,9 +48,9 @@ struct Xcode: Identifiable, CustomStringConvertible {
|
|||
releaseDate: Date? = nil,
|
||||
sdks: SDKs? = nil,
|
||||
compilers: Compilers? = nil,
|
||||
downloadFileSize: Int64? = nil
|
||||
downloadFileSize: Int64? = nil,
|
||||
architectures: [Architecture]? = nil
|
||||
) {
|
||||
self.version = version
|
||||
self.identicalBuilds = identicalBuilds
|
||||
self.installState = installState
|
||||
self.selected = selected
|
||||
|
|
@ -43,10 +61,10 @@ struct Xcode: Identifiable, CustomStringConvertible {
|
|||
self.sdks = sdks
|
||||
self.compilers = compilers
|
||||
self.downloadFileSize = downloadFileSize
|
||||
self.architectures = architectures
|
||||
self.id = XcodeID(version: version, architectures: architectures)
|
||||
}
|
||||
|
||||
var id: Version { version }
|
||||
|
||||
var description: String {
|
||||
version.appleDescription
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import struct XCModel.Compilers
|
||||
import XcodesKit
|
||||
|
||||
struct CompilersView: View {
|
||||
let compilers: Compilers?
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ import XcodesKit
|
|||
import Path
|
||||
import SwiftUI
|
||||
import Version
|
||||
import struct XCModel.Compilers
|
||||
import struct XCModel.SDKs
|
||||
|
||||
struct InfoPane: View {
|
||||
let xcode: Xcode
|
||||
|
|
@ -42,7 +40,7 @@ struct InfoPane: View {
|
|||
VStack(alignment: .leading) {
|
||||
ReleaseDateView(date: xcode.releaseDate, url: xcode.releaseNotesURL)
|
||||
CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion)
|
||||
IdenticalBuildsView(builds: xcode.identicalBuilds)
|
||||
IdenticalBuildsView(builds: xcode.identicalBuilds.map { $0.version })
|
||||
SDKandCompilers
|
||||
}
|
||||
.frame(width: 200)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import Version
|
||||
import XCModel
|
||||
import XcodesKit
|
||||
import Path
|
||||
|
||||
struct InstalledStateButtons: View {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import Version
|
|||
|
||||
struct NotInstalledStateButtons: View {
|
||||
let downloadFileSizeString: String?
|
||||
let id: Version
|
||||
let id: XcodeID
|
||||
|
||||
@EnvironmentObject var appState: AppState
|
||||
|
||||
|
|
@ -20,7 +20,11 @@ struct NotInstalledStateButtons: View {
|
|||
Button {
|
||||
appState.checkMinVersionAndInstall(id: id)
|
||||
} label: {
|
||||
Text("Install") .help("Install")
|
||||
if id.architectures?.isAppleSilicon ?? false {
|
||||
Text("Install Apple Silicon").help("Install")
|
||||
} else {
|
||||
Text("Install Universal").help("Install")
|
||||
}
|
||||
}
|
||||
|
||||
if let size = downloadFileSizeString {
|
||||
|
|
@ -38,7 +42,7 @@ struct NotInstalledStateButtons: View {
|
|||
#Preview {
|
||||
NotInstalledStateButtons(
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.displayString, systemImage: "m4.button.horizontal")
|
||||
.labelStyle(.trailingIcon)
|
||||
case .x86_64:
|
||||
Label(selectedRuntimeArchitecture.displayValue, systemImage: "cpu.fill")
|
||||
Label(selectedRuntimeArchitecture.displayString, 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.displayString)
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import struct XCModel.SDKs
|
||||
import XcodesKit
|
||||
|
||||
struct SDKsView: View {
|
||||
let content: String
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ struct MainWindow: View {
|
|||
// FB8979533 SceneStorage doesn't restore value after app is quit by user
|
||||
@AppStorage("isShowingInfoPane") private var isShowingInfoPane = false
|
||||
@AppStorage("xcodeListCategory") private var category: XcodeListCategory = .all
|
||||
@AppStorage("xcodeListArchitecture") private var architecture: XcodeListArchitecture = .universal
|
||||
@AppStorage("isInstalledOnly") private var isInstalledOnly = false
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitViewWrapper {
|
||||
XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly)
|
||||
XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly, architecture: architecture)
|
||||
.layoutPriority(1)
|
||||
.alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in
|
||||
Alert(title: Text(String(format: localizeString("Alert.Uninstall.Title"), xcode.description)),
|
||||
|
|
@ -31,7 +32,8 @@ struct MainWindow: View {
|
|||
.mainToolbar(
|
||||
category: $category,
|
||||
isInstalledOnly: $isInstalledOnly,
|
||||
isShowingInfoPane: $isShowingInfoPane
|
||||
isShowingInfoPane: $isShowingInfoPane,
|
||||
architecture: $architecture
|
||||
)
|
||||
} detail: {
|
||||
Group {
|
||||
|
|
@ -191,11 +193,11 @@ struct MainWindow: View {
|
|||
case let .checkMinSupportedVersion(xcode, deviceVersion):
|
||||
return Alert(
|
||||
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(
|
||||
Text("Install"),
|
||||
action: {
|
||||
self.appState.install(id: xcode.version)
|
||||
self.appState.install(id: xcode.xcodeID)
|
||||
}
|
||||
),
|
||||
secondaryButton: .cancel(Text("Cancel"))
|
||||
|
|
@ -223,7 +225,7 @@ struct MainWindow_Previews: PreviewProvider {
|
|||
MainWindow().environmentObject({ () -> AppState in
|
||||
let a = AppState()
|
||||
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.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),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ struct MainToolbarModifier: ViewModifier {
|
|||
@Binding var category: XcodeListCategory
|
||||
@Binding var isInstalledOnly: Bool
|
||||
@Binding var isShowingInfoPane: Bool
|
||||
@Binding var architectures: XcodeListArchitecture
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
|
|
@ -23,6 +24,24 @@ struct MainToolbarModifier: ViewModifier {
|
|||
.help("RefreshDescription")
|
||||
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: {
|
||||
switch category {
|
||||
case .all: category = .release
|
||||
|
|
@ -65,13 +84,15 @@ extension View {
|
|||
func mainToolbar(
|
||||
category: Binding<XcodeListCategory>,
|
||||
isInstalledOnly: Binding<Bool>,
|
||||
isShowingInfoPane: Binding<Bool>
|
||||
isShowingInfoPane: Binding<Bool>,
|
||||
architecture: Binding<XcodeListArchitecture>
|
||||
) -> some View {
|
||||
modifier(
|
||||
MainToolbarModifier(
|
||||
category: category,
|
||||
isInstalledOnly: isInstalledOnly,
|
||||
isShowingInfoPane: isShowingInfoPane
|
||||
isShowingInfoPane: isShowingInfoPane,
|
||||
architectures: architecture
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Foundation
|
||||
import XcodesKit
|
||||
|
||||
enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConvertible {
|
||||
case all
|
||||
|
|
@ -17,3 +18,19 @@ enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConverti
|
|||
|
||||
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?
|
||||
private let searchText: String
|
||||
private let category: XcodeListCategory
|
||||
private let architecture: XcodeListArchitecture
|
||||
private let isInstalledOnly: Bool
|
||||
@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.searchText = searchText
|
||||
self.category = category
|
||||
self.isInstalledOnly = isInstalledOnly
|
||||
self.architecture = architecture
|
||||
}
|
||||
|
||||
var visibleXcodes: [Xcode] {
|
||||
|
|
@ -28,6 +30,11 @@ struct XcodeListView: View {
|
|||
xcodes = appState.allXcodes.filter { $0.version.isPrerelease }
|
||||
}
|
||||
|
||||
if architecture == .appleSilicon {
|
||||
xcodes = xcodes.filter { $0.architectures == [.arm64] }
|
||||
}
|
||||
|
||||
|
||||
let latestMajor = xcodes.sorted(\.version)
|
||||
.filter { $0.version.isNotPrerelease }
|
||||
.last?
|
||||
|
|
@ -95,11 +102,11 @@ struct PlatformsPocket: View {
|
|||
struct XcodeListView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
XcodeListView(selectedXcodeID: .constant(nil), searchText: "", category: .all, isInstalledOnly: false)
|
||||
XcodeListView(selectedXcodeID: .constant(nil), searchText: "", category: .all, isInstalledOnly: false, architecture: .appleSilicon)
|
||||
.environmentObject({ () -> AppState in
|
||||
let a = AppState()
|
||||
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.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),
|
||||
|
|
|
|||
|
|
@ -21,9 +21,17 @@ struct XcodeListViewRow: View {
|
|||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.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")
|
||||
}
|
||||
|
||||
if xcode.architectures?.isAppleSilicon ?? false {
|
||||
Image(systemName: "m4.button.horizontal")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.accessibility(label: Text("Apple Silicon"))
|
||||
.help("Apple Silicon")
|
||||
}
|
||||
}
|
||||
|
||||
if case let .installed(path) = xcode.installState {
|
||||
|
|
@ -156,7 +164,7 @@ struct XcodeListViewRow_Previews: PreviewProvider {
|
|||
)
|
||||
|
||||
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,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -2083,6 +2083,131 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Alert.Install.Error.Need.Xcode26" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"ca" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"el" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"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",
|
||||
|
|
@ -10478,6 +10603,242 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Install Apple Silicon" : {
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"ca" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"el" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Apple Silicon"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Install Universal" : {
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"ca" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"el" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install Universal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"InstallationError.CodesignVerifyFailed" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
|
|
@ -22103,6 +22464,124 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Universal" : {
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"ca" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"el" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Universal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"UnxipExperiment" : {
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// 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, Identifiable {
|
||||
public var id: Self { self }
|
||||
|
||||
/// The Arm64 architecture (Apple Silicon)
|
||||
case arm64 = "arm64"
|
||||
/// The X86\_64 architecture (64-bit Intel)
|
||||
case x86_64 = "x86_64"
|
||||
|
||||
public var displayString: String {
|
||||
switch self {
|
||||
case .arm64:
|
||||
return "Apple Silicon"
|
||||
case .x86_64:
|
||||
return "Intel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == Architecture {
|
||||
public var isAppleSilicon: Bool {
|
||||
self == [.arm64]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,14 @@ public typealias ProcessOutput = (status: Int32, out: String, err: String)
|
|||
|
||||
extension Process {
|
||||
static func run(_ executable: Path, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) async throws -> ProcessOutput {
|
||||
return try await run(executable.url, workingDirectory: workingDirectory, input: input, arguments)
|
||||
return try run(executable.url, workingDirectory: workingDirectory, input: input, arguments)
|
||||
}
|
||||
|
||||
static func run(_ executable: URL, workingDirectory: URL? = nil, input: String? = nil, _ arguments: [String]) async throws -> ProcessOutput {
|
||||
static func run(_ executable: Path, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) throws -> ProcessOutput {
|
||||
return try run(executable.url, workingDirectory: workingDirectory, input: input, arguments)
|
||||
}
|
||||
|
||||
static func run(_ executable: URL, workingDirectory: URL? = nil, input: String? = nil, _ arguments: [String]) throws -> ProcessOutput {
|
||||
|
||||
let process = Process()
|
||||
process.currentDirectoryURL = workingDirectory ?? executable.deletingLastPathComponent()
|
||||
|
|
|
|||
|
|
@ -26,4 +26,8 @@ public struct XcodesShell {
|
|||
public var deleteRuntime: (String) async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.bin.join("xcrun"), "simctl", "runtime", "delete", $0)
|
||||
}
|
||||
|
||||
public var archs: (URL) throws -> ProcessOutput = {
|
||||
try Process.run(Path.root.usr.bin.join("lipo"), "-archs", $0.path)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ class AppStateUpdateTests: XCTestCase {
|
|||
)
|
||||
|
||||
XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0+12D4e")!])
|
||||
XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[Version("12.4.0+12D4e")!, Version("12.4.0-RC+12D4e")!]])
|
||||
XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[XcodeID(version: Version("12.4.0+12D4e")!), XcodeID(version: Version("12.4.0-RC+12D4e")!)]])
|
||||
}
|
||||
|
||||
func testIdenticalBuilds_DoNotMergeReleaseVersions() {
|
||||
|
|
@ -234,7 +234,7 @@ class AppStateUpdateTests: XCTestCase {
|
|||
)
|
||||
|
||||
XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0+12D4e")!])
|
||||
XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[Version("12.4.0+12D4e")!, Version("12.4.0-RC+12D4e")!]])
|
||||
XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[XcodeID(version: Version("12.4.0+12D4e")!), XcodeID(version: Version("12.4.0-RC+12D4e")!)]])
|
||||
}
|
||||
|
||||
func testIdenticalBuilds_AppleDataSource_DoNotMergeVersionsWithoutBuildIdentifiers() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue