mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
more runtime download work
This commit is contained in:
parent
4f25905f4c
commit
7325502853
16 changed files with 269 additions and 77 deletions
|
|
@ -1054,7 +1054,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
|
||||
PRODUCT_NAME = Xcodes;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -1286,7 +1286,7 @@
|
|||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = PBH8V487HB;
|
||||
DEVELOPMENT_TEAM = ZU6GR6B2FY;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = Xcodes/Resources/Info.plist;
|
||||
|
|
@ -1295,7 +1295,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
|
||||
PRODUCT_NAME = Xcodes;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
|
@ -1310,7 +1310,7 @@
|
|||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = PBH8V487HB;
|
||||
DEVELOPMENT_TEAM = ZU6GR6B2FY;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = Xcodes/Resources/Info.plist;
|
||||
|
|
@ -1319,7 +1319,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
|
||||
PRODUCT_NAME = Xcodes;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
|
@ -1417,8 +1417,8 @@
|
|||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/xcodereleases/data";
|
||||
requirement = {
|
||||
kind = revision;
|
||||
revision = b47228c688b608e34b3b84079ab6052a24c7a981;
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */ = {
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
"package": "XcodeReleases",
|
||||
"repositoryURL": "https://github.com/xcodereleases/data",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b47228c688b608e34b3b84079ab6052a24c7a981",
|
||||
"branch": "main",
|
||||
"revision": "a43ad89e536d7a3da525fcc23fb182c37b756ecc",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -466,6 +466,15 @@ extension AppState {
|
|||
let xcode = self.allXcodes[index]
|
||||
Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal)
|
||||
}
|
||||
}w func setInstallationStep(of runtime: DownloadableRuntime, to step: InstallationStep) {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
|
||||
self.downloadableRuntimes[index].installState = .installing(step)
|
||||
|
||||
// let xcode = self.allXcodes[index]
|
||||
// Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,27 @@
|
|||
import Foundation
|
||||
import XcodesKit
|
||||
import OSLog
|
||||
import Combine
|
||||
import Path
|
||||
import AppleAPI
|
||||
|
||||
extension AppState {
|
||||
func updateDownloadableRuntimes() {
|
||||
Task {
|
||||
do {
|
||||
let runtimes = try await self.runtimeService.downloadableRuntimes().downloadables
|
||||
|
||||
let downloadableRuntimes = try await self.runtimeService.downloadableRuntimes()
|
||||
let runtimes = downloadableRuntimes.downloadables.map { runtime in
|
||||
var updatedRuntime = runtime
|
||||
|
||||
// This loops through and matches up the simulatorVersion to the mappings
|
||||
let simulatorBuildUpdate = downloadableRuntimes.sdkToSimulatorMappings.first { SDKToSimulatorMapping in
|
||||
SDKToSimulatorMapping.simulatorBuildUpdate == runtime.simulatorVersion.buildUpdate
|
||||
}
|
||||
updatedRuntime.sdkBuildUpdate = simulatorBuildUpdate?.sdkBuildUpdate
|
||||
return updatedRuntime
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.downloadableRuntimes = runtimes
|
||||
}
|
||||
|
|
@ -29,4 +44,111 @@ extension AppState {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloadRuntime(runtime: DownloadableRuntime) {
|
||||
self.runtimePublishers[runtime.identifier] = downloadRunTimeFull(runtime: runtime)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(
|
||||
receiveCompletion: { [unowned self] completion in
|
||||
self.runtimePublishers[runtime.identifier] = nil
|
||||
if case let .failure(error) = completion {
|
||||
// // Prevent setting the app state error if it is an invalid session, we will present the sign in view instead
|
||||
// if error as? AuthenticationError != .invalidSession {
|
||||
// self.error = error
|
||||
// self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
|
||||
// }
|
||||
// if let index = self.allXcodes.firstIndex(where: { $0.id == id }) {
|
||||
// self.allXcodes[index].installState = .notInstalled
|
||||
// }
|
||||
}
|
||||
},
|
||||
receiveValue: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
func downloadRunTimeFull(runtime: DownloadableRuntime) -> AnyPublisher<(DownloadableRuntime, URL), Error> {
|
||||
// gets a proper cookie for runtimes
|
||||
|
||||
return validateADCSession(path: runtime.downloadPath)
|
||||
.flatMap { _ in
|
||||
// we shouldn't have to be authenticated to download runtimes
|
||||
let downloader = Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2
|
||||
Logger.appState.info("Downloading \(runtime.visibleIdentifier) with \(downloader)")
|
||||
|
||||
return self.downloadRuntime(for: runtime, downloader: downloader, progressChanged: { [unowned self] progress in
|
||||
DispatchQueue.main.async {
|
||||
self.setInstallationStep(of: runtime, to: .downloading(progress: progress))
|
||||
}
|
||||
})
|
||||
.map { return (runtime, $0) }
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func downloadRuntime(for runtime: DownloadableRuntime, downloader: Downloader, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
|
||||
// Check to see if the dmg is in the expected path in case it was downloaded but failed to install
|
||||
|
||||
// call https://developerservices2.apple.com/services/download?path=/Developer_Tools/watchOS_10_beta/watchOS_10_beta_Simulator_Runtime.dmg 1st to get cookie
|
||||
// use runtime.url for final with cookies
|
||||
|
||||
// Check to see if the archive is in the expected path in case it was downloaded but failed to install
|
||||
let expectedRuntimePath = Path.xcodesApplicationSupport/"\(runtime.name).\(runtime.name.suffix(fromLast: "."))"
|
||||
// aria2 downloads directly to the destination (instead of into /tmp first) so we need to make sure that the download isn't incomplete
|
||||
let aria2DownloadMetadataPath = expectedRuntimePath.parent/(expectedRuntimePath.basename() + ".aria2")
|
||||
var aria2DownloadIsIncomplete = false
|
||||
if case .aria2 = downloader, aria2DownloadMetadataPath.exists {
|
||||
aria2DownloadIsIncomplete = true
|
||||
}
|
||||
if Current.files.fileExistsAtPath(expectedRuntimePath.string), aria2DownloadIsIncomplete == false {
|
||||
Logger.appState.info("Found existing runtime that will be used for installation at \(expectedRuntimePath).")
|
||||
return Just(expectedRuntimePath.url)
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
else {
|
||||
// let destination = Path.xcodesApplicationSupport/"Xcode-\(availableXcode.version).\(availableXcode.filename.suffix(fromLast: "."))"
|
||||
switch downloader {
|
||||
case .aria2:
|
||||
let aria2Path = Path(url: Bundle.main.url(forAuxiliaryExecutable: "aria2c")!)!
|
||||
return downloadRuntimeWithAria2(
|
||||
runtime,
|
||||
to: expectedRuntimePath,
|
||||
aria2Path: aria2Path,
|
||||
progressChanged: progressChanged)
|
||||
// return downloadXcodeWithAria2(
|
||||
// availableXcode,
|
||||
// to: destination,
|
||||
// aria2Path: aria2Path,
|
||||
// progressChanged: progressChanged
|
||||
// )
|
||||
case .urlSession:
|
||||
|
||||
return Just(runtime.url)
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
// return downloadXcodeWithURLSession(
|
||||
// availableXcode,
|
||||
// to: destination,
|
||||
// progressChanged: progressChanged
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func downloadRuntimeWithAria2(_ runtime: DownloadableRuntime, to destination: Path, aria2Path: Path, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
|
||||
let cookies = AppleAPI.Current.network.session.configuration.httpCookieStorage?.cookies(for: runtime.url) ?? []
|
||||
|
||||
let (progress, publisher) = Current.shell.downloadWithAria2(
|
||||
aria2Path,
|
||||
runtime.url,
|
||||
destination,
|
||||
cookies
|
||||
)
|
||||
progressChanged(progress)
|
||||
return publisher
|
||||
.map { _ in destination.url }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,12 +105,13 @@ class AppState: ObservableObject {
|
|||
// MARK: - Runtimes
|
||||
|
||||
@Published var downloadableRuntimes: [DownloadableRuntime] = []
|
||||
@Published var installedRuntimes: [CoreSimulatorRuntimeInfo] = []
|
||||
@Published var installedRuntimes: [CoreSimulatorImage] = []
|
||||
|
||||
// MARK: - Publisher Cancellables
|
||||
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
private var installationPublishers: [Version: AnyCancellable] = [:]
|
||||
internal var runtimePublishers: [String: AnyCancellable] = [:]
|
||||
private var selectPublisher: AnyCancellable?
|
||||
private var uninstallPublisher: AnyCancellable?
|
||||
private var autoInstallTimer: Timer?
|
||||
|
|
@ -148,6 +149,7 @@ class AppState: ObservableObject {
|
|||
checkIfHelperIsInstalled()
|
||||
setupAutoInstallTimer()
|
||||
setupDefaults()
|
||||
updateInstalledRuntimes()
|
||||
}
|
||||
|
||||
func setupDefaults() {
|
||||
|
|
@ -175,7 +177,11 @@ class AppState: ObservableObject {
|
|||
func validateADCSession(path: String) -> AnyPublisher<Void, Error> {
|
||||
return Current.network.dataTask(with: URLRequest.downloadADCAuth(path: path))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.tryMap { _ in
|
||||
.tryMap { result -> Void in
|
||||
let httpResponse = result.response as! HTTPURLResponse
|
||||
if httpResponse.statusCode == 401 {
|
||||
throw AuthenticationError.notAuthorized
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
|
@ -796,16 +802,27 @@ class AppState: ObservableObject {
|
|||
func getRunTimes(xcode: Xcode) -> [DownloadableRuntime] {
|
||||
|
||||
let builds = xcode.sdks?.allBuilds()
|
||||
|
||||
let runtime = builds?.flatMap { sdkBuild in
|
||||
|
||||
let runtimes: [DownloadableRuntime]? = builds?.flatMap { sdkBuild in
|
||||
downloadableRuntimes.filter {
|
||||
$0.simulatorVersion.buildUpdate == sdkBuild
|
||||
$0.sdkBuildUpdate == sdkBuild
|
||||
}
|
||||
}
|
||||
// appState.installedRuntimes has a list of builds that user has installed.
|
||||
|
||||
return runtime ?? []
|
||||
|
||||
let updatedRuntimes = runtimes?.map { runtime in
|
||||
var updatedRuntime = runtime
|
||||
if let coreSimulatorInfo = installedRuntimes.filter({ $0.runtimeInfo.build == runtime.sdkBuildUpdate }).first {
|
||||
let url = URL(fileURLWithPath: coreSimulatorInfo.path["relative"]!)
|
||||
updatedRuntime.installState = .installed(Path(url: url)!)
|
||||
} else {
|
||||
updatedRuntime.installState = .notInstalled
|
||||
}
|
||||
return updatedRuntime
|
||||
}
|
||||
|
||||
return updatedRuntimes ?? []
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
|
|
|
|||
|
|
@ -1,45 +1,45 @@
|
|||
import Foundation
|
||||
|
||||
/// A numbered step
|
||||
enum InstallationStep: Equatable, CustomStringConvertible {
|
||||
case downloading(progress: Progress)
|
||||
case unarchiving
|
||||
case moving(destination: String)
|
||||
case trashingArchive
|
||||
case checkingSecurity
|
||||
case finishing
|
||||
|
||||
var description: String {
|
||||
"(\(stepNumber)/\(stepCount)) \(message)"
|
||||
}
|
||||
|
||||
var message: String {
|
||||
switch self {
|
||||
case .downloading:
|
||||
return localizeString("Downloading")
|
||||
case .unarchiving:
|
||||
return localizeString("Unarchiving")
|
||||
case .moving(let destination):
|
||||
return String(format: localizeString("Moving"), destination)
|
||||
case .trashingArchive:
|
||||
return localizeString("TrashingArchive")
|
||||
case .checkingSecurity:
|
||||
return localizeString("CheckingSecurity")
|
||||
case .finishing:
|
||||
return localizeString("Finishing")
|
||||
}
|
||||
}
|
||||
|
||||
var stepNumber: Int {
|
||||
switch self {
|
||||
case .downloading: return 1
|
||||
case .unarchiving: return 2
|
||||
case .moving: return 3
|
||||
case .trashingArchive: return 4
|
||||
case .checkingSecurity: return 5
|
||||
case .finishing: return 6
|
||||
}
|
||||
}
|
||||
|
||||
var stepCount: Int { 6 }
|
||||
}
|
||||
//import Foundation
|
||||
//
|
||||
///// A numbered step
|
||||
//enum InstallationStep: Equatable, CustomStringConvertible {
|
||||
// case downloading(progress: Progress)
|
||||
// case unarchiving
|
||||
// case moving(destination: String)
|
||||
// case trashingArchive
|
||||
// case checkingSecurity
|
||||
// case finishing
|
||||
//
|
||||
// var description: String {
|
||||
// "(\(stepNumber)/\(stepCount)) \(message)"
|
||||
// }
|
||||
//
|
||||
// var message: String {
|
||||
// switch self {
|
||||
// case .downloading:
|
||||
// return localizeString("Downloading")
|
||||
// case .unarchiving:
|
||||
// return localizeString("Unarchiving")
|
||||
// case .moving(let destination):
|
||||
// return String(format: localizeString("Moving"), destination)
|
||||
// case .trashingArchive:
|
||||
// return localizeString("TrashingArchive")
|
||||
// case .checkingSecurity:
|
||||
// return localizeString("CheckingSecurity")
|
||||
// case .finishing:
|
||||
// return localizeString("Finishing")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var stepNumber: Int {
|
||||
// switch self {
|
||||
// case .downloading: return 1
|
||||
// case .unarchiving: return 2
|
||||
// case .moving: return 3
|
||||
// case .trashingArchive: return 4
|
||||
// case .checkingSecurity: return 5
|
||||
// case .finishing: return 6
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var stepCount: Int { 6 }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ extension SDKs {
|
|||
if let watchOS = self.watchOS?.compactMap({ $0.build }) {
|
||||
buildNumbers += watchOS
|
||||
}
|
||||
if let visionOS = self.visionOS?.compactMap({ $0.build }) {
|
||||
buildNumbers += visionOS
|
||||
}
|
||||
|
||||
return buildNumbers
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ struct DownloadRuntimeButton: View {
|
|||
|
||||
private func install() {
|
||||
guard let runtime = runtime else { return }
|
||||
// appState.checkMinVersionAndInstall(id: xcode.id)
|
||||
appState.downloadRuntime(runtime: runtime)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Foundation
|
||||
import Path
|
||||
import XcodesKit
|
||||
|
||||
enum XcodeInstallState: Equatable {
|
||||
case notInstalled
|
||||
|
|
|
|||
|
|
@ -257,13 +257,27 @@ struct InfoPane: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
ForEach(runtimes, id: \.simulatorVersion.buildUpdate) { runtime in
|
||||
HStack {
|
||||
Text("\(runtime.visibleIdentifier)")
|
||||
.font(.subheadline)
|
||||
Spacer()
|
||||
Text(runtime.downloadFileSizeString)
|
||||
.font(.subheadline)
|
||||
DownloadRuntimeButton(runtime: runtime)
|
||||
VStack {
|
||||
HStack {
|
||||
Text("\(runtime.visibleIdentifier)")
|
||||
.font(.subheadline)
|
||||
Spacer()
|
||||
Text(runtime.downloadFileSizeString)
|
||||
.font(.subheadline)
|
||||
DownloadRuntimeButton(runtime: runtime)
|
||||
}
|
||||
switch runtime.installState {
|
||||
|
||||
|
||||
case .notInstalled:
|
||||
Text("NOT INSTALLED")
|
||||
case .installing(let installationStep):
|
||||
Text("INSTALLING")
|
||||
InstallationStepDetailView(installationStep: installationStep)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
case .installed(let path):
|
||||
Text(path.string)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import SwiftUI
|
||||
import XcodesKit
|
||||
|
||||
struct InstallationStepDetailView: View {
|
||||
let installationStep: InstallationStep
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import SwiftUI
|
||||
import XcodesKit
|
||||
|
||||
struct InstallationStepRowView: View {
|
||||
let installationStep: InstallationStep
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public enum InstallationStep: Equatable, CustomStringConvertible {
|
|||
"(\(stepNumber)/\(stepCount)) \(message)"
|
||||
}
|
||||
|
||||
var message: String {
|
||||
public var message: String {
|
||||
switch self {
|
||||
case .downloading:
|
||||
return localizeString("Downloading")
|
||||
|
|
@ -37,7 +37,7 @@ public enum InstallationStep: Equatable, CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
var stepNumber: Int {
|
||||
public var stepNumber: Int {
|
||||
switch self {
|
||||
case .downloading: return 1
|
||||
case .unarchiving: return 2
|
||||
|
|
@ -48,7 +48,7 @@ public enum InstallationStep: Equatable, CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
var stepCount: Int { 6 }
|
||||
public var stepCount: Int { 6 }
|
||||
}
|
||||
func localizeString(_ key: String, comment: String = "") -> String {
|
||||
if #available(macOS 12, *) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public struct CoreSimulatorPlist: Decodable {
|
|||
|
||||
public struct CoreSimulatorImage: Decodable {
|
||||
public let uuid: String
|
||||
public let path: [String: String]
|
||||
public let runtimeInfo: CoreSimulatorRuntimeInfo
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,16 @@ public struct DownloadableRuntime: Codable {
|
|||
public let hostRequirements: HostRequirements?
|
||||
public let name: String
|
||||
public let authentication: Authentication?
|
||||
public var url: URL {
|
||||
return URL(string: source)!
|
||||
}
|
||||
public var downloadPath: String {
|
||||
url.path
|
||||
}
|
||||
|
||||
// dynamically updated - not decoded
|
||||
public var installState: InstallState = .notInstalled
|
||||
public var sdkBuildUpdate: String?
|
||||
|
||||
enum CodingKeys: CodingKey {
|
||||
case category
|
||||
|
|
@ -38,6 +45,7 @@ public struct DownloadableRuntime: Codable {
|
|||
case hostRequirements
|
||||
case name
|
||||
case authentication
|
||||
case sdkBuildUpdate
|
||||
}
|
||||
|
||||
var betaNumber: Int? {
|
||||
|
|
@ -108,13 +116,15 @@ extension DownloadableRuntime {
|
|||
case macOS = "com.apple.platform.macosx"
|
||||
case watchOS = "com.apple.platform.watchos"
|
||||
case tvOS = "com.apple.platform.appletvos"
|
||||
|
||||
case visionOS = "com.apple.platform.xros"
|
||||
|
||||
var order: Int {
|
||||
switch self {
|
||||
case .iOS: return 1
|
||||
case .macOS: return 2
|
||||
case .watchOS: return 3
|
||||
case .tvOS: return 4
|
||||
case .visionOS: return 5
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,6 +134,7 @@ extension DownloadableRuntime {
|
|||
case .macOS: return "macOS"
|
||||
case .watchOS: return "watchOS"
|
||||
case .tvOS: return "tvOS"
|
||||
case .visionOS: return "visionOS"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,6 +167,16 @@ extension InstalledRuntime {
|
|||
case tvOS = "com.apple.platform.appletvsimulator"
|
||||
case iOS = "com.apple.platform.iphonesimulator"
|
||||
case watchOS = "com.apple.platform.watchsimulator"
|
||||
case visionOS = "com.apple.platform.xrsimulator"
|
||||
|
||||
var asPlatformOS: DownloadableRuntime.Platform {
|
||||
switch self {
|
||||
case .watchOS: return .watchOS
|
||||
case .iOS: return .iOS
|
||||
case .tvOS: return .tvOS
|
||||
case .visionOS: return .visionOS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,17 +38,19 @@ public struct RuntimeService {
|
|||
|
||||
/// Loops through `/Library/Developer/CoreSimulator/images/images.plist` which contains a list of downloaded Simuator Runtimes
|
||||
/// This is different then using `simctl` (`installedRuntimes()`) which only returns the installed runtimes for the selected xcode version.
|
||||
public func localInstalledRuntimes() async throws -> [CoreSimulatorRuntimeInfo] {
|
||||
public func localInstalledRuntimes() async throws -> [CoreSimulatorImage] {
|
||||
guard let path = Path("/Library/Developer/CoreSimulator/images/images.plist") else { throw "Could not find images.plist for CoreSimulators" }
|
||||
guard let infoPlistData = FileManager.default.contents(atPath: path.string) else { throw "Could not get data from \(path.string)" }
|
||||
|
||||
do {
|
||||
let infoPlist: CoreSimulatorPlist = try PropertyListDecoder().decode(CoreSimulatorPlist.self, from: infoPlistData)
|
||||
return infoPlist.images.map { $0.runtimeInfo }
|
||||
return infoPlist.images
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue