Skip to content

Commit

Permalink
Consolidate versions from both download sites
Browse files Browse the repository at this point in the history
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.

Because pre-release versions that are installed also have build metadata, but this won't be the case for pre-release versions listed on /download/more, we also need to replace versions that are available with the installed ones with build metadata before printing them.
  • Loading branch information
interstateone committed Sep 19, 2019
1 parent a289f3d commit 3922cee
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 20 deletions.
27 changes: 18 additions & 9 deletions Sources/XcodesKit/Version+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions Sources/XcodesKit/XcodeList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: "/")))
Expand Down
20 changes: 16 additions & 4 deletions Sources/xcodes/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,28 @@ func login(_ username: String, password: String) -> Promise<Void> {
}
}

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 {
Expand Down
6 changes: 3 additions & 3 deletions Tests/XcodesKitTests/Version+XcodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")!))
}
}

0 comments on commit 3922cee

Please sign in to comment.