Skip to content

Commit 3c6e474

Browse files
committed
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.
1 parent aa795f8 commit 3c6e474

5 files changed

Lines changed: 47 additions & 7 deletions

File tree

Sources/XcodesKit/Models+Runtimes.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct DownloadableRuntimesResponse: Decodable {
1111
public struct DownloadableRuntime: Decodable {
1212
let category: Category
1313
let simulatorVersion: SimulatorVersion
14-
let source: String
14+
let source: String?
1515
let dictionaryVersion: Int
1616
let contentType: ContentType
1717
let platform: Platform
@@ -79,6 +79,7 @@ extension DownloadableRuntime {
7979
enum ContentType: String, Decodable {
8080
case diskImage = "diskImage"
8181
case package = "package"
82+
case cryptexDiskImage = "cryptexDiskImage"
8283
}
8384

8485
enum Platform: String, Decodable {

Sources/XcodesKit/RuntimeInstaller.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ public class RuntimeInstaller {
112112
try await installFromPackage(dmgUrl: dmgUrl, runtime: matchedRuntime)
113113
case .diskImage:
114114
try await installFromImage(dmgUrl: dmgUrl)
115+
case .cryptexDiskImage:
116+
throw Error.unsupportedCryptexDiskImage
115117
}
116118
if shouldDelete {
117119
Current.logging.log("Deleting Archive")
@@ -183,7 +185,10 @@ public class RuntimeInstaller {
183185

184186
@MainActor
185187
public func downloadOrUseExistingArchive(runtime: DownloadableRuntime, to destinationDirectory: Path, downloader: Downloader) async throws -> URL {
186-
let url = URL(string: runtime.source)!
188+
guard let source = runtime.source else {
189+
throw Error.missingRuntimeSource(runtime.identifier)
190+
}
191+
let url = URL(string: source)!
187192
let destination = destinationDirectory/url.lastPathComponent
188193
let aria2DownloadMetadataPath = destination.parent/(destination.basename() + ".aria2")
189194
var aria2DownloadIsIncomplete = false
@@ -226,6 +231,8 @@ extension RuntimeInstaller {
226231
case unavailableRuntime(String)
227232
case failedMountingDMG
228233
case rootNeeded
234+
case missingRuntimeSource(String)
235+
case unsupportedCryptexDiskImage
229236

230237
public var errorDescription: String? {
231238
switch self {
@@ -235,6 +242,10 @@ extension RuntimeInstaller {
235242
return "Failed to mount image."
236243
case .rootNeeded:
237244
return "Must be run as root to install the specified runtime"
245+
case let .missingRuntimeSource(identifier):
246+
return "Runtime \(identifier) is missing source url."
247+
case .unsupportedCryptexDiskImage:
248+
return "Cryptex Disk Image is not yet supported."
238249
}
239250
}
240251
}

Tests/XcodesKitTests/Fixtures/DownloadableRuntimes.plist

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1794,7 +1794,34 @@
17941794
<key>version</key>
17951795
<string>1.0.0.1</string>
17961796
</dict>
1797-
</array>
1797+
<dict>
1798+
<key>category</key>
1799+
<string>simulator</string>
1800+
<key>contentType</key>
1801+
<string>cryptexDiskImage</string>
1802+
<key>dictionaryVersion</key>
1803+
<integer>2</integer>
1804+
<key>downloadMethod</key>
1805+
<string>mobileAsset</string>
1806+
<key>fileSize</key>
1807+
<integer>8455760175</integer>
1808+
<key>identifier</key>
1809+
<string>com.apple.dmg.iPhoneSimulatorSDK18_0_b1</string>
1810+
<key>name</key>
1811+
<string>iOS 18.0 beta Simulator Runtime</string>
1812+
<key>platform</key>
1813+
<string>com.apple.platform.iphoneos</string>
1814+
<key>simulatorVersion</key>
1815+
<dict>
1816+
<key>buildUpdate</key>
1817+
<string>22A5282m</string>
1818+
<key>version</key>
1819+
<string>18.0</string>
1820+
</dict>
1821+
<key>version</key>
1822+
<string>18.0.0.1</string>
1823+
</dict>
1824+
</array>
17981825
<key>refreshInterval</key>
17991826
<integer>86400</integer>
18001827
<key>sdkToSeedMappings</key>

Tests/XcodesKitTests/Fixtures/LogOutput-Runtimes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ iOS 16.2
2525
iOS 16.4
2626
iOS 17.0-beta1
2727
iOS 17.0-beta2
28+
iOS 18.0-beta1
2829
-- watchOS --
2930
watchOS 6.0
3031
watchOS 6.1.1

Tests/XcodesKitTests/RuntimeTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ final class RuntimeTests: XCTestCase {
8080
func test_downloadableRuntimes() async throws {
8181
mockDownloadables()
8282
let values = try await runtimeList.downloadableRuntimes().downloadables
83-
XCTAssertEqual(values.count, 59)
83+
XCTAssertEqual(values.count, 60)
8484
}
8585

8686
func test_downloadableRuntimesNoBetas() async throws {
@@ -171,7 +171,7 @@ final class RuntimeTests: XCTestCase {
171171
}
172172

173173
let url = try await runtimeInstaller.downloadOrUseExistingArchive(runtime: runtime, to: .xcodesCaches, downloader: .urlSession)
174-
let fileName = URL(string: runtime.source)!.lastPathComponent
174+
let fileName = URL(string: runtime.source!)!.lastPathComponent
175175
XCTAssertEqual(url, Path.xcodesCaches.join(fileName).url)
176176
XCTAssertNil(xcodeDownloadURL)
177177
}
@@ -185,10 +185,10 @@ final class RuntimeTests: XCTestCase {
185185
return (Progress(), Promise.value((destination, HTTPURLResponse(url: url.pmkRequest.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!)))
186186
}
187187
let runtime = try await runtimeList.downloadableRuntimes().downloadables.first { $0.visibleIdentifier == "iOS 15.5" }!
188-
let fileName = URL(string: runtime.source)!.lastPathComponent
188+
let fileName = URL(string: runtime.source!)!.lastPathComponent
189189
let url = try await runtimeInstaller.downloadOrUseExistingArchive(runtime: runtime, to: .xcodesCaches, downloader: .urlSession)
190190
XCTAssertEqual(url, Path.xcodesCaches.join(fileName).url)
191-
XCTAssertEqual(xcodeDownloadURL, URL(string: runtime.source)!)
191+
XCTAssertEqual(xcodeDownloadURL, URL(string: runtime.source!)!)
192192
}
193193

194194
func test_installStepsForPackage() async throws {

0 commit comments

Comments
 (0)