diff --git a/Sources/XcodesKit/Version+.swift b/Sources/XcodesKit/Version+.swift index 8ff839f..d5cd1ee 100644 --- a/Sources/XcodesKit/Version+.swift +++ b/Sources/XcodesKit/Version+.swift @@ -10,20 +10,29 @@ public extension Version { /// If release versions, don't compare build metadata because that's not provided in the /downloads/more list /// if beta versions, compare build metadata because it's available in versions.plist - func isEquivalentForDeterminingIfInstalled(to other: Version) -> Bool { + func isEquivalentForDeterminingIfInstalled(toInstalled installed: Version) -> Bool { let isBeta = !prereleaseIdentifiers.isEmpty - let otherIsBeta = !other.prereleaseIdentifiers.isEmpty + let otherIsBeta = !installed.prereleaseIdentifiers.isEmpty if isBeta && otherIsBeta { - return major == other.major && - minor == other.minor && - patch == other.patch && - buildMetadataIdentifiers.map { $0.lowercased() } == other.buildMetadataIdentifiers.map { $0.lowercased() } + if buildMetadataIdentifiers.isEmpty { + return major == installed.major && + minor == installed.minor && + patch == installed.patch && + prereleaseIdentifiers == installed.prereleaseIdentifiers + } + else { + return major == installed.major && + minor == installed.minor && + patch == installed.patch && + prereleaseIdentifiers == installed.prereleaseIdentifiers && + buildMetadataIdentifiers.map { $0.lowercased() } == installed.buildMetadataIdentifiers.map { $0.lowercased() } + } } else if !isBeta && !otherIsBeta { - return major == other.major && - minor == other.minor && - patch == other.patch + return major == installed.major && + minor == installed.minor && + patch == installed.patch } return false diff --git a/Sources/XcodesKit/XcodeList.swift b/Sources/XcodesKit/XcodeList.swift index f827d5c..bcc7473 100644 --- a/Sources/XcodesKit/XcodeList.swift +++ b/Sources/XcodesKit/XcodeList.swift @@ -33,8 +33,14 @@ public final class XcodeList { public func update() -> Promise<[Xcode]> { return when(fulfilled: releasedXcodes(), prereleaseXcodes()) - .map { availableXcodes, prereleaseXcodes in - let xcodes = availableXcodes + prereleaseXcodes + .map { releasedXcodes, prereleaseXcodes in + // Starting with Xcode 11 beta 6, developer.apple.com/download and developer.apple.com/download/more both list some pre-release versions of Xcode. + // Previously pre-release versions only appeared on developer.apple.com/download. + // /download/more doesn't include build numbers, so we trust that if the version number and prerelease identifiers are the same that they're the same build. + // If an Xcode version is listed on both sites then prefer the one on /download because the build metadata is used to compare against installed Xcodes. + let xcodes = releasedXcodes.filter { releasedXcode in + prereleaseXcodes.contains { $0.version.isEqualWithoutBuildMetadataIdentifiers(to: releasedXcode.version) } == false + } + prereleaseXcodes self.availableXcodes = xcodes try? self.cacheAvailableXcodes(xcodes) return xcodes @@ -92,8 +98,7 @@ extension XcodeList { guard let xcodeFile = download.files.first(where: { $0.remotePath.hasSuffix("dmg") || $0.remotePath.hasSuffix("xip") }), let url = URL(string: urlPrefix + xcodeFile.remotePath), - let versionString = download.name.replacingOccurrences(of: "Xcode ", with: "").split(separator: " ").map(String.init).first, - let version = Version(tolerant: versionString) + let version = Version(xcodeVersion: download.name) else { return nil } return Xcode(version: version, url: url, filename: String(xcodeFile.remotePath.suffix(fromLast: "/"))) diff --git a/Sources/xcodes/main.swift b/Sources/xcodes/main.swift index 0829c8a..ef5effb 100644 --- a/Sources/xcodes/main.swift +++ b/Sources/xcodes/main.swift @@ -120,16 +120,28 @@ func login(_ username: String, password: String) -> Promise { } } -func printAvailableXcodes(_ xcodes: [Xcode], installed: [InstalledXcode]) { +func printAvailableXcodes(_ xcodes: [Xcode], installed installedXcodes: [InstalledXcode]) { var allXcodeVersions = xcodes.map { $0.version } - for xcode in installed where !allXcodeVersions.contains(where: { $0.isEquivalentForDeterminingIfInstalled(to: xcode.version) }) { - allXcodeVersions.append(xcode.version) + for installedXcode in installedXcodes { + // If an installed version isn't listed online, add the installed version + if !allXcodeVersions.contains(where: { version in + version.isEquivalentForDeterminingIfInstalled(toInstalled: installedXcode.version) + }) { + allXcodeVersions.append(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 with build metadata + else if let index = allXcodeVersions.firstIndex(where: { version in + version.isEquivalentForDeterminingIfInstalled(toInstalled: installedXcode.version) && + version.buildMetadataIdentifiers.isEmpty + }) { + allXcodeVersions[index] = installedXcode.version + } } allXcodeVersions .sorted { $0 < $1 } .forEach { xcodeVersion in - if installed.contains(where: { $0.version.isEquivalentForDeterminingIfInstalled(to: xcodeVersion) }) { + if installedXcodes.contains(where: { xcodeVersion.isEquivalentForDeterminingIfInstalled(toInstalled: $0.version) }) { print("\(xcodeVersion.xcodeDescription) (Installed)") } else { diff --git a/Tests/XcodesKitTests/Version+XcodeTests.swift b/Tests/XcodesKitTests/Version+XcodeTests.swift index 5bc5fc0..e534b3a 100644 --- a/Tests/XcodesKitTests/Version+XcodeTests.swift +++ b/Tests/XcodesKitTests/Version+XcodeTests.swift @@ -29,8 +29,8 @@ class VersionXcodeTests: XCTestCase { } func test_Equivalence() { - XCTAssertTrue(Version("10.2.1")!.isEquivalentForDeterminingIfInstalled(to: Version("10.2.1+abcdef")!)) - XCTAssertFalse(Version("10.2.1-beta+qwerty")!.isEquivalentForDeterminingIfInstalled(to: Version("10.2.1-beta+abcdef")!)) - XCTAssertTrue(Version("10.2.1-beta+qwerty")!.isEquivalentForDeterminingIfInstalled(to: Version("10.2.1-beta+QWERTY")!)) + XCTAssertTrue(Version("10.2.1")!.isEquivalentForDeterminingIfInstalled(toInstalled: Version("10.2.1+abcdef")!)) + XCTAssertFalse(Version("10.2.1-beta+qwerty")!.isEquivalentForDeterminingIfInstalled(toInstalled: Version("10.2.1-beta+abcdef")!)) + XCTAssertTrue(Version("10.2.1-beta+qwerty")!.isEquivalentForDeterminingIfInstalled(toInstalled: Version("10.2.1-beta+QWERTY")!)) } }