Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update select command to use .xcode-version #238

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Sources/XcodesKit/Version+Xcode.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import Path
import Version

public extension Version {
Expand Down Expand Up @@ -55,6 +56,21 @@ public extension Version {
self = Version(major: major, minor: minor, patch: patch, prereleaseIdentifiers: prereleaseIdentifiers, buildMetadataIdentifiers: [buildMetadataIdentifier].compactMap { $0 })
}

/// Attempt to instatiate a `Version` using the `.xcode-version` file in the provided directory
static func fromXcodeVersionFile(inDirectory: Path = Path.cwd) -> Version? {
let xcodeVersionFilePath = inDirectory.join(".xcode-version")
guard
Current.files.fileExists(atPath: xcodeVersionFilePath.string),
let contents = Current.files.contents(atPath: xcodeVersionFilePath.string),
let versionString = String(data: contents, encoding: .utf8),
let version = Version(gemVersion: versionString)
else {
return nil
}

return version
}

/// The intent here is to match Apple's marketing version
///
/// Only show the patch number if it's not 0
Expand Down
30 changes: 11 additions & 19 deletions Sources/XcodesKit/XcodeInstaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,13 @@ public final class XcodeInstaller {
return self.downloadXcode(version: latestPrereleaseXcode.version, dataSource: dataSource, downloader: downloader, willInstall: willInstall)
}
case .path(let versionString, let path):
guard let version = Version(xcodeVersion: versionString) ?? versionFromXcodeVersionFile() else {
guard let version = Version(xcodeVersion: versionString) ?? Version.fromXcodeVersionFile() else {
throw Error.invalidVersion(versionString)
}
let xcode = Xcode(version: version, url: path.url, filename: String(path.string.suffix(fromLast: "/")), releaseDate: nil)
return Promise.value((xcode, path.url))
case .version(let versionString):
guard let version = Version(xcodeVersion: versionString) ?? versionFromXcodeVersionFile() else {
guard let version = Version(xcodeVersion: versionString) ?? Version.fromXcodeVersionFile() else {
throw Error.invalidVersion(versionString)
}
if willInstall, let installedXcode = Current.files.installedXcodes(destination).first(where: { $0.version.isEquivalent(to: version) }) {
Expand All @@ -280,14 +280,6 @@ public final class XcodeInstaller {
}
}

private func versionFromXcodeVersionFile() -> Version? {
let xcodeVersionFilePath = Path.cwd.join(".xcode-version")
let version = (try? Data(contentsOf: xcodeVersionFilePath.url))
.flatMap { String(data: $0, encoding: .utf8) }
.flatMap(Version.init(gemVersion:))
return version
}

private func downloadXcode(version: Version, dataSource: DataSource, downloader: Downloader, willInstall: Bool) -> Promise<(Xcode, URL)> {
return firstly { () -> Promise<Void> in
switch dataSource {
Expand Down Expand Up @@ -414,13 +406,13 @@ public final class XcodeInstaller {
.recover { error -> Promise<Void> in

if let error = error as? Client.Error {
switch error {
case .invalidUsernameOrPassword(_):
// remove any keychain password if we fail to log with an invalid username or password so it doesn't try again.
try? Current.keychain.remove(username)
default:
break
}
switch error {
case .invalidUsernameOrPassword(_):
// remove any keychain password if we fail to log with an invalid username or password so it doesn't try again.
try? Current.keychain.remove(username)
default:
break
}
}

return Promise(error: error)
Expand Down Expand Up @@ -529,7 +521,7 @@ public final class XcodeInstaller {

return attemptRetryableTask(maximumRetryCount: 3) {
let (progress, promise) = Current.shell.downloadWithAria2(
aria2Path,
aria2Path,
xcode.url,
destination,
cookies
Expand Down Expand Up @@ -561,7 +553,7 @@ public final class XcodeInstaller {
switch archiveURL.pathExtension {
case "xip":
return unarchiveAndMoveXIP(at: archiveURL, to: destinationURL, experimentalUnxip: experimentalUnxip).map { xcodeURL in
guard
guard
let path = Path(url: xcodeURL),
Current.files.fileExists(atPath: path.string),
let installedXcode = InstalledXcode(path: path)
Expand Down
3 changes: 2 additions & 1 deletion Sources/XcodesKit/XcodeSelect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ public func selectXcode(shouldPrint: Bool, pathOrVersion: String, directory: Pat
}
}

let versionToSelect = pathOrVersion.isEmpty ? Version.fromXcodeVersionFile() : Version(xcodeVersion: pathOrVersion)
let installedXcodes = Current.files.installedXcodes(directory)
if let version = Version(xcodeVersion: pathOrVersion),
if let version = versionToSelect,
let installedXcode = installedXcodes.first(withVersion: version) {
let selectedInstalledXcodeVersion = installedXcodes.first { output.out.hasPrefix($0.path.string) }.map { $0.version }
if installedXcode.version == selectedInstalledXcodeVersion {
Expand Down
4 changes: 2 additions & 2 deletions Sources/xcodes/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ struct Xcodes: AsyncParsableCommand {
}
.then { xcode -> Promise<Void> in
if select {
return selectXcode(shouldPrint: print, pathOrVersion: xcode.path.string, directory: destination, fallbackToInteractive: false)
return selectXcode(shouldPrint: print, pathOrVersion: xcode.path.string, directory: destination, fallbackToInteractive: false)
} else {
return .init()
}
Expand Down Expand Up @@ -378,7 +378,7 @@ struct Xcodes: AsyncParsableCommand {
static var configuration = CommandConfiguration(
abstract: "Change the selected Xcode",
discussion: """
Run without any arguments to interactively select from a list, or provide an absolute path.
Select a version of Xcode by specifying a version number or an absolute path. Run without arguments to select the version specified in your .xcode-version file. If no version file is found, you will be prompted to interactively select from a list.

EXAMPLES:
xcodes select
Expand Down
61 changes: 61 additions & 0 deletions Tests/XcodesKitTests/XcodesKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,67 @@ final class XcodesKitTests: XCTestCase {

""")
}

func test_SelectUsingXcodeVersionFile() {
var log = ""
XcodesKit.Current.logging.log = { log.append($0 + "\n") }

// There are installed Xcodes
Current.files.installedXcodes = { _ in
[InstalledXcode(path: Path("/Applications/Xcode-0.0.0.app")!)!,
InstalledXcode(path: Path("/Applications/Xcode-2.0.1.app")!)!]
}
Current.files.contentsAtPath = { path in
if path == "/Applications/Xcode-0.0.0.app/Contents/Info.plist" {
let url = Bundle.module.url(forResource: "Stub-0.0.0.Info", withExtension: "plist", subdirectory: "Fixtures")!
return try? Data(contentsOf: url)
}
else if path == "/Applications/Xcode-2.0.1.app/Contents/Info.plist" {
let url = Bundle.module.url(forResource: "Stub-2.0.1.Info", withExtension: "plist", subdirectory: "Fixtures")!
return try? Data(contentsOf: url)
}
else if path.contains("version.plist") {
let url = Bundle.module.url(forResource: "Stub.version", withExtension: "plist", subdirectory: "Fixtures")!
return try? Data(contentsOf: url)
}
else if path.hasSuffix(".xcode-version") {
return "2.0.1\n".data(using: .utf8)
}
else {
return nil
}
}
Current.files.fileExistsAtPath = { path in
if path == "" {
return false
}
return true
}
// It prints the expected paths
var xcodeSelectPrintPathCallCount = 0
Current.shell.xcodeSelectPrintPath = {
defer { xcodeSelectPrintPathCallCount += 1 }
if xcodeSelectPrintPathCallCount == 0 {
return Promise.value((status: 0, out: "/Applications/Xcode-0.0.0.app/Contents/Developer", err: ""))
} else if xcodeSelectPrintPathCallCount == 1 {
return Promise.value((status: 0, out: "/Applications/Xcode-2.0.1.app/Contents/Developer", err: ""))
} else {
fatalError("Unexpected third invocation of xcode select")
}
}
// It successfully switches
Current.shell.xcodeSelectSwitch = { _, _ in
Promise.value((status: 0, out: "", err: ""))
}

selectXcode(shouldPrint: false, pathOrVersion: "", directory: Path.root.join("Applications"))
.cauterize()

XCTAssertEqual(log, """
Selected /Applications/Xcode-2.0.1.app/Contents/Developer

""")
}

func test_Installed_InteractiveTerminal() {
var log = ""
Expand Down