diff --git a/Sources/XcodesKit/XcodeInstaller.swift b/Sources/XcodesKit/XcodeInstaller.swift index f0c918a..445ad17 100644 --- a/Sources/XcodesKit/XcodeInstaller.swift +++ b/Sources/XcodesKit/XcodeInstaller.swift @@ -762,6 +762,21 @@ public final class XcodeInstaller { } } + public func printXcodePath(ofVersion versionString: String, searchingIn directory: Path) -> Promise { + return firstly { () -> Promise in + guard let version = Version(xcodeVersion: versionString) else { + throw Error.invalidVersion(versionString) + } + let installedXcodes = Current.files.installedXcodes(directory) + .sorted { $0.version < $1.version } + guard let installedXcode = installedXcodes.first(withVersion: version) else { + throw Error.versionNotInstalled(version) + } + Current.logging.log(installedXcode.path.string) + return Promise.value(()) + } + } + func unarchiveAndMoveXIP(at source: URL, to destination: URL, experimentalUnxip: Bool) -> Promise { return firstly { () -> Promise in Current.logging.log(InstallationStep.unarchiving(experimentalUnxip: experimentalUnxip).description) diff --git a/Sources/xcodes/main.swift b/Sources/xcodes/main.swift index c98034e..8c3c611 100644 --- a/Sources/xcodes/main.swift +++ b/Sources/xcodes/main.swift @@ -241,6 +241,10 @@ struct Xcodes: ParsableCommand { static var configuration = CommandConfiguration( abstract: "List the versions of Xcode that are installed" ) + + @Argument(help: "The version installed to which to print the path for", + completion: .custom { _ in Current.files.installedXcodes(getDirectory(possibleDirectory: nil)).sorted { $0.version < $1.version }.map { $0.version.appleDescription } }) + var version: [String] = [] @OptionGroup var globalDirectory: GlobalDirectoryOption @@ -252,8 +256,16 @@ struct Xcodes: ParsableCommand { Rainbow.enabled = Rainbow.enabled && globalColor.color let directory = getDirectory(possibleDirectory: globalDirectory.directory) - - installer.printInstalledXcodes(directory: directory) + + installer.printXcodePath(ofVersion: version.joined(separator: " "), searchingIn: directory) + .recover { error -> Promise in + switch error { + case XcodeInstaller.Error.invalidVersion: + return installer.printInstalledXcodes(directory: directory) + default: + throw error + } + } .done { Installed.exit() } .catch { error in Installed.exit(withLegibleError: error) } diff --git a/Tests/XcodesKitTests/XcodesKitTests.swift b/Tests/XcodesKitTests/XcodesKitTests.swift index 91da13c..0d3c850 100644 --- a/Tests/XcodesKitTests/XcodesKitTests.swift +++ b/Tests/XcodesKitTests/XcodesKitTests.swift @@ -1212,6 +1212,84 @@ final class XcodesKitTests: XCTestCase { """ ) } + + func test_Installed_WithValidVersion_PrintsXcodePath() { + var log = "" + XcodesKit.Current.logging.log = { log.append($0 + "\n") } + + // There are installed Xcodes + Current.files.contentsAtPath = { path in + if path == "/Applications/Xcode-2.0.0.app/Contents/Info.plist" { + let url = Bundle.module.url(forResource: "Stub-2.0.0.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 { + return nil + } + } + let installedXcodes = [ + InstalledXcode(path: Path("/Applications/Xcode-2.0.0.app")!)!, + ] + Current.files.installedXcodes = { _ in installedXcodes } + + // One is selected + Current.shell.xcodeSelectPrintPath = { + Promise.value((status: 0, out: "/Applications/Xcode-2.0.0.app/Contents/Developer", err: "")) + } + + // Standard output is not an interactive terminal + Current.shell.isatty = { false } + + installer.printXcodePath(ofVersion: "2", searchingIn: Path.root/"Applications") + .cauterize() + + XCTAssertEqual( + log, + """ + /Applications/Xcode-2.0.0.app + + """ + ) + } + + func test_Installed_WithUninstalledVersion_ThrowsError() { + var log = "" + XcodesKit.Current.logging.log = { log.append($0 + "\n") } + + // There are installed Xcodes + Current.files.contentsAtPath = { path in + if path == "/Applications/Xcode-2.0.0.app/Contents/Info.plist" { + let url = Bundle.module.url(forResource: "Stub-2.0.0.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 { + return nil + } + } + let installedXcodes = [ + InstalledXcode(path: Path("/Applications/Xcode-2.0.0.app")!)!, + ] + Current.files.installedXcodes = { _ in installedXcodes } + + // One is selected + Current.shell.xcodeSelectPrintPath = { + Promise.value((status: 0, out: "/Applications/Xcode-2.0.0.app/Contents/Developer", err: "")) + } + + // Standard output is not an interactive terminal + Current.shell.isatty = { false } + + installer.printXcodePath(ofVersion: "3", searchingIn: Path.root/"Applications") + .catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.versionNotInstalled(Version(xcodeVersion: "3")!)) } + } func test_Signout_WithExistingSession() { var keychainDidRemove = false