mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
Add new platforms list in settings
This commit is contained in:
parent
3c5f86023e
commit
b968149235
19 changed files with 513 additions and 48 deletions
|
|
@ -116,6 +116,8 @@
|
|||
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; };
|
||||
E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */; };
|
||||
E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */; };
|
||||
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F532B333864003F3959 /* PlatformsListView.swift */; };
|
||||
E84E4F572B335094003F3959 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E84E4F562B335094003F3959 /* OrderedCollections */; };
|
||||
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86671262B309D2F0048559A /* PlatformsView.swift */; };
|
||||
E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; };
|
||||
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; };
|
||||
|
|
@ -313,6 +315,7 @@
|
|||
E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeInstallationStepDetailView.swift; sourceTree = "<group>"; };
|
||||
E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewWrapper.swift; sourceTree = "<group>"; };
|
||||
E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadiusModifier.swift; sourceTree = "<group>"; };
|
||||
E84E4F532B333864003F3959 /* PlatformsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformsListView.swift; sourceTree = "<group>"; };
|
||||
E856BB73291EDD3D00DC438B /* XcodesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = XcodesKit; path = Xcodes/XcodesKit; sourceTree = "<group>"; };
|
||||
E86671262B309D2F0048559A /* PlatformsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformsView.swift; sourceTree = "<group>"; };
|
||||
E87AB3C42939B65E00D72F43 /* Hardware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hardware.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -352,6 +355,7 @@
|
|||
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */,
|
||||
CAA1CB2D255A5262003FD669 /* AppleAPI in Frameworks */,
|
||||
CABFA9EE2592F0CC00380FEE /* SwiftSoup in Frameworks */,
|
||||
E84E4F572B335094003F3959 /* OrderedCollections in Frameworks */,
|
||||
E8F44A1E296B4CD7002D6592 /* Path in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -628,6 +632,7 @@
|
|||
E8977EA225C11E1500835F80 /* PreferencesView.swift */,
|
||||
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */,
|
||||
E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */,
|
||||
E84E4F532B333864003F3959 /* PlatformsListView.swift */,
|
||||
);
|
||||
path = Preferences;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -705,6 +710,7 @@
|
|||
E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
|
||||
E8C0EB19291EF43E0081528A /* XcodesKit */,
|
||||
E8F44A1D296B4CD7002D6592 /* Path */,
|
||||
E84E4F562B335094003F3959 /* OrderedCollections */,
|
||||
);
|
||||
productName = XcodesMac;
|
||||
productReference = CAD2E79E2449574E00113D76 /* Xcodes.app */;
|
||||
|
|
@ -791,6 +797,7 @@
|
|||
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
|
||||
E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */,
|
||||
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */,
|
||||
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
|
||||
);
|
||||
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -914,6 +921,7 @@
|
|||
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
|
||||
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */,
|
||||
36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */,
|
||||
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */,
|
||||
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */,
|
||||
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */,
|
||||
CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */,
|
||||
|
|
@ -1501,6 +1509,14 @@
|
|||
minimumVersion = 3.2.0;
|
||||
};
|
||||
};
|
||||
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/apple/swift-collections.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.5;
|
||||
};
|
||||
};
|
||||
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mxcl/Path.swift";
|
||||
|
|
@ -1572,6 +1588,11 @@
|
|||
package = E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */;
|
||||
productName = DockProgress;
|
||||
};
|
||||
E84E4F562B335094003F3959 /* OrderedCollections */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */;
|
||||
productName = OrderedCollections;
|
||||
};
|
||||
E8C0EB19291EF43E0081528A /* XcodesKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = XcodesKit;
|
||||
|
|
|
|||
|
|
@ -82,6 +82,15 @@
|
|||
"version": "2.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-collections",
|
||||
"repositoryURL": "https://github.com/apple/swift-collections.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "a902f1823a7ff3c9ab2fba0f992396b948eda307",
|
||||
"version": "1.0.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftSoup",
|
||||
"repositoryURL": "https://github.com/scinfu/SwiftSoup",
|
||||
|
|
|
|||
|
|
@ -500,14 +500,15 @@ extension AppState {
|
|||
}
|
||||
}
|
||||
|
||||
func setInstallationStep(of runtime: DownloadableRuntime, to step: RuntimeInstallationStep) {
|
||||
func setInstallationStep(of runtime: DownloadableRuntime, to step: RuntimeInstallationStep, postNotification: Bool = true) {
|
||||
DispatchQueue.main.async {
|
||||
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
|
||||
self.downloadableRuntimes[index].installState = .installing(step)
|
||||
|
||||
if postNotification {
|
||||
Current.notificationManager.scheduleNotification(title: runtime.name, body: step.description, category: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppState {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ extension AppState {
|
|||
func updateInstalledRuntimes() {
|
||||
Task {
|
||||
do {
|
||||
Logger.appState.info("Loading Installed runtimes")
|
||||
let runtimes = try await self.runtimeService.localInstalledRuntimes()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
|
@ -51,7 +52,7 @@ extension AppState {
|
|||
do {
|
||||
let downloadedURL = try await downloadRunTimeFull(runtime: runtime)
|
||||
if !Task.isCancelled {
|
||||
Logger.appState.debug("Installing rungtime: \(runtime.name)")
|
||||
Logger.appState.debug("Installing runtime: \(runtime.name)")
|
||||
DispatchQueue.main.async {
|
||||
self.setInstallationStep(of: runtime, to: .installing)
|
||||
}
|
||||
|
|
@ -110,11 +111,10 @@ extension AppState {
|
|||
let aria2Path = Path(url: Bundle.main.url(forAuxiliaryExecutable: "aria2c")!)!
|
||||
for try await progress in downloadRuntimeWithAria2(runtime, to: expectedRuntimePath, aria2Path: aria2Path) {
|
||||
DispatchQueue.main.async {
|
||||
Logger.appState.debug("Downloading: \(progress.fractionCompleted)")
|
||||
self.setInstallationStep(of: runtime, to: .downloading(progress: progress))
|
||||
self.setInstallationStep(of: runtime, to: .downloading(progress: progress), postNotification: false)
|
||||
}
|
||||
}
|
||||
Logger.appState.debug("Done downloading")
|
||||
Logger.appState.debug("Done downloading runtime")
|
||||
|
||||
case .urlSession:
|
||||
throw "Downloading runtimes with URLSession is not supported. Please use aria2"
|
||||
|
|
@ -210,6 +210,35 @@ extension AppState {
|
|||
|
||||
updateInstalledRuntimes()
|
||||
}
|
||||
|
||||
func runtimeInstallPath(xcode: Xcode, runtime: DownloadableRuntime) -> Path? {
|
||||
if let coreSimulatorInfo = coreSimulatorInfo(runtime: runtime) {
|
||||
let urlString = coreSimulatorInfo.path["relative"]!
|
||||
// app was not allowed to open up file:// url's so remove
|
||||
let fileRemovedString = urlString.replacingOccurrences(of: "file://", with: "")
|
||||
let url = URL(fileURLWithPath: fileRemovedString)
|
||||
|
||||
return Path(url: url)!
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? {
|
||||
return installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first
|
||||
}
|
||||
|
||||
func deleteRuntime(runtime: DownloadableRuntime) async throws {
|
||||
if let info = coreSimulatorInfo(runtime: runtime) {
|
||||
try await runtimeService.deleteRuntime(identifier: info.uuid)
|
||||
|
||||
// give it some time to actually finish deleting before updating
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
||||
self?.updateInstalledRuntimes()
|
||||
}
|
||||
} else {
|
||||
throw "No simulator found with \(runtime.identifier)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyPublisher {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class AppState: ObservableObject {
|
|||
@Published var isProcessingAuthRequest = false
|
||||
@Published var xcodeBeingConfirmedForUninstallation: Xcode?
|
||||
@Published var presentedAlert: XcodesAlert?
|
||||
@Published var presentedPreferenceAlert: XcodesPreferencesAlert?
|
||||
@Published var helperInstallState: HelperInstallState = .notInstalled
|
||||
/// Whether the user is being prepared for the helper installation alert with an explanation.
|
||||
/// This closure will be performed after the user chooses whether or not to proceed.
|
||||
|
|
@ -825,18 +826,6 @@ class AppState: ObservableObject {
|
|||
self.allXcodes = newAllXcodes.sorted { $0.version > $1.version }
|
||||
}
|
||||
|
||||
// MARK: Runtimes
|
||||
func runtimeInstallPath(xcode: Xcode, runtime: DownloadableRuntime) -> Path? {
|
||||
if let coreSimulatorInfo = installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first {
|
||||
let urlString = coreSimulatorInfo.path["relative"]!
|
||||
// app was not allowed to open up file:// url's so remove
|
||||
let fileRemovedString = urlString.replacingOccurrences(of: "file://", with: "")
|
||||
let url = URL(fileURLWithPath: fileRemovedString)
|
||||
|
||||
return Path(url: url)!
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
|
|
|
|||
|
|
@ -18,3 +18,17 @@ enum XcodesAlert: Identifiable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Splitting out alerts that are shown on the preference screen as by default we are showing on the MainWindow()
|
||||
// and users awkwardly switch screens, sometimes losing the preference screen
|
||||
enum XcodesPreferencesAlert: Identifiable {
|
||||
case deletePlatform(runtime: DownloadableRuntime)
|
||||
case generic(title: String, message: String)
|
||||
|
||||
var id: Int {
|
||||
switch self {
|
||||
case .deletePlatform: return 1
|
||||
case .generic: return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,8 @@ var downloadableRuntimes: [DownloadableRuntime] = {
|
|||
}()
|
||||
|
||||
var installedRuntimes: [CoreSimulatorImage] = {
|
||||
[CoreSimulatorImage(uuid: "85B22F5B-048B-4331-B6E2-F4196D8B7475", path: ["relative" : "file:///Library/Developer/CoreSimulator/Images/85B22F5B-048B-4331-B6E2-F4196D8B7475.dmg"], runtimeInfo: CoreSimulatorRuntimeInfo(build: "19E240"))] // same as iOS in _SDK's
|
||||
[CoreSimulatorImage(uuid: "85B22F5B-048B-4331-B6E2-F4196D8B7475", path: ["relative" : "file:///Library/Developer/CoreSimulator/Images/85B22F5B-048B-4331-B6E2-F4196D8B7475.dmg"], runtimeInfo: CoreSimulatorRuntimeInfo(build: "19E240")),
|
||||
CoreSimulatorImage(uuid: "85B22F5B-048B-4331-B6E2-F4196D8B7473", path: ["relative" : "file:///Library/Developer/CoreSimulator/Images/85B22F5B-048B-4331-B6E2-F4196D8B7475.dmg"], runtimeInfo: CoreSimulatorRuntimeInfo(build: "21N5233f"))]
|
||||
}()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ struct PlatformsView: View {
|
|||
HStack(alignment: .top, spacing: 5){
|
||||
RuntimeInstallationStepDetailView(installationStep: installationStep)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
CancelRuntimeInstallButton(runtime: runtime)
|
||||
}
|
||||
|
||||
|
|
|
|||
88
Xcodes/Frontend/Preferences/PlatformsListView.swift
Normal file
88
Xcodes/Frontend/Preferences/PlatformsListView.swift
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// PlatformsListView.swift
|
||||
// Xcodes
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-12-20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Path
|
||||
import XcodesKit
|
||||
import OrderedCollections
|
||||
|
||||
struct PlatformsListView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@State private var runtimes: OrderedDictionary<DownloadableRuntime.Platform, [DownloadableRuntime]> = [:]
|
||||
@State private var selectedRuntime: DownloadableRuntime?
|
||||
|
||||
var body: some View {
|
||||
List(selection: $selectedRuntime) {
|
||||
Text("PlatformsList.Title")
|
||||
.font(.body)
|
||||
ForEach(runtimes.elements.sorted(\.key.order), id: \.key) { platform, runtimeList in
|
||||
Section {
|
||||
ForEach(runtimeList, id: \.self) { runtime in
|
||||
HStack {
|
||||
Text(runtime.name)
|
||||
Spacer()
|
||||
Text(runtime.downloadFileSizeString)
|
||||
Button {
|
||||
deleteRuntime(runtime: runtime)
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
.foregroundStyle(.red)
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.frame(height: 30)
|
||||
}
|
||||
|
||||
} header: {
|
||||
HStack {
|
||||
runtimeList.first!.icon()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 20)
|
||||
Text(platform.shortName)
|
||||
.font(.headline)
|
||||
}
|
||||
} footer: {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.inset(alternatesRowBackgrounds: true))
|
||||
.task {
|
||||
loadRuntimes()
|
||||
}
|
||||
.onChange(of: appState.installedRuntimes) { _ in
|
||||
loadRuntimes()
|
||||
}
|
||||
}
|
||||
|
||||
func loadRuntimes() {
|
||||
let filteredRuntimes = appState.downloadableRuntimes.filter { runtime in
|
||||
appState.installedRuntimes.contains { $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate
|
||||
}
|
||||
}
|
||||
runtimes = OrderedDictionary(grouping: filteredRuntimes, by: { $0.platform })
|
||||
}
|
||||
|
||||
func deleteRuntime(runtime: DownloadableRuntime) {
|
||||
appState.presentedPreferenceAlert = .deletePlatform(runtime: runtime)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#Preview {
|
||||
PlatformsListView()
|
||||
.environmentObject({ () -> AppState in
|
||||
let a = AppState()
|
||||
|
||||
a.installedRuntimes = installedRuntimes
|
||||
a.downloadableRuntimes = downloadableRuntimes
|
||||
|
||||
return a
|
||||
|
||||
}())
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import SwiftUI
|
|||
|
||||
struct PreferencesView: View {
|
||||
private enum Tabs: Hashable {
|
||||
case general, updates, advanced, experiment
|
||||
case general, updates, platforms, advanced, experiment
|
||||
}
|
||||
@EnvironmentObject var appState: AppState
|
||||
@EnvironmentObject var updater: ObservableUpdater
|
||||
|
|
@ -26,6 +26,12 @@ struct PreferencesView: View {
|
|||
.tabItem {
|
||||
Label("Downloads", systemImage: "icloud.and.arrow.down")
|
||||
}
|
||||
PlatformsListView()
|
||||
.environmentObject(appState)
|
||||
.tabItem {
|
||||
Label("Platforms", systemImage: "ipad.and.iphone")
|
||||
}
|
||||
.tag(Tabs.platforms)
|
||||
AdvancedPreferencePane()
|
||||
.environmentObject(appState)
|
||||
.tabItem {
|
||||
|
|
|
|||
|
|
@ -335,30 +335,220 @@ For more information, please refer to <<http://unlicense.org/>>\
|
|||
otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.\
|
||||
\
|
||||
|
||||
\fs34 SwiftUIMasonry\
|
||||
\fs34 swift-collections\
|
||||
\
|
||||
|
||||
\fs26 MIT License\
|
||||
\fs26 Apache License\
|
||||
Version 2.0, January 2004\
|
||||
http://www.apache.org/licenses/\
|
||||
\
|
||||
Copyright (c) 2022 Ciaran O'Brien\
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\
|
||||
\
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy\
|
||||
of this software and associated documentation files (the "Software"), to deal\
|
||||
in the Software without restriction, including without limitation the rights\
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\
|
||||
copies of the Software, and to permit persons to whom the Software is\
|
||||
furnished to do so, subject to the following conditions:\
|
||||
1. Definitions.\
|
||||
\
|
||||
The above copyright notice and this permission notice shall be included in all\
|
||||
copies or substantial portions of the Software.\
|
||||
"License" shall mean the terms and conditions for use, reproduction,\
|
||||
and distribution as defined by Sections 1 through 9 of this document.\
|
||||
\
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
|
||||
SOFTWARE.\
|
||||
"Licensor" shall mean the copyright owner or entity authorized by\
|
||||
the copyright owner that is granting the License.\
|
||||
\
|
||||
"Legal Entity" shall mean the union of the acting entity and all\
|
||||
other entities that control, are controlled by, or are under common\
|
||||
control with that entity. For the purposes of this definition,\
|
||||
"control" means (i) the power, direct or indirect, to cause the\
|
||||
direction or management of such entity, whether by contract or\
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the\
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.\
|
||||
\
|
||||
"You" (or "Your") shall mean an individual or Legal Entity\
|
||||
exercising permissions granted by this License.\
|
||||
\
|
||||
"Source" form shall mean the preferred form for making modifications,\
|
||||
including but not limited to software source code, documentation\
|
||||
source, and configuration files.\
|
||||
\
|
||||
"Object" form shall mean any form resulting from mechanical\
|
||||
transformation or translation of a Source form, including but\
|
||||
not limited to compiled object code, generated documentation,\
|
||||
and conversions to other media types.\
|
||||
\
|
||||
"Work" shall mean the work of authorship, whether in Source or\
|
||||
Object form, made available under the License, as indicated by a\
|
||||
copyright notice that is included in or attached to the work\
|
||||
(an example is provided in the Appendix below).\
|
||||
\
|
||||
"Derivative Works" shall mean any work, whether in Source or Object\
|
||||
form, that is based on (or derived from) the Work and for which the\
|
||||
editorial revisions, annotations, elaborations, or other modifications\
|
||||
represent, as a whole, an original work of authorship. For the purposes\
|
||||
of this License, Derivative Works shall not include works that remain\
|
||||
separable from, or merely link (or bind by name) to the interfaces of,\
|
||||
the Work and Derivative Works thereof.\
|
||||
\
|
||||
"Contribution" shall mean any work of authorship, including\
|
||||
the original version of the Work and any modifications or additions\
|
||||
to that Work or Derivative Works thereof, that is intentionally\
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner\
|
||||
or by an individual or Legal Entity authorized to submit on behalf of\
|
||||
the copyright owner. For the purposes of this definition, "submitted"\
|
||||
means any form of electronic, verbal, or written communication sent\
|
||||
to the Licensor or its representatives, including but not limited to\
|
||||
communication on electronic mailing lists, source code control systems,\
|
||||
and issue tracking systems that are managed by, or on behalf of, the\
|
||||
Licensor for the purpose of discussing and improving the Work, but\
|
||||
excluding communication that is conspicuously marked or otherwise\
|
||||
designated in writing by the copyright owner as "Not a Contribution."\
|
||||
\
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity\
|
||||
on behalf of whom a Contribution has been received by Licensor and\
|
||||
subsequently incorporated within the Work.\
|
||||
\
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of\
|
||||
this License, each Contributor hereby grants to You a perpetual,\
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable\
|
||||
copyright license to reproduce, prepare Derivative Works of,\
|
||||
publicly display, publicly perform, sublicense, and distribute the\
|
||||
Work and such Derivative Works in Source or Object form.\
|
||||
\
|
||||
3. Grant of Patent License. Subject to the terms and conditions of\
|
||||
this License, each Contributor hereby grants to You a perpetual,\
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable\
|
||||
(except as stated in this section) patent license to make, have made,\
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,\
|
||||
where such license applies only to those patent claims licensable\
|
||||
by such Contributor that are necessarily infringed by their\
|
||||
Contribution(s) alone or by combination of their Contribution(s)\
|
||||
with the Work to which such Contribution(s) was submitted. If You\
|
||||
institute patent litigation against any entity (including a\
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work\
|
||||
or a Contribution incorporated within the Work constitutes direct\
|
||||
or contributory patent infringement, then any patent licenses\
|
||||
granted to You under this License for that Work shall terminate\
|
||||
as of the date such litigation is filed.\
|
||||
\
|
||||
4. Redistribution. You may reproduce and distribute copies of the\
|
||||
Work or Derivative Works thereof in any medium, with or without\
|
||||
modifications, and in Source or Object form, provided that You\
|
||||
meet the following conditions:\
|
||||
\
|
||||
(a) You must give any other recipients of the Work or\
|
||||
Derivative Works a copy of this License; and\
|
||||
\
|
||||
(b) You must cause any modified files to carry prominent notices\
|
||||
stating that You changed the files; and\
|
||||
\
|
||||
(c) You must retain, in the Source form of any Derivative Works\
|
||||
that You distribute, all copyright, patent, trademark, and\
|
||||
attribution notices from the Source form of the Work,\
|
||||
excluding those notices that do not pertain to any part of\
|
||||
the Derivative Works; and\
|
||||
\
|
||||
(d) If the Work includes a "NOTICE" text file as part of its\
|
||||
distribution, then any Derivative Works that You distribute must\
|
||||
include a readable copy of the attribution notices contained\
|
||||
within such NOTICE file, excluding those notices that do not\
|
||||
pertain to any part of the Derivative Works, in at least one\
|
||||
of the following places: within a NOTICE text file distributed\
|
||||
as part of the Derivative Works; within the Source form or\
|
||||
documentation, if provided along with the Derivative Works; or,\
|
||||
within a display generated by the Derivative Works, if and\
|
||||
wherever such third-party notices normally appear. The contents\
|
||||
of the NOTICE file are for informational purposes only and\
|
||||
do not modify the License. You may add Your own attribution\
|
||||
notices within Derivative Works that You distribute, alongside\
|
||||
or as an addendum to the NOTICE text from the Work, provided\
|
||||
that such additional attribution notices cannot be construed\
|
||||
as modifying the License.\
|
||||
\
|
||||
You may add Your own copyright statement to Your modifications and\
|
||||
may provide additional or different license terms and conditions\
|
||||
for use, reproduction, or distribution of Your modifications, or\
|
||||
for any such Derivative Works as a whole, provided Your use,\
|
||||
reproduction, and distribution of the Work otherwise complies with\
|
||||
the conditions stated in this License.\
|
||||
\
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,\
|
||||
any Contribution intentionally submitted for inclusion in the Work\
|
||||
by You to the Licensor shall be under the terms and conditions of\
|
||||
this License, without any additional terms or conditions.\
|
||||
Notwithstanding the above, nothing herein shall supersede or modify\
|
||||
the terms of any separate license agreement you may have executed\
|
||||
with Licensor regarding such Contributions.\
|
||||
\
|
||||
6. Trademarks. This License does not grant permission to use the trade\
|
||||
names, trademarks, service marks, or product names of the Licensor,\
|
||||
except as required for reasonable and customary use in describing the\
|
||||
origin of the Work and reproducing the content of the NOTICE file.\
|
||||
\
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or\
|
||||
agreed to in writing, Licensor provides the Work (and each\
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,\
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\
|
||||
implied, including, without limitation, any warranties or conditions\
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the\
|
||||
appropriateness of using or redistributing the Work and assume any\
|
||||
risks associated with Your exercise of permissions under this License.\
|
||||
\
|
||||
8. Limitation of Liability. In no event and under no legal theory,\
|
||||
whether in tort (including negligence), contract, or otherwise,\
|
||||
unless required by applicable law (such as deliberate and grossly\
|
||||
negligent acts) or agreed to in writing, shall any Contributor be\
|
||||
liable to You for damages, including any direct, indirect, special,\
|
||||
incidental, or consequential damages of any character arising as a\
|
||||
result of this License or out of the use or inability to use the\
|
||||
Work (including but not limited to damages for loss of goodwill,\
|
||||
work stoppage, computer failure or malfunction, or any and all\
|
||||
other commercial damages or losses), even if such Contributor\
|
||||
has been advised of the possibility of such damages.\
|
||||
\
|
||||
9. Accepting Warranty or Additional Liability. While redistributing\
|
||||
the Work or Derivative Works thereof, You may choose to offer,\
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,\
|
||||
or other liability obligations and/or rights consistent with this\
|
||||
License. However, in accepting such obligations, You may act only\
|
||||
on Your own behalf and on Your sole responsibility, not on behalf\
|
||||
of any other Contributor, and only if You agree to indemnify,\
|
||||
defend, and hold each Contributor harmless for any liability\
|
||||
incurred by, or claims asserted against, such Contributor by reason\
|
||||
of your accepting any such warranty or additional liability.\
|
||||
\
|
||||
END OF TERMS AND CONDITIONS\
|
||||
\
|
||||
APPENDIX: How to apply the Apache License to your work.\
|
||||
\
|
||||
To apply the Apache License to your work, attach the following\
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"\
|
||||
replaced with your own identifying information. (Don't include\
|
||||
the brackets!) The text should be enclosed in the appropriate\
|
||||
comment syntax for the file format. We also recommend that a\
|
||||
file or class name and description of purpose be included on the\
|
||||
same "printed page" as the copyright notice for easier\
|
||||
identification within third-party archives.\
|
||||
\
|
||||
Copyright [yyyy] [name of copyright owner]\
|
||||
\
|
||||
Licensed under the Apache License, Version 2.0 (the "License");\
|
||||
you may not use this file except in compliance with the License.\
|
||||
You may obtain a copy of the License at\
|
||||
\
|
||||
http://www.apache.org/licenses/LICENSE-2.0\
|
||||
\
|
||||
Unless required by applicable law or agreed to in writing, software\
|
||||
distributed under the License is distributed on an "AS IS" BASIS,\
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\
|
||||
See the License for the specific language governing permissions and\
|
||||
limitations under the License.\
|
||||
\
|
||||
\
|
||||
\
|
||||
## Runtime Library Exception to the Apache 2.0 License: ##\
|
||||
\
|
||||
\
|
||||
As an exception, if you use this Software to compile your source code and\
|
||||
portions of this Software are embedded into the binary product as a result,\
|
||||
you may redistribute such product without providing attribution as would\
|
||||
otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.\
|
||||
\
|
||||
\
|
||||
|
||||
|
|
|
|||
|
|
@ -1166,6 +1166,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Alert.DeletePlatform.PrimaryButton" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Alert.DeletePlatform.Title" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Are you sure you want to delete %@?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Alert.Install.Error.Title" : {
|
||||
"comment" : "Install",
|
||||
"extractionState" : "manual",
|
||||
|
|
@ -6504,6 +6526,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Error" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"example@icloud.com" : {
|
||||
"localizations" : {
|
||||
"tr" : {
|
||||
|
|
@ -14581,6 +14614,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"PlatformsList.Title" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Below are a list of platforms that are installed on this machine. "
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Preferences" : {
|
||||
"localizations" : {
|
||||
"ca" : {
|
||||
|
|
|
|||
|
|
@ -73,9 +73,50 @@ struct XcodesApp: App {
|
|||
PreferencesView()
|
||||
.environmentObject(appState)
|
||||
.environmentObject(updater)
|
||||
.alert(item: $appState.presentedPreferenceAlert, content: { presentedAlert in
|
||||
alert(for: presentedAlert)
|
||||
})
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func alert(for alertType: XcodesPreferencesAlert) -> Alert {
|
||||
switch alertType {
|
||||
case let .deletePlatform(runtime):
|
||||
return Alert(
|
||||
title: Text(String(format: localizeString("Alert.DeletePlatform.Title"), runtime.name)),
|
||||
primaryButton: .destructive(
|
||||
Text("Alert.DeletePlatform.PrimaryButton"),
|
||||
action: {
|
||||
Task {
|
||||
do {
|
||||
try await self.appState.deleteRuntime(runtime: runtime)
|
||||
} catch {
|
||||
var errorString: String
|
||||
if let error = error as? String {
|
||||
errorString = error
|
||||
} else {
|
||||
errorString = error.localizedDescription
|
||||
}
|
||||
self.appState.presentedPreferenceAlert = .generic(title: "Error", message: errorString)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
),
|
||||
secondaryButton: .cancel(Text("Cancel"))
|
||||
)
|
||||
case let .generic(title, message):
|
||||
return Alert(
|
||||
title: Text(title),
|
||||
message: Text(message),
|
||||
dismissButton: .default(
|
||||
Text("OK"),
|
||||
action: { appState.presentedAlert = nil }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ public struct CoreSimulatorPlist: Decodable {
|
|||
}
|
||||
}
|
||||
|
||||
public struct CoreSimulatorImage: Decodable {
|
||||
public struct CoreSimulatorImage: Decodable, Identifiable, Equatable {
|
||||
public var id: String {
|
||||
return uuid
|
||||
}
|
||||
|
||||
public let uuid: String
|
||||
public let path: [String: String]
|
||||
public let runtimeInfo: CoreSimulatorRuntimeInfo
|
||||
|
|
@ -25,6 +29,10 @@ public struct CoreSimulatorImage: Decodable {
|
|||
self.path = path
|
||||
self.runtimeInfo = runtimeInfo
|
||||
}
|
||||
|
||||
public static func == (lhs: CoreSimulatorImage, rhs: CoreSimulatorImage) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
public struct CoreSimulatorRuntimeInfo: Decodable {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import Foundation
|
||||
import Path
|
||||
|
||||
public enum RuntimeInstallState: Equatable {
|
||||
public enum RuntimeInstallState: Equatable, Hashable {
|
||||
case notInstalled
|
||||
case installing(RuntimeInstallationStep)
|
||||
case installed
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public enum RuntimeInstallationStep: Equatable, CustomStringConvertible {
|
||||
public enum RuntimeInstallationStep: Equatable, CustomStringConvertible, Hashable {
|
||||
case downloading(progress: Progress)
|
||||
case installing
|
||||
case trashingArchive
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ public struct DownloadableRuntimesResponse: Codable {
|
|||
public let version: String
|
||||
}
|
||||
|
||||
public struct DownloadableRuntime: Codable {
|
||||
public struct DownloadableRuntime: Codable, Identifiable, Hashable {
|
||||
public let category: Category
|
||||
public let simulatorVersion: SimulatorVersion
|
||||
public let source: String
|
||||
|
|
@ -71,6 +71,14 @@ public struct DownloadableRuntime: Codable {
|
|||
public var downloadFileSizeString: String {
|
||||
return ByteCountFormatter.string(fromByteCount: Int64(fileSize), countStyle: .file)
|
||||
}
|
||||
|
||||
public var id: String {
|
||||
return visibleIdentifier
|
||||
}
|
||||
|
||||
public static func == (lhs: DownloadableRuntime, rhs: DownloadableRuntime) -> Bool {
|
||||
return lhs.identifier == rhs.identifier
|
||||
}
|
||||
}
|
||||
|
||||
public struct SDKToSeedMapping: Codable {
|
||||
|
|
@ -86,12 +94,12 @@ public struct SDKToSimulatorMapping: Codable {
|
|||
}
|
||||
|
||||
extension DownloadableRuntime {
|
||||
public struct SimulatorVersion: Codable {
|
||||
public struct SimulatorVersion: Codable, Hashable {
|
||||
public let buildUpdate: String
|
||||
public let version: String
|
||||
}
|
||||
|
||||
public struct HostRequirements: Codable {
|
||||
public struct HostRequirements: Codable, Hashable {
|
||||
let maxHostVersion: String?
|
||||
let excludedHostArchitectures: [String]?
|
||||
let minHostVersion: String?
|
||||
|
|
@ -118,7 +126,7 @@ extension DownloadableRuntime {
|
|||
case tvOS = "com.apple.platform.appletvos"
|
||||
case visionOS = "com.apple.platform.xros"
|
||||
|
||||
var order: Int {
|
||||
public var order: Int {
|
||||
switch self {
|
||||
case .iOS: return 1
|
||||
case .macOS: return 2
|
||||
|
|
@ -128,7 +136,7 @@ extension DownloadableRuntime {
|
|||
}
|
||||
}
|
||||
|
||||
var shortName: String {
|
||||
public var shortName: String {
|
||||
switch self {
|
||||
case .iOS: return "iOS"
|
||||
case .macOS: return "macOS"
|
||||
|
|
@ -137,6 +145,7 @@ extension DownloadableRuntime {
|
|||
case .visionOS: return "visionOS"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,17 @@ public struct RuntimeService {
|
|||
public func installPkg(pkgPath: Path, expandedPkgPath: Path) async throws {
|
||||
_ = try await Current.shell.installPkg(pkgPath.url, expandedPkgPath.url.absoluteString)
|
||||
}
|
||||
|
||||
public func deleteRuntime(identifier: String) async throws {
|
||||
do {
|
||||
_ = try await Current.shell.deleteRuntime(identifier)
|
||||
} catch {
|
||||
if let executionError = error as? ProcessExecutionError {
|
||||
throw executionError.standardError
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension String: Error {}
|
||||
|
|
|
|||
|
|
@ -23,4 +23,7 @@ public struct XcodesShell {
|
|||
public var installRuntimeImage: (URL) async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.bin.join("xcrun"), "simctl", "runtime", "add", $0.path)
|
||||
}
|
||||
public var deleteRuntime: (String) async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.bin.join("xcrun"), "simctl", "runtime", "delete", $0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue