Skip to content

Commit

Permalink
Quick fix to ignore new iOS 18 runtime downloadables
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
alvar-bolt committed Jun 12, 2024
1 parent aa795f8 commit 3c6e474
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 7 deletions.
3 changes: 2 additions & 1 deletion Sources/XcodesKit/Models+Runtimes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -79,6 +79,7 @@ extension DownloadableRuntime {
enum ContentType: String, Decodable {
case diskImage = "diskImage"
case package = "package"
case cryptexDiskImage = "cryptexDiskImage"
}

enum Platform: String, Decodable {
Expand Down
13 changes: 12 additions & 1 deletion Sources/XcodesKit/RuntimeInstaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -226,6 +231,8 @@ extension RuntimeInstaller {
case unavailableRuntime(String)
case failedMountingDMG
case rootNeeded
case missingRuntimeSource(String)
case unsupportedCryptexDiskImage

public var errorDescription: String? {
switch self {
Expand All @@ -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."
}
}
}
Expand Down
29 changes: 28 additions & 1 deletion Tests/XcodesKitTests/Fixtures/DownloadableRuntimes.plist
Original file line number Diff line number Diff line change
Expand Up @@ -1794,7 +1794,34 @@
<key>version</key>
<string>1.0.0.1</string>
</dict>
</array>
<dict>
<key>category</key>
<string>simulator</string>
<key>contentType</key>
<string>cryptexDiskImage</string>
<key>dictionaryVersion</key>
<integer>2</integer>
<key>downloadMethod</key>
<string>mobileAsset</string>
<key>fileSize</key>
<integer>8455760175</integer>
<key>identifier</key>
<string>com.apple.dmg.iPhoneSimulatorSDK18_0_b1</string>
<key>name</key>
<string>iOS 18.0 beta Simulator Runtime</string>
<key>platform</key>
<string>com.apple.platform.iphoneos</string>
<key>simulatorVersion</key>
<dict>
<key>buildUpdate</key>
<string>22A5282m</string>
<key>version</key>
<string>18.0</string>
</dict>
<key>version</key>
<string>18.0.0.1</string>
</dict>
</array>
<key>refreshInterval</key>
<integer>86400</integer>
<key>sdkToSeedMappings</key>
Expand Down
1 change: 1 addition & 0 deletions Tests/XcodesKitTests/Fixtures/LogOutput-Runtimes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions Tests/XcodesKitTests/RuntimeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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 {
Expand Down

0 comments on commit 3c6e474

Please sign in to comment.