Update the installer to print the errors when the commands fail
This commit is contained in:
parent
1c5cb693bc
commit
062ebb3ae9
|
@ -123,33 +123,56 @@ final class Installer: Installing {
|
||||||
|
|
||||||
// Cloning and building
|
// Cloning and building
|
||||||
Printer.shared.print("Pulling source code")
|
Printer.shared.print("Pulling source code")
|
||||||
try System.shared.run("/usr/bin/env", "git", "clone", Constants.gitRepositoryURL, temporaryDirectory.path.pathString)
|
_ = try System.shared.observable(["/usr/bin/env", "git", "clone", Constants.gitRepositoryURL, temporaryDirectory.path.pathString])
|
||||||
|
.mapToString()
|
||||||
|
.printStandardError()
|
||||||
|
.toBlocking()
|
||||||
|
.last()
|
||||||
|
|
||||||
do {
|
let gitCheckoutResult = System.shared.observable(["/usr/bin/env", "git", "-C", temporaryDirectory.path.pathString, "checkout", version])
|
||||||
try System.shared.run("/usr/bin/env", "git", "-C", temporaryDirectory.path.pathString, "checkout", version)
|
.mapToString()
|
||||||
} catch let error as TuistSupport.SystemError {
|
.toBlocking()
|
||||||
if error.description.contains("did not match any file(s) known to git") {
|
.materialize()
|
||||||
|
|
||||||
|
if case let .failed(elements, error) = gitCheckoutResult {
|
||||||
|
if elements.map({ $0.value }).first(where: { $0.contains("did not match any file(s) known to git") }) != nil {
|
||||||
throw InstallerError.versionNotFound(version)
|
throw InstallerError.versionNotFound(version)
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Printer.shared.print("Building using Swift (it might take a while)")
|
Printer.shared.print("Building using Swift (it might take a while)")
|
||||||
let swiftPath = try System.shared.capture("/usr/bin/xcrun", "-f", "swift").spm_chuzzle()!
|
let swiftPath = try System.shared
|
||||||
|
.observable(["/usr/bin/xcrun", "-f", "swift"])
|
||||||
|
.mapToString()
|
||||||
|
.collectOutput()
|
||||||
|
.toBlocking()
|
||||||
|
.last()!
|
||||||
|
.standardOutput
|
||||||
|
.spm_chuzzle()!
|
||||||
|
|
||||||
try System.shared.run(swiftPath, "build",
|
_ = try System.shared.observable([swiftPath, "build",
|
||||||
"--product", "tuist",
|
"--product", "tuist",
|
||||||
"--package-path", temporaryDirectory.path.pathString,
|
"--package-path", temporaryDirectory.path.pathString,
|
||||||
"--configuration", "release")
|
"--configuration", "release"])
|
||||||
|
.mapToString()
|
||||||
|
.printStandardError()
|
||||||
|
.toBlocking()
|
||||||
|
.last()
|
||||||
|
|
||||||
try System.shared.run(swiftPath, "build",
|
_ = try System.shared.observable([swiftPath, "build",
|
||||||
"--product", "ProjectDescription",
|
"--product", "ProjectDescription",
|
||||||
"--package-path", temporaryDirectory.path.pathString,
|
"--package-path", temporaryDirectory.path.pathString,
|
||||||
"--configuration", "release",
|
"--configuration", "release",
|
||||||
"-Xswiftc", "-enable-library-evolution",
|
"-Xswiftc", "-enable-library-evolution",
|
||||||
"-Xswiftc", "-emit-module-interface",
|
"-Xswiftc", "-emit-module-interface",
|
||||||
"-Xswiftc", "-emit-module-interface-path",
|
"-Xswiftc", "-emit-module-interface-path",
|
||||||
"-Xswiftc", temporaryDirectory.path.appending(RelativePath(".build/release/ProjectDescription.swiftinterface")).pathString) // swiftlint:disable:this line_length
|
"-Xswiftc", temporaryDirectory.path.appending(RelativePath(".build/release/ProjectDescription.swiftinterface")).pathString]) // swiftlint:disable:this line_length
|
||||||
|
.mapToString()
|
||||||
|
.printStandardError()
|
||||||
|
.toBlocking()
|
||||||
|
.last()
|
||||||
|
|
||||||
if FileHandler.shared.exists(installationDirectory) {
|
if FileHandler.shared.exists(installationDirectory) {
|
||||||
try FileHandler.shared.delete(installationDirectory)
|
try FileHandler.shared.delete(installationDirectory)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import RxSwift
|
import RxSwift
|
||||||
|
|
||||||
extension Observable where Element == SystemEvent<Data> {
|
public extension Observable where Element == SystemEvent<Data> {
|
||||||
/// Returns another observable where the standard output and error data are mapped
|
/// Returns another observable where the standard output and error data are mapped
|
||||||
/// to a string.
|
/// to a string.
|
||||||
func mapToString() -> Observable<SystemEvent<String>> {
|
func mapToString() -> Observable<SystemEvent<String>> {
|
||||||
|
@ -9,7 +9,19 @@ extension Observable where Element == SystemEvent<Data> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Observable where Element == SystemEvent<String> {
|
public extension Observable where Element == SystemEvent<String> {
|
||||||
|
/// Returns an observable that prints the standard error.
|
||||||
|
func printStandardError() -> Observable<SystemEvent<String>> {
|
||||||
|
self.do(onNext: { event in
|
||||||
|
switch event {
|
||||||
|
case let .standardError(error):
|
||||||
|
Printer.shared.print(errorMessage: "\(error)")
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns an observable that collects and merges the standard output and error into a single string.
|
/// Returns an observable that collects and merges the standard output and error into a single string.
|
||||||
func collectAndMergeOutput() -> Observable<String> {
|
func collectAndMergeOutput() -> Observable<String> {
|
||||||
reduce("") { (collected, event) -> String in
|
reduce("") { (collected, event) -> String in
|
||||||
|
|
|
@ -153,25 +153,25 @@ extension ProcessResult {
|
||||||
func throwIfErrored() throws {
|
func throwIfErrored() throws {
|
||||||
switch exitStatus {
|
switch exitStatus {
|
||||||
case let .signalled(code):
|
case let .signalled(code):
|
||||||
throw TuistSupport.SystemError.signalled(code: code)
|
throw TuistSupport.SystemError.signalled(command: arguments.first!, code: code)
|
||||||
case let .terminated(code):
|
case let .terminated(code):
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
throw TuistSupport.SystemError.terminated(code: code, error: try utf8stderrOutput())
|
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SystemError: FatalError {
|
public enum SystemError: FatalError, Equatable {
|
||||||
case terminated(code: Int32, error: String)
|
case terminated(command: String, code: Int32)
|
||||||
case signalled(code: Int32)
|
case signalled(command: String, code: Int32)
|
||||||
|
|
||||||
public var description: String {
|
public var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case let .signalled(code):
|
case let .signalled(command, code):
|
||||||
return "Command interrupted with a signal \(code)"
|
return "The '\(command)' was interrupted with a signal \(code)"
|
||||||
case let .terminated(code, error):
|
case let .terminated(command, code):
|
||||||
return "Command exited with error code \(code) and error: \(error)"
|
return "The '\(command)' command exited with error code \(code)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,18 +386,25 @@ public final class System: Systeming {
|
||||||
|
|
||||||
public func observable(_ arguments: [String], verbose: Bool, environment: [String: String]) -> Observable<SystemEvent<Data>> {
|
public func observable(_ arguments: [String], verbose: Bool, environment: [String: String]) -> Observable<SystemEvent<Data>> {
|
||||||
Observable.create { (observer) -> Disposable in
|
Observable.create { (observer) -> Disposable in
|
||||||
|
var errorData: [UInt8] = []
|
||||||
let process = Process(arguments: arguments,
|
let process = Process(arguments: arguments,
|
||||||
environment: environment,
|
environment: environment,
|
||||||
outputRedirection: .stream(stdout: { bytes in
|
outputRedirection: .stream(stdout: { bytes in
|
||||||
observer.onNext(.standardOutput(Data(bytes)))
|
observer.onNext(.standardOutput(Data(bytes)))
|
||||||
}, stderr: { bytes in
|
}, stderr: { bytes in
|
||||||
|
errorData.append(contentsOf: bytes)
|
||||||
observer.onNext(.standardError(Data(bytes)))
|
observer.onNext(.standardError(Data(bytes)))
|
||||||
}),
|
}),
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
startNewProcessGroup: false)
|
startNewProcessGroup: false)
|
||||||
do {
|
do {
|
||||||
try process.launch()
|
try process.launch()
|
||||||
try process.waitUntilExit().throwIfErrored()
|
var result = try process.waitUntilExit()
|
||||||
|
result = ProcessResult(arguments: result.arguments,
|
||||||
|
exitStatus: result.exitStatus,
|
||||||
|
output: result.output,
|
||||||
|
stderrOutput: result.stderrOutput.map { _ in errorData })
|
||||||
|
try result.throwIfErrored()
|
||||||
observer.onCompleted()
|
observer.onCompleted()
|
||||||
} catch {
|
} catch {
|
||||||
observer.onError(error)
|
observer.onError(error)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct SystemCollectedOutput {
|
public struct SystemCollectedOutput {
|
||||||
/// Standard output.
|
/// Standard output.
|
||||||
var standardOutput: String = ""
|
public var standardOutput: String = ""
|
||||||
|
|
||||||
/// Standard error.
|
/// Standard error.
|
||||||
var standardError: String = ""
|
public var standardError: String = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// https://github.com/rhodgkins/SwiftHTTPStatusCodes
|
||||||
//
|
//
|
||||||
// HTTPStatusCodes+Extensions.swift
|
// HTTPStatusCodes+Extensions.swift
|
||||||
// HTTPStatusCodes
|
// HTTPStatusCodes
|
|
@ -33,10 +33,10 @@ public final class MockSystem: Systeming {
|
||||||
public func run(_ arguments: [String]) throws {
|
public func run(_ arguments: [String]) throws {
|
||||||
let command = arguments.joined(separator: " ")
|
let command = arguments.joined(separator: " ")
|
||||||
guard let stub = stubs[command] else {
|
guard let stub = stubs[command] else {
|
||||||
throw TuistSupport.SystemError.terminated(code: 1, error: "command '\(command)' not stubbed")
|
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1)
|
||||||
}
|
}
|
||||||
if stub.exitstatus != 0 {
|
if stub.exitstatus != 0 {
|
||||||
throw TuistSupport.SystemError.terminated(code: 1, error: stub.stderror ?? "")
|
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,10 +59,10 @@ public final class MockSystem: Systeming {
|
||||||
public func capture(_ arguments: [String], verbose _: Bool, environment _: [String: String]) throws -> String {
|
public func capture(_ arguments: [String], verbose _: Bool, environment _: [String: String]) throws -> String {
|
||||||
let command = arguments.joined(separator: " ")
|
let command = arguments.joined(separator: " ")
|
||||||
guard let stub = stubs[command] else {
|
guard let stub = stubs[command] else {
|
||||||
throw TuistSupport.SystemError.terminated(code: 1, error: "command '\(command)' not stubbed")
|
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1)
|
||||||
}
|
}
|
||||||
if stub.exitstatus != 0 {
|
if stub.exitstatus != 0 {
|
||||||
throw TuistSupport.SystemError.terminated(code: 1, error: stub.stderror ?? "")
|
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1)
|
||||||
}
|
}
|
||||||
return stub.stdout ?? ""
|
return stub.stdout ?? ""
|
||||||
}
|
}
|
||||||
|
@ -91,13 +91,13 @@ public final class MockSystem: Systeming {
|
||||||
) throws {
|
) throws {
|
||||||
let command = arguments.joined(separator: " ")
|
let command = arguments.joined(separator: " ")
|
||||||
guard let stub = stubs[command] else {
|
guard let stub = stubs[command] else {
|
||||||
throw TuistSupport.SystemError.terminated(code: 1, error: "command '\(command)' not stubbed")
|
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1)
|
||||||
}
|
}
|
||||||
if stub.exitstatus != 0 {
|
if stub.exitstatus != 0 {
|
||||||
if let error = stub.stderror {
|
if let error = stub.stderror {
|
||||||
redirection.outputClosures?.stderrClosure([UInt8](error.data(using: .utf8)!))
|
redirection.outputClosures?.stderrClosure([UInt8](error.data(using: .utf8)!))
|
||||||
}
|
}
|
||||||
throw TuistSupport.SystemError.terminated(code: 1, error: stub.stderror ?? "")
|
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,14 +113,14 @@ public final class MockSystem: Systeming {
|
||||||
Observable.create { (observer) -> Disposable in
|
Observable.create { (observer) -> Disposable in
|
||||||
let command = arguments.joined(separator: " ")
|
let command = arguments.joined(separator: " ")
|
||||||
guard let stub = self.stubs[command] else {
|
guard let stub = self.stubs[command] else {
|
||||||
observer.onError(TuistSupport.SystemError.terminated(code: 1, error: "command '\(command)' not stubbed"))
|
observer.onError(TuistSupport.SystemError.terminated(command: arguments.first!, code: 1))
|
||||||
return Disposables.create()
|
return Disposables.create()
|
||||||
}
|
}
|
||||||
guard stub.exitstatus == 0 else {
|
guard stub.exitstatus == 0 else {
|
||||||
if let error = stub.stderror {
|
if let error = stub.stderror {
|
||||||
observer.onNext(.standardError(error.data(using: .utf8)!))
|
observer.onNext(.standardError(error.data(using: .utf8)!))
|
||||||
}
|
}
|
||||||
observer.onError(TuistSupport.SystemError.terminated(code: 1, error: stub.stderror ?? ""))
|
observer.onError(TuistSupport.SystemError.terminated(command: arguments.first!, code: 1))
|
||||||
return Disposables.create()
|
return Disposables.create()
|
||||||
}
|
}
|
||||||
if let stdout = stub.stdout {
|
if let stdout = stub.stdout {
|
||||||
|
@ -138,10 +138,10 @@ public final class MockSystem: Systeming {
|
||||||
public func async(_ arguments: [String], verbose _: Bool, environment _: [String: String]) throws {
|
public func async(_ arguments: [String], verbose _: Bool, environment _: [String: String]) throws {
|
||||||
let command = arguments.joined(separator: " ")
|
let command = arguments.joined(separator: " ")
|
||||||
guard let stub = stubs[command] else {
|
guard let stub = stubs[command] else {
|
||||||
throw TuistSupport.SystemError.terminated(code: 1, error: "command '\(command)' not stubbed")
|
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1)
|
||||||
}
|
}
|
||||||
if stub.exitstatus != 0 {
|
if stub.exitstatus != 0 {
|
||||||
throw TuistSupport.SystemError.terminated(code: 1, error: stub.stderror ?? "")
|
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,7 +117,7 @@ final class GoogleCloudStorageClientTests: TuistUnitTestCase {
|
||||||
var releaseRequest = URLRequest(url: releaseURL)
|
var releaseRequest = URLRequest(url: releaseURL)
|
||||||
releaseRequest.httpMethod = "HEAD"
|
releaseRequest.httpMethod = "HEAD"
|
||||||
|
|
||||||
let buildURL = GoogleCloudStorageClient.url(buildsPath: "tuist-\(version).zip")
|
let buildURL = GoogleCloudStorageClient.url(buildsPath: "\(version).zip")
|
||||||
var buildRequest = URLRequest(url: buildURL)
|
var buildRequest = URLRequest(url: buildURL)
|
||||||
buildRequest.httpMethod = "HEAD"
|
buildRequest.httpMethod = "HEAD"
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,23 @@ final class SystemIntegrationTests: TuistTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test_observable_when_it_errors() throws {
|
||||||
|
// Given
|
||||||
|
let observable = subject.observable(["/usr/bin/xcrun", "invalid"]).mapToString()
|
||||||
|
|
||||||
|
// When
|
||||||
|
let result = observable.toBlocking().materialize()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
switch result {
|
||||||
|
case .completed:
|
||||||
|
XCTFail("expected command to fail but it did not")
|
||||||
|
case let .failed(elements, error):
|
||||||
|
XCTAssertTrue(elements.first(where: { $0.value.contains("errno=No such file or directory") }) != nil)
|
||||||
|
XCTAssertEqual(error as? TuistSupport.SystemError, TuistSupport.SystemError.terminated(command: "/usr/bin/xcrun", code: 72))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func test_pass_DEVELOPER_DIR() throws {
|
func test_pass_DEVELOPER_DIR() throws {
|
||||||
try sandbox("DEVELOPER_DIR", value: "/Applications/Xcode/Xcode-10.2.1.app/Contents/Developer/") {
|
try sandbox("DEVELOPER_DIR", value: "/Applications/Xcode/Xcode-10.2.1.app/Contents/Developer/") {
|
||||||
let result = try subject.capture("env")
|
let result = try subject.capture("env")
|
||||||
|
|
Loading…
Reference in New Issue