From 62237bf4a8a06c2087a481d5c8e58cd6717dbe93 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Sat, 16 Jan 2021 11:10:23 -0700 Subject: [PATCH] Fix a bug when an installed version was appended We were appending a version without appending a corresponding AvailableXcode, and these two arrays were being zipped later so they wouldn't line up. This change simplifies this method a bit by working on only a single array, and then also moves that appending to the end after the array of Xcodes is created. --- Xcodes/Backend/AppState.swift | 57 ++++++++++++++++----------- Xcodes/Backend/AvailableXcode.swift | 2 +- XcodesTests/AppStateUpdateTests.swift | 18 +++++++++ 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index e3d84b2..8be93b1 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -357,48 +357,40 @@ class AppState: ObservableObject { } func updateAllXcodes(availableXcodes: [AvailableXcode], installedXcodes: [InstalledXcode], selectedXcodePath: String?) { - // First, adjust all of the available Xcode versions so that available and installed versions line up and the second part of this function works properly. - var allAvailableXcodeVersions = availableXcodes.map { $0.version } + var adjustedAvailableXcodes = availableXcodes + + // First, adjust all of the available Xcodes so that available and installed versions line up and the second part of this function works properly. for installedXcode in installedXcodes { // We can trust that build metadata identifiers are unique for each version of Xcode, so if we have it then it's all we need. // If build metadata matches exactly, replace the available version with the installed version. // This should handle both Xcode Releases versions which can have different prerelease identifiers and Apple versions which rarely have build metadata identifiers. - if let index = allAvailableXcodeVersions.firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) { - allAvailableXcodeVersions[index] = installedXcode.version - } - // If an installed version isn't listed online, add the installed version - // Xcode Releases should have all versions - // Apple didn't used to keep all prerelease versions around but has started to recently - else if !allAvailableXcodeVersions.contains(where: { version in - version.isEquivalent(to: installedXcode.version) - }) { - allAvailableXcodeVersions.append(installedXcode.version) + if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) { + adjustedAvailableXcodes[index].version = installedXcode.version } // If an installed version is the same as one that's listed online which doesn't have build metadata, replace it with the installed version - // This was originally added for Apple versions - else if let index = allAvailableXcodeVersions.firstIndex(where: { version in - version.isEquivalent(to: installedXcode.version) && - version.buildMetadataIdentifiers.isEmpty + // Not all prerelease Apple versions available online include build metadata + else if let index = adjustedAvailableXcodes.firstIndex(where: { availableXcode in + availableXcode.version.isEquivalent(to: installedXcode.version) && + availableXcode.version.buildMetadataIdentifiers.isEmpty }) { - allAvailableXcodeVersions[index] = installedXcode.version + adjustedAvailableXcodes[index].version = installedXcode.version } } // Map all of the available versions into Xcode values that join available and installed Xcode data for display. - allXcodes = zip(allAvailableXcodeVersions, availableXcodes) - .sorted(by: { $0.0 > $1.0 }) - .map { availableXcodeVersion, availableXcode in + var newAllXcodes = adjustedAvailableXcodes + .map { availableXcode -> Xcode in let installedXcode = installedXcodes.first(where: { installedXcode in - availableXcodeVersion.isEquivalent(to: installedXcode.version) + availableXcode.version.isEquivalent(to: installedXcode.version) }) // If the existing install state is "installing", keep it - let existingXcodeInstallState = allXcodes.first { $0.version == availableXcodeVersion && $0.installing }?.installState + let existingXcodeInstallState = allXcodes.first { $0.version == availableXcode.version && $0.installing }?.installState // Otherwise, determine it from whether there's an installed Xcode let defaultXcodeInstallState: XcodeInstallState = installedXcode != nil ? .installed : .notInstalled return Xcode( - version: availableXcodeVersion, + version: availableXcode.version, installState: existingXcodeInstallState ?? defaultXcodeInstallState, selected: installedXcode != nil && selectedXcodePath?.hasPrefix(installedXcode!.path.string) == true, path: installedXcode?.path.string, @@ -409,6 +401,25 @@ class AppState: ObservableObject { compilers: availableXcode.compilers ) } + + // If an installed version isn't listed in the available versions, add the installed version + // Xcode Releases should have all versions + // Apple didn't used to keep all prerelease versions around but has started to recently + for installedXcode in installedXcodes { + if !newAllXcodes.contains(where: { xcode in xcode.version.isEquivalent(to: installedXcode.version) }) { + newAllXcodes.append( + Xcode( + version: installedXcode.version, + installState: .installed, + selected: selectedXcodePath?.hasPrefix(installedXcode.path.string) == true, + path: installedXcode.path.string, + icon: NSWorkspace.shared.icon(forFile: installedXcode.path.string) + ) + ) + } + } + + self.allXcodes = newAllXcodes.sorted { $0.version > $1.version } } // MARK: - Private diff --git a/Xcodes/Backend/AvailableXcode.swift b/Xcodes/Backend/AvailableXcode.swift index 8ef4e18..c1810ba 100644 --- a/Xcodes/Backend/AvailableXcode.swift +++ b/Xcodes/Backend/AvailableXcode.swift @@ -5,7 +5,7 @@ import struct XCModel.Compilers /// A version of Xcode that's available for installation public struct AvailableXcode: Codable { - public let version: Version + public var version: Version public let url: URL public let filename: String public let releaseDate: Date? diff --git a/XcodesTests/AppStateUpdateTests.swift b/XcodesTests/AppStateUpdateTests.swift index 13983b9..a640c90 100644 --- a/XcodesTests/AppStateUpdateTests.swift +++ b/XcodesTests/AppStateUpdateTests.swift @@ -88,4 +88,22 @@ class AppStateUpdateTests: XCTestCase { // XCModel types aren't equatable, so just check for non-nil for now XCTAssertNotNil(subject.allXcodes[0].sdks) } + + func testAppendingInstalledVersionThatIsNotAvailable() { + subject.allXcodes = [ + ] + + subject.updateAllXcodes( + availableXcodes: [ + AvailableXcode(version: Version("1.2.3")!, url: URL(string: "https://apple.com/xcode.xip")!, filename: "mock.xip", releaseDate: nil, sdks: .init(iOS: .init("14.3"))) + ], + installedXcodes: [ + // There's a version installed which for some reason isn't listed online + InstalledXcode(path: Path("/Applications/Xcode-0.0.0.app")!)! + ], + selectedXcodePath: nil + ) + + XCTAssertEqual(subject.allXcodes.map(\.version), [Version("1.2.3")!, Version("0.0.0+ABC123")!]) + } }