diff --git a/Sources/XcodesKit/XcodeList.swift b/Sources/XcodesKit/XcodeList.swift index b46c970..f3a3ed6 100644 --- a/Sources/XcodesKit/XcodeList.swift +++ b/Sources/XcodesKit/XcodeList.swift @@ -165,30 +165,32 @@ extension XcodeList { } return xcodes } - .map(filterPrereleasesThatMatchReleaseBuildMetadataIdentifiers) + .map(XcodeList.filterPrereleasesThatMatchReleaseBuildMetadataIdentifiers) } /// Xcode Releases may have multiple releases with the same build metadata when a build doesn't change between candidate and final releases. /// For example, 12.3 RC and 12.3 are both build 12C33 /// We don't care about that difference, so only keep the final release (GM or Release, in XCModel terms). /// The downside of this is that a user could technically have both releases installed, and so they won't both be shown in the list, but I think most users wouldn't do this. - func filterPrereleasesThatMatchReleaseBuildMetadataIdentifiers(_ xcodes: [Xcode]) -> [Xcode] { + /// This may not preserve order. + static func filterPrereleasesThatMatchReleaseBuildMetadataIdentifiers(_ xcodes: [Xcode]) -> [Xcode] { + + let xcodesByBuildMetadataIdentifiers = + Dictionary(grouping: xcodes, by: { $0.version.buildMetadataIdentifiers }) + var filteredXcodes: [Xcode] = [] - for xcode in xcodes { - if xcode.version.buildMetadataIdentifiers.isEmpty { - filteredXcodes.append(xcode) + for (buildMetadataIdentifiers, xcodes) in xcodesByBuildMetadataIdentifiers { + if buildMetadataIdentifiers.isEmpty || xcodes.count == 1 { + filteredXcodes.append(contentsOf: xcodes) continue } - let xcodesWithSameBuildMetadataIdentifiers = xcodes - .filter({ $0.version.buildMetadataIdentifiers == xcode.version.buildMetadataIdentifiers }) - if xcodesWithSameBuildMetadataIdentifiers.count > 1, - xcode.version.prereleaseIdentifiers.isEmpty || xcode.version.prereleaseIdentifiers == ["GM"] { - filteredXcodes.append(xcode) - } else if xcodesWithSameBuildMetadataIdentifiers.count == 1 { - filteredXcodes.append(xcode) - } + // Use the final release if there is one, otherwise just (arbitrarily) pick the first. + let finalRelease = xcodes.first(where: { + $0.version.prereleaseIdentifiers.isEmpty || $0.version.prereleaseIdentifiers == ["GM"] }) + filteredXcodes.append(finalRelease ?? xcodes.first!) } + return filteredXcodes } } diff --git a/Tests/XcodesKitTests/XcodeListTests.swift b/Tests/XcodesKitTests/XcodeListTests.swift new file mode 100644 index 0000000..1176e42 --- /dev/null +++ b/Tests/XcodesKitTests/XcodeListTests.swift @@ -0,0 +1,61 @@ +import XCTest +import Version +@testable import XcodesKit + +class XcodeListTests: XCTestCase { + + func xcodeFromVersion(version: Version?) -> Xcode + { + return Xcode(version: version!, + url: URL(fileURLWithPath: "https://developer.apple.com/Xcode_example.app"), + filename: "Xcode_example.app", + releaseDate: nil) + } + + let versions = [ + + // Single version + Version(major: 1, minor: 1, patch: 1, prereleaseIdentifiers: [], buildMetadataIdentifiers: ["1.1.1.build1"]), + + // 2 versions with the same build, one is beta + Version(major: 1, minor: 2, patch: 1, prereleaseIdentifiers: [], buildMetadataIdentifiers: ["1.2.1.build1"]), + Version(major: 1, minor: 2, patch: 1, prereleaseIdentifiers: ["beta"], buildMetadataIdentifiers: ["1.2.1.build1"]), + + // 2 versions with the same build, one is beta (other GM) + Version(major: 1, minor: 3, patch: 1, prereleaseIdentifiers: ["GM"], buildMetadataIdentifiers: ["1.3.1.build1"]), + Version(major: 1, minor: 3, patch: 1, prereleaseIdentifiers: ["beta"], buildMetadataIdentifiers: ["1.3.1.build1"]), + + // 2 versions with no buildMetaIdentifiers. + Version(major: 1, minor: 4, patch: 1, prereleaseIdentifiers: [], buildMetadataIdentifiers: []), + Version(major: 1, minor: 4, patch: 2, prereleaseIdentifiers: [], buildMetadataIdentifiers: []), + ] + + let filteredVersionsExpected = [ + // Single version + Version(major: 1, minor: 1, patch: 1, prereleaseIdentifiers: [], buildMetadataIdentifiers: ["1.1.1.build1"]), + + // 2 versions with the same build, one is beta + Version(major: 1, minor: 2, patch: 1, prereleaseIdentifiers: [], buildMetadataIdentifiers: ["1.2.1.build1"]), + //Version(major: 1, minor: 2, patch: 1, prereleaseIdentifiers: ["beta"], buildMetadataIdentifiers: ["1.2.1.build1"]), + + // 2 versions with the same build, one is beta (other GM) + Version(major: 1, minor: 3, patch: 1, prereleaseIdentifiers: ["GM"], buildMetadataIdentifiers: ["1.3.1.build1"]), + //Version(major: 1, minor: 3, patch: 1, prereleaseIdentifiers: ["beta"], buildMetadataIdentifiers: ["1.3.1.build1"]), + + // 2 versions with no buildMetaIdentifiers. + Version(major: 1, minor: 4, patch: 1, prereleaseIdentifiers: [], buildMetadataIdentifiers: []), + Version(major: 1, minor: 4, patch: 2, prereleaseIdentifiers: [], buildMetadataIdentifiers: []), + ] + + var filteredVersions : [Version] = [] + + override func setUpWithError() throws { + let xcodes = versions.map(xcodeFromVersion) + let filteredXcodes = XcodeList.filterPrereleasesThatMatchReleaseBuildMetadataIdentifiers(xcodes) + filteredVersions = filteredXcodes.map { $0.version } + } + + func test_filterPrereleasesThatMatchReleaseBuildMetadataIdentifiers() { + XCTAssertEqual(filteredVersions.sorted(), filteredVersionsExpected.sorted()) + } +}