diff --git a/Sources/XcodesKit/XcodeInstaller.swift b/Sources/XcodesKit/XcodeInstaller.swift index 81aba81..39ad6a3 100644 --- a/Sources/XcodesKit/XcodeInstaller.swift +++ b/Sources/XcodesKit/XcodeInstaller.swift @@ -172,6 +172,11 @@ public final class XcodeInstaller { Current.shell.exit(0) } } + + /// Perform the install but don't exit out but return the installed xcode version as output instead + public func installWithoutLogging(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, experimentalUnxip: Bool) -> Promise { + self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: 0, experimentalUnxip: experimentalUnxip) + } private func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, attemptNumber: Int, experimentalUnxip: Bool) -> Promise { return firstly { () -> Promise<(Xcode, URL)> in diff --git a/Sources/XcodesKit/XcodeSelect.swift b/Sources/XcodesKit/XcodeSelect.swift index 596a57a..2788a9f 100644 --- a/Sources/XcodesKit/XcodeSelect.swift +++ b/Sources/XcodesKit/XcodeSelect.swift @@ -4,7 +4,7 @@ import Path import Version import Rainbow -public func selectXcode(shouldPrint: Bool, pathOrVersion: String, directory: Path) -> Promise { +public func selectXcode(shouldPrint: Bool, pathOrVersion: String, directory: Path, fallbackToInteractive: Bool = true) -> Promise { firstly { () -> Promise in Current.shell.xcodeSelectPrintPath() } @@ -49,18 +49,23 @@ public func selectXcode(shouldPrint: Bool, pathOrVersion: String, directory: Pat return Promise.value(()) } - return selectXcodeAtPath(pathToSelect) + let selectPromise = selectXcodeAtPath(pathToSelect) .done { output in Current.logging.log("Selected \(output.out)".green) Current.shell.exit(0) } - .recover { _ in - selectXcodeInteractively(currentPath: output.out, directory: directory) - .done { output in - Current.logging.log("Selected \(output.out)".green) - Current.shell.exit(0) - } - } + if fallbackToInteractive { + return selectPromise + .recover { _ in + selectXcodeInteractively(currentPath: output.out, directory: directory) + .done { output in + Current.logging.log("Selected \(output.out)".green) + Current.shell.exit(0) + } + } + } else { + return selectPromise + } } } } diff --git a/Sources/xcodes/main.swift b/Sources/xcodes/main.swift index 91f818e..380c33b 100644 --- a/Sources/xcodes/main.swift +++ b/Sources/xcodes/main.swift @@ -146,6 +146,9 @@ struct Xcodes: ParsableCommand { } struct Install: ParsableCommand { + enum InstallError: Error { + case notAlreadyInstalled + } static var configuration = CommandConfiguration( abstract: "Download and install a specific version of Xcode", discussion: """ @@ -182,6 +185,15 @@ struct Xcodes: ParsableCommand { @Flag(help: "Don't use aria2 to download Xcode, even if its available.") var noAria2: Bool = false + + @Flag(help: "Select the installed xcode version after installation.") + var select: Bool = false + + @Flag(help: "Whether to update the list before installing") + var update: Bool = false + + @ArgumentParser.Flag(name: [.customShort("p"), .customLong("print-path")], help: "Print the path of the selected Xcode") + var print: Bool = false @Flag(help: "Use the experimental unxip functionality. May speed up unarchiving by up to 2-3x.") var experimentalUnxip: Bool = false @@ -220,17 +232,60 @@ struct Xcodes: ParsableCommand { } let destination = getDirectory(possibleDirectory: directory) - - installer.install(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination, experimentalUnxip: experimentalUnxip) - .done { Install.exit() } + + if select == false { + // install normally + installer.install(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination, experimentalUnxip: experimentalUnxip) + .done { Install.exit() } + .catch { error in + Install.processDownloadOrInstall(error: error) + } + } else { + // check if the version is already installed and try to select it + firstly { () -> Promise in + if case .version(let version) = installation { + return selectXcode(shouldPrint: print, pathOrVersion: version, directory: destination, fallbackToInteractive: false) + } else { + return Promise { _ in + throw InstallError.notAlreadyInstalled + } + } + } + .done { Install.exit() } // successfully selected .catch { error in - Install.processDownloadOrInstall(error: error) + // select failed. Xcode must not be installed. + firstly { () -> Promise in + // update the list before installing only for version type because the other types already update internally + if update, case .version = installation { + Current.logging.log("Updating...") + return xcodeList.update(dataSource: globalDataSource.dataSource) + .then { _ -> Promise in + installer.installWithoutLogging(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination, experimentalUnxip: experimentalUnxip) + } + } else { + // install + return installer.installWithoutLogging(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination, experimentalUnxip: experimentalUnxip) + } + } + .then { xcode -> Promise in + Current.logging.log("\nXcode \(xcode.version.descriptionWithoutBuildMetadata) has been installed to \(xcode.path.string)".green) + + // Install was successful, now select it + return selectXcode(shouldPrint: print, pathOrVersion: xcode.path.string, directory: destination, fallbackToInteractive: false) + } + .done { + Install.exit() + } + .catch { error in + Install.processDownloadOrInstall(error: error) + } } - + } + RunLoop.current.run() } } - + struct Installed: ParsableCommand { static var configuration = CommandConfiguration( abstract: "List the versions of Xcode that are installed"