From 20e7b57ad44e90e03d303be277149bb95660a23c Mon Sep 17 00:00:00 2001 From: Matt Kiazyk Date: Mon, 11 Apr 2022 22:21:09 -0500 Subject: [PATCH] Add check for min MacOS version when installing --- Xcodes.xcodeproj/project.pbxproj | 4 ++++ Xcodes/Backend/AppState+Install.swift | 5 ++-- Xcodes/Backend/AppState.swift | 24 +++++++++++++++++++ Xcodes/Backend/Collection+.swift | 17 +++++++++++++ Xcodes/Backend/XcodeCommands.swift | 2 +- Xcodes/Frontend/Common/XcodesAlert.swift | 2 ++ Xcodes/Frontend/MainWindow.swift | 12 ++++++++++ .../Frontend/XcodeList/XcodeListViewRow.swift | 2 +- 8 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 Xcodes/Backend/Collection+.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 3c1ee78..48fc2e1 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -101,6 +101,7 @@ CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4AB325B7D3AF0064FE51 /* AdvancedPreferencePane.swift */; }; CAFE4ABC25B7D54B0064FE51 /* UpdatesPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */; }; CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */; }; + E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; }; E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; }; E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89342F925EDCC17007CF557 /* NotificationManager.swift */; }; E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; }; @@ -272,6 +273,7 @@ CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatesPreferencePane.swift; sourceTree = ""; }; CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListViewRow.swift; sourceTree = ""; }; CAFFFEEE259CEAC400903F81 /* RingProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingProgressViewStyle.swift; sourceTree = ""; }; + E81D7E9F2805250100A205FC /* Collection+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+.swift"; sourceTree = ""; }; E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = ""; }; E89342F925EDCC17007CF557 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; E8977EA225C11E1500835F80 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; @@ -458,6 +460,7 @@ CA25192925A9644800F08414 /* XcodeInstallState.swift */, CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */, E87DD6EA25D053FA00D86808 /* Progress+.swift */, + E81D7E9F2805250100A205FC /* Collection+.swift */, ); path = Backend; sourceTree = ""; @@ -815,6 +818,7 @@ CA25192A25A9644800F08414 /* XcodeInstallState.swift in Sources */, E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */, CAA1CB35255A5AD5003FD669 /* SignInCredentialsView.swift in Sources */, + E81D7EA02805250100A205FC /* Collection+.swift in Sources */, CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */, CABFAA2D2592FBFC00380FEE /* Configure.swift in Sources */, CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */, diff --git a/Xcodes/Backend/AppState+Install.swift b/Xcodes/Backend/AppState+Install.swift index a60a91c..509f351 100644 --- a/Xcodes/Backend/AppState+Install.swift +++ b/Xcodes/Backend/AppState+Install.swift @@ -24,10 +24,10 @@ extension AppState { if autoInstallType == .newestBeta { Logger.appState.info("Auto installing newest Xcode Beta") // install it, as user doesn't have it installed and it's either latest beta or latest release - install(id: newestXcode.id) + checkMinVersionAndInstall(id: newestXcode.id) } else if autoInstallType == .newestVersion && newestXcode.version.isNotPrerelease { Logger.appState.info("Auto installing newest Xcode") - install(id: newestXcode.id) + checkMinVersionAndInstall(id: newestXcode.id) } else { Logger.appState.info("No new Xcodes version found to auto install") } @@ -92,6 +92,7 @@ extension AppState { return Fail(error: InstallationError.versionAlreadyInstalled(installedXcode)) .eraseToAnyPublisher() } + return downloadXcode(availableXcode: availableXcode, downloader: downloader) } } diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index db190ca..95db88a 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -312,6 +312,24 @@ class AppState: ObservableObject { // MARK: - Install + func checkMinVersionAndInstall(id: Xcode.ID) { + guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } + + // Check to see if users MacOS is supported + if let requiredMacOSVersion = availableXcode.requiredMacOSVersion { + let split = requiredMacOSVersion.components(separatedBy: ".").compactMap { Int($0) } + let xcodeMinimumMacOSVersion = OperatingSystemVersion(majorVersion: split[safe: 0] ?? 0, minorVersion: split[safe: 1] ?? 0, patchVersion: split[safe: 2] ?? 0) + + if !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion) { + // prompt + self.presentedAlert = .checkMinSupportedVersion(xcode: availableXcode, macOS: ProcessInfo.processInfo.operatingSystemVersion.versionString()) + return + } + } + + install(id: id) + } + func install(id: Xcode.ID) { guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } @@ -631,3 +649,9 @@ class AppState: ObservableObject { let sessionData: AppleSessionData } } + +extension OperatingSystemVersion { + func versionString() -> String { + return String(majorVersion) + "." + String(minorVersion) + "." + String(patchVersion) + } +} diff --git a/Xcodes/Backend/Collection+.swift b/Xcodes/Backend/Collection+.swift new file mode 100644 index 0000000..5a59f33 --- /dev/null +++ b/Xcodes/Backend/Collection+.swift @@ -0,0 +1,17 @@ +// +// Collection+.swift +// Xcodes +// +// Created by Matt Kiazyk on 2022-04-11. +// Copyright © 2022 Robots and Pencils. All rights reserved. +// + +import Foundation + +public extension Collection { + + /// Returns the element at the specified index iff it is within bounds, otherwise nil. + subscript (safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/Xcodes/Backend/XcodeCommands.swift b/Xcodes/Backend/XcodeCommands.swift index 2e904ac..78d4504 100644 --- a/Xcodes/Backend/XcodeCommands.swift +++ b/Xcodes/Backend/XcodeCommands.swift @@ -44,7 +44,7 @@ struct InstallButton: View { private func install() { guard let xcode = xcode else { return } - appState.install(id: xcode.id) + appState.checkMinVersionAndInstall(id: xcode.id) } } diff --git a/Xcodes/Frontend/Common/XcodesAlert.swift b/Xcodes/Frontend/Common/XcodesAlert.swift index c57d089..c928501 100644 --- a/Xcodes/Frontend/Common/XcodesAlert.swift +++ b/Xcodes/Frontend/Common/XcodesAlert.swift @@ -4,12 +4,14 @@ enum XcodesAlert: Identifiable { case cancelInstall(xcode: Xcode) case privilegedHelper case generic(title: String, message: String) + case checkMinSupportedVersion(xcode: AvailableXcode, macOS: String) var id: Int { switch self { case .cancelInstall: return 1 case .privilegedHelper: return 2 case .generic: return 3 + case .checkMinSupportedVersion: return 4 } } } diff --git a/Xcodes/Frontend/MainWindow.swift b/Xcodes/Frontend/MainWindow.swift index 3403e34..022413c 100644 --- a/Xcodes/Frontend/MainWindow.swift +++ b/Xcodes/Frontend/MainWindow.swift @@ -151,6 +151,18 @@ struct MainWindow: View { action: { appState.presentedAlert = nil } ) ) + case let .checkMinSupportedVersion(xcode, deviceVersion): + return Alert( + title: Text("Minimum requirements not met"), + message: Text("Xcode \(xcode.version.descriptionWithoutBuildMetadata) requires MacOS \(xcode.requiredMacOSVersion ?? ""), but you are running MacOS \(deviceVersion), do you still want to install it?"), + primaryButton: .default( + Text("Install"), + action: { + self.appState.install(id: xcode.version) + } + ), + secondaryButton: .cancel(Text("Cancel")) + ) } } } diff --git a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift index 5d88a70..fbb702b 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift @@ -105,7 +105,7 @@ struct XcodeListViewRow: View { .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: selected)) .help("Open this version") case .notInstalled: - Button("INSTALL") { appState.install(id: xcode.id) } + Button("INSTALL") { appState.checkMinVersionAndInstall(id: xcode.id) } .buttonStyle(AppStoreButtonStyle(primary: false, highlighted: selected)) .help("Install this version") case let .installing(installationStep):