Skip to content

Commit

Permalink
Add force flag to the update and install command (#157)
Browse files Browse the repository at this point in the history
* Define force argument

* Add documentation

* Update Installer to accept force installs

* Add tests
  • Loading branch information
Pedro Piñera Buendía committed Nov 7, 2018
1 parent 3a15268 commit 30ff33c
Show file tree
Hide file tree
Showing 15 changed files with 307 additions and 57 deletions.
1 change: 0 additions & 1 deletion Sources/TuistCore/Utils/EnvironmentController.swift
@@ -1,6 +1,5 @@
import Basic
import Foundation
import TuistCore

/// Protocol that defines the interface of a local environment controller.
/// It manages the local directory where tuistenv stores the tuist versions and user settings.
Expand Down
2 changes: 1 addition & 1 deletion Sources/TuistEnvKit/Commands/BundleCommand.swift
Expand Up @@ -81,7 +81,7 @@ final class BundleCommand: Command {
// Installing
if !fileHandler.exists(versionPath) {
printer.print("Version \(version) not available locally. Installing...")
try installer.install(version: version)
try installer.install(version: version, force: false)
}

// Copying
Expand Down
4 changes: 2 additions & 2 deletions Sources/TuistEnvKit/Commands/CommandRunner.swift
Expand Up @@ -93,7 +93,7 @@ class CommandRunner: CommandRunning {
if let highgestVersion = versionsController.semverVersions().last?.description {
version = highgestVersion
} else {
try updater.update()
try updater.update(force: false)
guard let highgestVersion = versionsController.semverVersions().last?.description else {
throw CommandRunnerError.versionNotFound
}
Expand All @@ -107,7 +107,7 @@ class CommandRunner: CommandRunning {
func runVersion(_ version: String) throws {
if !versionsController.versions().contains(where: { $0.description == version }) {
printer.print("Version \(version) not found locally. Installing...")
try installer.install(version: version)
try installer.install(version: version, force: false)
}

let path = versionsController.path(version: version)
Expand Down
25 changes: 24 additions & 1 deletion Sources/TuistEnvKit/Commands/InstallCommand.swift
Expand Up @@ -2,19 +2,33 @@ import Foundation
import TuistCore
import Utility

/// Command that installs new versions of Tuist in the system.
final class InstallCommand: Command {
// MARK: - Command

/// Command name.
static var command: String = "install"

/// Command description.
static var overview: String = "Installs a version of tuist"

// MARK: - Attributes

/// Controller to manage system versions.
private let versionsController: VersionsControlling

/// Printer instance to output messages to the user.
private let printer: Printing

/// Installer instance to run the installation.
private let installer: Installing

/// Version argument to specify the version that will be installed.
let versionArgument: PositionalArgument<String>

/// Force argument (-f). When passed, it re-installs the version compiling it from the source.
let forceArgument: OptionArgument<Bool>

// MARK: - Init

convenience init(parser: ArgumentParser) {
Expand All @@ -37,15 +51,24 @@ final class InstallCommand: Command {
kind: String.self,
optional: false,
usage: "The version of tuist to be installed")
forceArgument = subParser.add(option: "--force",
shortName: "-f",
kind: Bool.self,
usage: "Re-installs the version compiling it from the source", completion: nil)
}

/// Runs the install command.
///
/// - Parameter result: Result obtained from parsing the CLI arguments.
/// - Throws: An error if the installation process fails.
func run(with result: ArgumentParser.Result) throws {
let force = result.get(forceArgument) ?? false
let version = result.get(versionArgument)!
let versions = versionsController.versions().map({ $0.description })
if versions.contains(version) {
printer.print(warning: "Version \(version) already installed, skipping")
return
}
try installer.install(version: version)
try installer.install(version: version, force: force)
}
}
40 changes: 32 additions & 8 deletions Sources/TuistEnvKit/Commands/UpdateCommand.swift
Expand Up @@ -2,39 +2,63 @@ import Foundation
import TuistCore
import Utility

/// Command that updates the version of Tuist in the environment.
final class UpdateCommand: Command {
// MARK: - Command

/// Name of the command.
static var command: String = "update"

/// Description of the command.
static var overview: String = "Installs the latest version if it's not already installed"

// MARK: - Attributes

private let versionsController: VersionsControlling
/// Updater instance that runs the update.
private let updater: Updating

/// Printer instance to output updates during the process.
private let printer: Printing

/// Force argument (-f). When passed, it re-installs the latest version compiling it from the source.
let forceArgument: OptionArgument<Bool>

// MARK: - Init

/// Initializes the update command.
///
/// - Parameter parser: Argument parser where the command should be registered.
convenience init(parser: ArgumentParser) {
self.init(parser: parser,
versionsController: VersionsController(),
updater: Updater(),
printer: Printer())
}

/// Initializes the update command.
///
/// - Parameters:
/// - parser: Argument parser where the command should be registered.
/// - updater: Updater instance that runs the update.
/// - printer: Printer instance to output updates during the process.
init(parser: ArgumentParser,
versionsController: VersionsControlling,
updater: Updating,
printer: Printer) {
parser.add(subparser: UpdateCommand.command, overview: UpdateCommand.overview)
self.versionsController = versionsController
printer: Printing) {
let subParser = parser.add(subparser: UpdateCommand.command, overview: UpdateCommand.overview)
self.printer = printer
self.updater = updater
forceArgument = subParser.add(option: "--force",
shortName: "-f",
kind: Bool.self,
usage: "Re-installs the latest version compiling it from the source", completion: nil)
}

func run(with _: ArgumentParser.Result) throws {
/// Runs the update command.
///
/// - Parameter result: Result obtained from parsing the CLI arguments.
/// - Throws: An error if the update process fails.
func run(with result: ArgumentParser.Result) throws {
let force = result.get(forceArgument) ?? false
printer.print(section: "Checking for updates...")
try updater.update()
try updater.update(force: force)
}
}
35 changes: 28 additions & 7 deletions Sources/TuistEnvKit/Installer/Installer.swift
Expand Up @@ -2,10 +2,21 @@ import Basic
import Foundation
import TuistCore

/// Protocol that defines the interface of an instance that can install versions of Tuist.
protocol Installing: AnyObject {
func install(version: String) throws
/// It installs a version of Tuist in the local environment.
///
/// - Parameters:
/// - version: Version to be installed.
/// - force: When true, it ignores the Swift version and compiles it from the source.
/// - Throws: An error if the installation fails.
func install(version: String, force: Bool) throws
}

/// Error thrown by the installer.
///
/// - versionNotFound: When the specified version cannot be found.
/// - incompatibleSwiftVersion: When the environment Swift version is incompatible with the Swift version Tuist has been compiled with.
enum InstallerError: FatalError, Equatable {
case versionNotFound(String)
case incompatibleSwiftVersion(local: String, expected: String)
Expand Down Expand Up @@ -38,6 +49,7 @@ enum InstallerError: FatalError, Equatable {
}
}

/// Class that manages the installation of Tuist versions.
final class Installer: Installing {
// MARK: - Attributes

Expand Down Expand Up @@ -66,12 +78,20 @@ final class Installer: Installing {

// MARK: - Installing

func install(version: String) throws {
func install(version: String, force: Bool) throws {
let temporaryDirectory = try TemporaryDirectory(removeTreeOnDeinit: true)
try install(version: version, temporaryDirectory: temporaryDirectory)
try install(version: version, temporaryDirectory: temporaryDirectory, force: force)
}

func install(version: String, temporaryDirectory: TemporaryDirectory) throws {
func install(version: String, temporaryDirectory: TemporaryDirectory, force: Bool = false) throws {
// We ignore the Swift version and install from the soruce code
if force {
printer.print("Forcing the installation of \(version) from the source code")
try installFromSource(version: version,
temporaryDirectory: temporaryDirectory)
return
}

try verifySwiftVersion(version: version)

var bundleURL: URL?
Expand Down Expand Up @@ -184,10 +204,11 @@ final class Installer: Installing {
verbose: false,
environment: System.userEnvironment).throwIfError()

// Copying files
if !fileHandler.exists(installationDirectory) {
try system.capture("/bin/mkdir", arguments: installationDirectory.asString, verbose: false, environment: nil).throwIfError()
if fileHandler.exists(installationDirectory) {
try fileHandler.delete(installationDirectory)
}
try fileHandler.createFolder(installationDirectory)

try buildCopier.copy(from: buildDirectory,
to: installationDirectory)

Expand Down
15 changes: 9 additions & 6 deletions Sources/TuistEnvKit/Updater/Updater.swift
Expand Up @@ -2,7 +2,7 @@ import Foundation
import TuistCore

protocol Updating: AnyObject {
func update() throws
func update(force: Bool) throws
}

final class Updater: Updating {
Expand All @@ -27,24 +27,27 @@ final class Updater: Updating {

// MARK: - Internal

func update() throws {
func update(force: Bool) throws {
let releases = try githubClient.releases()

guard let highestRemoteVersion = releases.map({ $0.version }).sorted().last else {
print("No remote versions found")
printer.print("No remote versions found")
return
}

if let highestLocalVersion = versionsController.semverVersions().sorted().last {
if force {
printer.print("Forcing the update of version \(highestRemoteVersion)")
try installer.install(version: highestRemoteVersion.description, force: true)
} else if let highestLocalVersion = versionsController.semverVersions().sorted().last {
if highestRemoteVersion <= highestLocalVersion {
printer.print("There are no updates available")
} else {
printer.print("Installing new version available \(highestRemoteVersion)")
try installer.install(version: highestRemoteVersion.description)
try installer.install(version: highestRemoteVersion.description, force: false)
}
} else {
printer.print("No local versions available. Installing the latest version \(highestRemoteVersion)")
try installer.install(version: highestRemoteVersion.description)
try installer.install(version: highestRemoteVersion.description, force: false)
}
}
}
8 changes: 4 additions & 4 deletions Tests/TuistEnvKitTests/Commands/BundleCommandTests.swift
Expand Up @@ -68,8 +68,8 @@ final class BundleCommandTests: XCTestCase {
let tuistVersionPath = fileHandler.currentPath.appending(component: Constants.versionFileName)
try "3.2.1".write(to: tuistVersionPath.url, atomically: true, encoding: .utf8)

installer.installStub = { versionToInstall in
let versionPath = self.versionsController.path(version: versionToInstall)
installer.installStub = { version, _ in
let versionPath = self.versionsController.path(version: version)
try self.fileHandler.createFolder(versionPath)
try Data().write(to: versionPath.appending(component: "test").url)
}
Expand Down Expand Up @@ -102,8 +102,8 @@ final class BundleCommandTests: XCTestCase {

try "3.2.1".write(to: tuistVersionPath.url, atomically: true, encoding: .utf8)

installer.installStub = { versionToInstall in
let versionPath = self.versionsController.path(version: versionToInstall)
installer.installStub = { version, _ in
let versionPath = self.versionsController.path(version: version)
try self.fileHandler.createFolder(versionPath)
try Data().write(to: versionPath.appending(component: "test").url)
}
Expand Down
13 changes: 7 additions & 6 deletions Tests/TuistEnvKitTests/Commands/CommandRunnerTests.swift
Expand Up @@ -84,8 +84,8 @@ final class CommandRunnerTests: XCTestCase {

versionResolver.resolveStub = { _ in ResolvedVersion.versionFile(self.fileHandler.currentPath, "3.2.1") }

var installedVersion: String?
installer.installStub = { installedVersion = $0 }
var installArgs: [(version: String, force: Bool)] = []
installer.installStub = { version, force in installArgs.append((version: version, force: force)) }

system.stub(args: [binaryPath.asString, "--help"],
stderror: nil,
Expand All @@ -97,7 +97,8 @@ final class CommandRunnerTests: XCTestCase {
XCTAssertEqual(printer.printArgs.count, 2)
XCTAssertEqual(printer.printArgs.first, "Using version 3.2.1 defined at \(fileHandler.currentPath.asString)")
XCTAssertEqual(printer.printArgs.last, "Version 3.2.1 not found locally. Installing...")
XCTAssertEqual(installedVersion, "3.2.1")
XCTAssertEqual(installArgs.count, 1)
XCTAssertEqual(installArgs.first?.version, "3.2.1")
}

func test_when_version_file_and_install_fails() throws {
Expand All @@ -106,7 +107,7 @@ final class CommandRunnerTests: XCTestCase {
versionResolver.resolveStub = { _ in ResolvedVersion.versionFile(self.fileHandler.currentPath, "3.2.1") }

let error = NSError.test()
installer.installStub = { _ in throw error }
installer.installStub = { _, _ in throw error }

XCTAssertThrowsError(try subject.run()) {
XCTAssertEqual($0 as NSError, error)
Expand Down Expand Up @@ -159,7 +160,7 @@ final class CommandRunnerTests: XCTestCase {
versionResolver.resolveStub = { _ in ResolvedVersion.undefined }

versionsController.semverVersionsStub = []
updater.updateStub = {
updater.updateStub = { _ in
self.versionsController.semverVersionsStub = [Version(string: "3.2.1")!]
}

Expand All @@ -182,7 +183,7 @@ final class CommandRunnerTests: XCTestCase {

versionsController.semverVersionsStub = []
let error = NSError.test()
updater.updateStub = {
updater.updateStub = { _ in
throw error
}

Expand Down
23 changes: 20 additions & 3 deletions Tests/TuistEnvKitTests/Commands/InstallCommandTests.swift
Expand Up @@ -54,11 +54,28 @@ final class InstallCommandTests: XCTestCase {

versionsController.versionsStub = []

var installedVersion: String?
installer.installStub = { installedVersion = $0 }
var installArgs: [(version: String, force: Bool)] = []
installer.installStub = { version, force in installArgs.append((version: version, force: force)) }

try subject.run(with: result)

XCTAssertEqual(installedVersion, "3.2.1")
XCTAssertEqual(installArgs.count, 1)
XCTAssertEqual(installArgs.first?.version, "3.2.1")
XCTAssertEqual(installArgs.first?.force, false)
}

func test_run_when_force() throws {
let result = try parser.parse(["install", "3.2.1", "-f"])

versionsController.versionsStub = []

var installArgs: [(version: String, force: Bool)] = []
installer.installStub = { version, force in installArgs.append((version: version, force: force)) }

try subject.run(with: result)

XCTAssertEqual(installArgs.count, 1)
XCTAssertEqual(installArgs.first?.version, "3.2.1")
XCTAssertEqual(installArgs.first?.force, true)
}
}

0 comments on commit 30ff33c

Please sign in to comment.