Skip to content

Commit 7325502

Browse files
committed
more runtime download work
1 parent 4f25905 commit 7325502

16 files changed

Lines changed: 269 additions & 77 deletions

File tree

Xcodes.xcodeproj/project.pbxproj

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@
10541054
"@executable_path/../Frameworks",
10551055
);
10561056
MARKETING_VERSION = 1.10.0;
1057-
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
1057+
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
10581058
PRODUCT_NAME = Xcodes;
10591059
PROVISIONING_PROFILE_SPECIFIER = "";
10601060
SWIFT_VERSION = 5.0;
@@ -1286,7 +1286,7 @@
12861286
COMBINE_HIDPI_IMAGES = YES;
12871287
CURRENT_PROJECT_VERSION = 18;
12881288
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
1289-
DEVELOPMENT_TEAM = PBH8V487HB;
1289+
DEVELOPMENT_TEAM = ZU6GR6B2FY;
12901290
ENABLE_HARDENED_RUNTIME = YES;
12911291
ENABLE_PREVIEWS = YES;
12921292
INFOPLIST_FILE = Xcodes/Resources/Info.plist;
@@ -1295,7 +1295,7 @@
12951295
"@executable_path/../Frameworks",
12961296
);
12971297
MARKETING_VERSION = 1.10.0;
1298-
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
1298+
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
12991299
PRODUCT_NAME = Xcodes;
13001300
SWIFT_VERSION = 5.0;
13011301
};
@@ -1310,7 +1310,7 @@
13101310
COMBINE_HIDPI_IMAGES = YES;
13111311
CURRENT_PROJECT_VERSION = 18;
13121312
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
1313-
DEVELOPMENT_TEAM = PBH8V487HB;
1313+
DEVELOPMENT_TEAM = ZU6GR6B2FY;
13141314
ENABLE_HARDENED_RUNTIME = YES;
13151315
ENABLE_PREVIEWS = YES;
13161316
INFOPLIST_FILE = Xcodes/Resources/Info.plist;
@@ -1319,7 +1319,7 @@
13191319
"@executable_path/../Frameworks",
13201320
);
13211321
MARKETING_VERSION = 1.10.0;
1322-
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
1322+
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
13231323
PRODUCT_NAME = Xcodes;
13241324
SWIFT_VERSION = 5.0;
13251325
};
@@ -1417,8 +1417,8 @@
14171417
isa = XCRemoteSwiftPackageReference;
14181418
repositoryURL = "https://github.com/xcodereleases/data";
14191419
requirement = {
1420-
kind = revision;
1421-
revision = b47228c688b608e34b3b84079ab6052a24c7a981;
1420+
branch = main;
1421+
kind = branch;
14221422
};
14231423
};
14241424
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */ = {

Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Xcodes/Backend/AppState+Install.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,15 @@ extension AppState {
466466
let xcode = self.allXcodes[index]
467467
Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal)
468468
}
469+
}w func setInstallationStep(of runtime: DownloadableRuntime, to step: InstallationStep) {
470+
DispatchQueue.main.async {
471+
472+
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
473+
self.downloadableRuntimes[index].installState = .installing(step)
474+
475+
// let xcode = self.allXcodes[index]
476+
// Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal)
477+
}
469478
}
470479
}
471480

Xcodes/Backend/AppState+Runtimes.swift

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
import Foundation
22
import XcodesKit
33
import OSLog
4+
import Combine
5+
import Path
6+
import AppleAPI
47

58
extension AppState {
69
func updateDownloadableRuntimes() {
710
Task {
811
do {
9-
let runtimes = try await self.runtimeService.downloadableRuntimes().downloadables
12+
13+
let downloadableRuntimes = try await self.runtimeService.downloadableRuntimes()
14+
let runtimes = downloadableRuntimes.downloadables.map { runtime in
15+
var updatedRuntime = runtime
16+
17+
// This loops through and matches up the simulatorVersion to the mappings
18+
let simulatorBuildUpdate = downloadableRuntimes.sdkToSimulatorMappings.first { SDKToSimulatorMapping in
19+
SDKToSimulatorMapping.simulatorBuildUpdate == runtime.simulatorVersion.buildUpdate
20+
}
21+
updatedRuntime.sdkBuildUpdate = simulatorBuildUpdate?.sdkBuildUpdate
22+
return updatedRuntime
23+
}
24+
1025
DispatchQueue.main.async {
1126
self.downloadableRuntimes = runtimes
1227
}
@@ -29,4 +44,111 @@ extension AppState {
2944
}
3045
}
3146
}
47+
48+
func downloadRuntime(runtime: DownloadableRuntime) {
49+
self.runtimePublishers[runtime.identifier] = downloadRunTimeFull(runtime: runtime)
50+
.receive(on: DispatchQueue.main)
51+
.sink(
52+
receiveCompletion: { [unowned self] completion in
53+
self.runtimePublishers[runtime.identifier] = nil
54+
if case let .failure(error) = completion {
55+
// // Prevent setting the app state error if it is an invalid session, we will present the sign in view instead
56+
// if error as? AuthenticationError != .invalidSession {
57+
// self.error = error
58+
// self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
59+
// }
60+
// if let index = self.allXcodes.firstIndex(where: { $0.id == id }) {
61+
// self.allXcodes[index].installState = .notInstalled
62+
// }
63+
}
64+
},
65+
receiveValue: { _ in }
66+
)
67+
}
68+
69+
func downloadRunTimeFull(runtime: DownloadableRuntime) -> AnyPublisher<(DownloadableRuntime, URL), Error> {
70+
// gets a proper cookie for runtimes
71+
72+
return validateADCSession(path: runtime.downloadPath)
73+
.flatMap { _ in
74+
// we shouldn't have to be authenticated to download runtimes
75+
let downloader = Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2
76+
Logger.appState.info("Downloading \(runtime.visibleIdentifier) with \(downloader)")
77+
78+
return self.downloadRuntime(for: runtime, downloader: downloader, progressChanged: { [unowned self] progress in
79+
DispatchQueue.main.async {
80+
self.setInstallationStep(of: runtime, to: .downloading(progress: progress))
81+
}
82+
})
83+
.map { return (runtime, $0) }
84+
}
85+
.eraseToAnyPublisher()
86+
}
87+
88+
func downloadRuntime(for runtime: DownloadableRuntime, downloader: Downloader, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
89+
// Check to see if the dmg is in the expected path in case it was downloaded but failed to install
90+
91+
// call https://developerservices2.apple.com/services/download?path=/Developer_Tools/watchOS_10_beta/watchOS_10_beta_Simulator_Runtime.dmg 1st to get cookie
92+
// use runtime.url for final with cookies
93+
94+
// Check to see if the archive is in the expected path in case it was downloaded but failed to install
95+
let expectedRuntimePath = Path.xcodesApplicationSupport/"\(runtime.name).\(runtime.name.suffix(fromLast: "."))"
96+
// aria2 downloads directly to the destination (instead of into /tmp first) so we need to make sure that the download isn't incomplete
97+
let aria2DownloadMetadataPath = expectedRuntimePath.parent/(expectedRuntimePath.basename() + ".aria2")
98+
var aria2DownloadIsIncomplete = false
99+
if case .aria2 = downloader, aria2DownloadMetadataPath.exists {
100+
aria2DownloadIsIncomplete = true
101+
}
102+
if Current.files.fileExistsAtPath(expectedRuntimePath.string), aria2DownloadIsIncomplete == false {
103+
Logger.appState.info("Found existing runtime that will be used for installation at \(expectedRuntimePath).")
104+
return Just(expectedRuntimePath.url)
105+
.setFailureType(to: Error.self)
106+
.eraseToAnyPublisher()
107+
}
108+
else {
109+
// let destination = Path.xcodesApplicationSupport/"Xcode-\(availableXcode.version).\(availableXcode.filename.suffix(fromLast: "."))"
110+
switch downloader {
111+
case .aria2:
112+
let aria2Path = Path(url: Bundle.main.url(forAuxiliaryExecutable: "aria2c")!)!
113+
return downloadRuntimeWithAria2(
114+
runtime,
115+
to: expectedRuntimePath,
116+
aria2Path: aria2Path,
117+
progressChanged: progressChanged)
118+
// return downloadXcodeWithAria2(
119+
// availableXcode,
120+
// to: destination,
121+
// aria2Path: aria2Path,
122+
// progressChanged: progressChanged
123+
// )
124+
case .urlSession:
125+
126+
return Just(runtime.url)
127+
.setFailureType(to: Error.self)
128+
.eraseToAnyPublisher()
129+
130+
// return downloadXcodeWithURLSession(
131+
// availableXcode,
132+
// to: destination,
133+
// progressChanged: progressChanged
134+
// )
135+
}
136+
}
137+
138+
}
139+
140+
public func downloadRuntimeWithAria2(_ runtime: DownloadableRuntime, to destination: Path, aria2Path: Path, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
141+
let cookies = AppleAPI.Current.network.session.configuration.httpCookieStorage?.cookies(for: runtime.url) ?? []
142+
143+
let (progress, publisher) = Current.shell.downloadWithAria2(
144+
aria2Path,
145+
runtime.url,
146+
destination,
147+
cookies
148+
)
149+
progressChanged(progress)
150+
return publisher
151+
.map { _ in destination.url }
152+
.eraseToAnyPublisher()
153+
}
32154
}

Xcodes/Backend/AppState.swift

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,13 @@ class AppState: ObservableObject {
105105
// MARK: - Runtimes
106106

107107
@Published var downloadableRuntimes: [DownloadableRuntime] = []
108-
@Published var installedRuntimes: [CoreSimulatorRuntimeInfo] = []
108+
@Published var installedRuntimes: [CoreSimulatorImage] = []
109109

110110
// MARK: - Publisher Cancellables
111111

112112
var cancellables = Set<AnyCancellable>()
113113
private var installationPublishers: [Version: AnyCancellable] = [:]
114+
internal var runtimePublishers: [String: AnyCancellable] = [:]
114115
private var selectPublisher: AnyCancellable?
115116
private var uninstallPublisher: AnyCancellable?
116117
private var autoInstallTimer: Timer?
@@ -148,6 +149,7 @@ class AppState: ObservableObject {
148149
checkIfHelperIsInstalled()
149150
setupAutoInstallTimer()
150151
setupDefaults()
152+
updateInstalledRuntimes()
151153
}
152154

153155
func setupDefaults() {
@@ -175,7 +177,11 @@ class AppState: ObservableObject {
175177
func validateADCSession(path: String) -> AnyPublisher<Void, Error> {
176178
return Current.network.dataTask(with: URLRequest.downloadADCAuth(path: path))
177179
.receive(on: DispatchQueue.main)
178-
.tryMap { _ in
180+
.tryMap { result -> Void in
181+
let httpResponse = result.response as! HTTPURLResponse
182+
if httpResponse.statusCode == 401 {
183+
throw AuthenticationError.notAuthorized
184+
}
179185
}
180186
.eraseToAnyPublisher()
181187
}
@@ -796,16 +802,27 @@ class AppState: ObservableObject {
796802
func getRunTimes(xcode: Xcode) -> [DownloadableRuntime] {
797803

798804
let builds = xcode.sdks?.allBuilds()
799-
800-
let runtime = builds?.flatMap { sdkBuild in
805+
806+
let runtimes: [DownloadableRuntime]? = builds?.flatMap { sdkBuild in
801807
downloadableRuntimes.filter {
802-
$0.simulatorVersion.buildUpdate == sdkBuild
808+
$0.sdkBuildUpdate == sdkBuild
803809
}
804810
}
805-
// appState.installedRuntimes has a list of builds that user has installed.
806-
807-
return runtime ?? []
811+
812+
let updatedRuntimes = runtimes?.map { runtime in
813+
var updatedRuntime = runtime
814+
if let coreSimulatorInfo = installedRuntimes.filter({ $0.runtimeInfo.build == runtime.sdkBuildUpdate }).first {
815+
let url = URL(fileURLWithPath: coreSimulatorInfo.path["relative"]!)
816+
updatedRuntime.installState = .installed(Path(url: url)!)
817+
} else {
818+
updatedRuntime.installState = .notInstalled
819+
}
820+
return updatedRuntime
821+
}
822+
823+
return updatedRuntimes ?? []
808824
}
825+
809826

810827
// MARK: - Private
811828

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,45 @@
1-
import Foundation
2-
3-
/// A numbered step
4-
enum InstallationStep: Equatable, CustomStringConvertible {
5-
case downloading(progress: Progress)
6-
case unarchiving
7-
case moving(destination: String)
8-
case trashingArchive
9-
case checkingSecurity
10-
case finishing
11-
12-
var description: String {
13-
"(\(stepNumber)/\(stepCount)) \(message)"
14-
}
15-
16-
var message: String {
17-
switch self {
18-
case .downloading:
19-
return localizeString("Downloading")
20-
case .unarchiving:
21-
return localizeString("Unarchiving")
22-
case .moving(let destination):
23-
return String(format: localizeString("Moving"), destination)
24-
case .trashingArchive:
25-
return localizeString("TrashingArchive")
26-
case .checkingSecurity:
27-
return localizeString("CheckingSecurity")
28-
case .finishing:
29-
return localizeString("Finishing")
30-
}
31-
}
32-
33-
var stepNumber: Int {
34-
switch self {
35-
case .downloading: return 1
36-
case .unarchiving: return 2
37-
case .moving: return 3
38-
case .trashingArchive: return 4
39-
case .checkingSecurity: return 5
40-
case .finishing: return 6
41-
}
42-
}
43-
44-
var stepCount: Int { 6 }
45-
}
1+
//import Foundation
2+
//
3+
///// A numbered step
4+
//enum InstallationStep: Equatable, CustomStringConvertible {
5+
// case downloading(progress: Progress)
6+
// case unarchiving
7+
// case moving(destination: String)
8+
// case trashingArchive
9+
// case checkingSecurity
10+
// case finishing
11+
//
12+
// var description: String {
13+
// "(\(stepNumber)/\(stepCount)) \(message)"
14+
// }
15+
//
16+
// var message: String {
17+
// switch self {
18+
// case .downloading:
19+
// return localizeString("Downloading")
20+
// case .unarchiving:
21+
// return localizeString("Unarchiving")
22+
// case .moving(let destination):
23+
// return String(format: localizeString("Moving"), destination)
24+
// case .trashingArchive:
25+
// return localizeString("TrashingArchive")
26+
// case .checkingSecurity:
27+
// return localizeString("CheckingSecurity")
28+
// case .finishing:
29+
// return localizeString("Finishing")
30+
// }
31+
// }
32+
//
33+
// var stepNumber: Int {
34+
// switch self {
35+
// case .downloading: return 1
36+
// case .unarchiving: return 2
37+
// case .moving: return 3
38+
// case .trashingArchive: return 4
39+
// case .checkingSecurity: return 5
40+
// case .finishing: return 6
41+
// }
42+
// }
43+
//
44+
// var stepCount: Int { 6 }
45+
//}

Xcodes/Backend/SDKs+Xcode.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ extension SDKs {
2626
if let watchOS = self.watchOS?.compactMap({ $0.build }) {
2727
buildNumbers += watchOS
2828
}
29+
if let visionOS = self.visionOS?.compactMap({ $0.build }) {
30+
buildNumbers += visionOS
31+
}
2932

3033
return buildNumbers
3134
}

0 commit comments

Comments
 (0)