From 3c6e47459cbca714b55b6a1538b2512f71d68ef3 Mon Sep 17 00:00:00 2001 From: Alvar Hansen Date: Wed, 12 Jun 2024 10:56:57 +0300 Subject: [PATCH] Quick fix to ignore new iOS 18 runtime downloadables With introduction if Xcode 16 and iOS 18 simulators, a breaking change for xcodes was introduced through the simulators API. ("https://devimages-cdn.apple.com/downloads/xcode/simulators/index2.dvtdownloadableindex") In order to keep current functionality working, I made 2 modifications: 1. Make runtime source optional. This is needed as iOS 18 downloadable does not have that value. 2. Add support for new `cryptexDiskImage` content type. As iOS 18 downloadable comes with new type of `contentType`, we should parse it in order to be able to process rest of the response. In RuntimeInstaller, I've added logic to handle new optional and new content type be throwing error if those downloadable were to be used. Test cases and fixtures were also updated to handle these new cases. --- Sources/XcodesKit/Models+Runtimes.swift | 3 +- Sources/XcodesKit/RuntimeInstaller.swift | 13 ++++++++- .../Fixtures/DownloadableRuntimes.plist | 29 ++++++++++++++++++- .../Fixtures/LogOutput-Runtimes.txt | 1 + Tests/XcodesKitTests/RuntimeTests.swift | 8 ++--- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/Sources/XcodesKit/Models+Runtimes.swift b/Sources/XcodesKit/Models+Runtimes.swift index 6463569..66f0636 100644 --- a/Sources/XcodesKit/Models+Runtimes.swift +++ b/Sources/XcodesKit/Models+Runtimes.swift @@ -11,7 +11,7 @@ struct DownloadableRuntimesResponse: Decodable { public struct DownloadableRuntime: Decodable { let category: Category let simulatorVersion: SimulatorVersion - let source: String + let source: String? let dictionaryVersion: Int let contentType: ContentType let platform: Platform @@ -79,6 +79,7 @@ extension DownloadableRuntime { enum ContentType: String, Decodable { case diskImage = "diskImage" case package = "package" + case cryptexDiskImage = "cryptexDiskImage" } enum Platform: String, Decodable { diff --git a/Sources/XcodesKit/RuntimeInstaller.swift b/Sources/XcodesKit/RuntimeInstaller.swift index cf0821c..34189e8 100644 --- a/Sources/XcodesKit/RuntimeInstaller.swift +++ b/Sources/XcodesKit/RuntimeInstaller.swift @@ -112,6 +112,8 @@ public class RuntimeInstaller { try await installFromPackage(dmgUrl: dmgUrl, runtime: matchedRuntime) case .diskImage: try await installFromImage(dmgUrl: dmgUrl) + case .cryptexDiskImage: + throw Error.unsupportedCryptexDiskImage } if shouldDelete { Current.logging.log("Deleting Archive") @@ -183,7 +185,10 @@ public class RuntimeInstaller { @MainActor public func downloadOrUseExistingArchive(runtime: DownloadableRuntime, to destinationDirectory: Path, downloader: Downloader) async throws -> URL { - let url = URL(string: runtime.source)! + guard let source = runtime.source else { + throw Error.missingRuntimeSource(runtime.identifier) + } + let url = URL(string: source)! let destination = destinationDirectory/url.lastPathComponent let aria2DownloadMetadataPath = destination.parent/(destination.basename() + ".aria2") var aria2DownloadIsIncomplete = false @@ -226,6 +231,8 @@ extension RuntimeInstaller { case unavailableRuntime(String) case failedMountingDMG case rootNeeded + case missingRuntimeSource(String) + case unsupportedCryptexDiskImage public var errorDescription: String? { switch self { @@ -235,6 +242,10 @@ extension RuntimeInstaller { return "Failed to mount image." case .rootNeeded: return "Must be run as root to install the specified runtime" + case let .missingRuntimeSource(identifier): + return "Runtime \(identifier) is missing source url." + case .unsupportedCryptexDiskImage: + return "Cryptex Disk Image is not yet supported." } } } diff --git a/Tests/XcodesKitTests/Fixtures/DownloadableRuntimes.plist b/Tests/XcodesKitTests/Fixtures/DownloadableRuntimes.plist index 4229254..46d8a23 100644 --- a/Tests/XcodesKitTests/Fixtures/DownloadableRuntimes.plist +++ b/Tests/XcodesKitTests/Fixtures/DownloadableRuntimes.plist @@ -1794,7 +1794,34 @@ version 1.0.0.1 - + + category + simulator + contentType + cryptexDiskImage + dictionaryVersion + 2 + downloadMethod + mobileAsset + fileSize + 8455760175 + identifier + com.apple.dmg.iPhoneSimulatorSDK18_0_b1 + name + iOS 18.0 beta Simulator Runtime + platform + com.apple.platform.iphoneos + simulatorVersion + + buildUpdate + 22A5282m + version + 18.0 + + version + 18.0.0.1 + + refreshInterval 86400 sdkToSeedMappings diff --git a/Tests/XcodesKitTests/Fixtures/LogOutput-Runtimes.txt b/Tests/XcodesKitTests/Fixtures/LogOutput-Runtimes.txt index 591f086..cedde68 100644 --- a/Tests/XcodesKitTests/Fixtures/LogOutput-Runtimes.txt +++ b/Tests/XcodesKitTests/Fixtures/LogOutput-Runtimes.txt @@ -25,6 +25,7 @@ iOS 16.2 iOS 16.4 iOS 17.0-beta1 iOS 17.0-beta2 +iOS 18.0-beta1 -- watchOS -- watchOS 6.0 watchOS 6.1.1 diff --git a/Tests/XcodesKitTests/RuntimeTests.swift b/Tests/XcodesKitTests/RuntimeTests.swift index 314761a..04f6c89 100644 --- a/Tests/XcodesKitTests/RuntimeTests.swift +++ b/Tests/XcodesKitTests/RuntimeTests.swift @@ -80,7 +80,7 @@ final class RuntimeTests: XCTestCase { func test_downloadableRuntimes() async throws { mockDownloadables() let values = try await runtimeList.downloadableRuntimes().downloadables - XCTAssertEqual(values.count, 59) + XCTAssertEqual(values.count, 60) } func test_downloadableRuntimesNoBetas() async throws { @@ -171,7 +171,7 @@ final class RuntimeTests: XCTestCase { } let url = try await runtimeInstaller.downloadOrUseExistingArchive(runtime: runtime, to: .xcodesCaches, downloader: .urlSession) - let fileName = URL(string: runtime.source)!.lastPathComponent + let fileName = URL(string: runtime.source!)!.lastPathComponent XCTAssertEqual(url, Path.xcodesCaches.join(fileName).url) XCTAssertNil(xcodeDownloadURL) } @@ -185,10 +185,10 @@ final class RuntimeTests: XCTestCase { return (Progress(), Promise.value((destination, HTTPURLResponse(url: url.pmkRequest.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!))) } let runtime = try await runtimeList.downloadableRuntimes().downloadables.first { $0.visibleIdentifier == "iOS 15.5" }! - let fileName = URL(string: runtime.source)!.lastPathComponent + let fileName = URL(string: runtime.source!)!.lastPathComponent let url = try await runtimeInstaller.downloadOrUseExistingArchive(runtime: runtime, to: .xcodesCaches, downloader: .urlSession) XCTAssertEqual(url, Path.xcodesCaches.join(fileName).url) - XCTAssertEqual(xcodeDownloadURL, URL(string: runtime.source)!) + XCTAssertEqual(xcodeDownloadURL, URL(string: runtime.source!)!) } func test_installStepsForPackage() async throws {