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
|
||||
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 {
|
||||
try System.shared.run("/usr/bin/env", "git", "-C", temporaryDirectory.path.pathString, "checkout", version)
|
||||
} catch let error as TuistSupport.SystemError {
|
||||
if error.description.contains("did not match any file(s) known to git") {
|
||||
let gitCheckoutResult = System.shared.observable(["/usr/bin/env", "git", "-C", temporaryDirectory.path.pathString, "checkout", version])
|
||||
.mapToString()
|
||||
.toBlocking()
|
||||
.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)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
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",
|
||||
"--product", "tuist",
|
||||
"--package-path", temporaryDirectory.path.pathString,
|
||||
"--configuration", "release")
|
||||
_ = try System.shared.observable([swiftPath, "build",
|
||||
"--product", "tuist",
|
||||
"--package-path", temporaryDirectory.path.pathString,
|
||||
"--configuration", "release"])
|
||||
.mapToString()
|
||||
.printStandardError()
|
||||
.toBlocking()
|
||||
.last()
|
||||
|
||||
try System.shared.run(swiftPath, "build",
|
||||
"--product", "ProjectDescription",
|
||||
"--package-path", temporaryDirectory.path.pathString,
|
||||
"--configuration", "release",
|
||||
"-Xswiftc", "-enable-library-evolution",
|
||||
"-Xswiftc", "-emit-module-interface",
|
||||
"-Xswiftc", "-emit-module-interface-path",
|
||||
"-Xswiftc", temporaryDirectory.path.appending(RelativePath(".build/release/ProjectDescription.swiftinterface")).pathString) // swiftlint:disable:this line_length
|
||||
_ = try System.shared.observable([swiftPath, "build",
|
||||
"--product", "ProjectDescription",
|
||||
"--package-path", temporaryDirectory.path.pathString,
|
||||
"--configuration", "release",
|
||||
"-Xswiftc", "-enable-library-evolution",
|
||||
"-Xswiftc", "-emit-module-interface",
|
||||
"-Xswiftc", "-emit-module-interface-path",
|
||||
"-Xswiftc", temporaryDirectory.path.appending(RelativePath(".build/release/ProjectDescription.swiftinterface")).pathString]) // swiftlint:disable:this line_length
|
||||
.mapToString()
|
||||
.printStandardError()
|
||||
.toBlocking()
|
||||
.last()
|
||||
|
||||
if FileHandler.shared.exists(installationDirectory) {
|
||||
try FileHandler.shared.delete(installationDirectory)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Foundation
|
||||
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
|
||||
/// to a 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.
|
||||
func collectAndMergeOutput() -> Observable<String> {
|
||||
reduce("") { (collected, event) -> String in
|
||||
|
|
|
@ -153,25 +153,25 @@ extension ProcessResult {
|
|||
func throwIfErrored() throws {
|
||||
switch exitStatus {
|
||||
case let .signalled(code):
|
||||
throw TuistSupport.SystemError.signalled(code: code)
|
||||
throw TuistSupport.SystemError.signalled(command: arguments.first!, code: code)
|
||||
case let .terminated(code):
|
||||
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 {
|
||||
case terminated(code: Int32, error: String)
|
||||
case signalled(code: Int32)
|
||||
public enum SystemError: FatalError, Equatable {
|
||||
case terminated(command: String, code: Int32)
|
||||
case signalled(command: String, code: Int32)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .signalled(code):
|
||||
return "Command interrupted with a signal \(code)"
|
||||
case let .terminated(code, error):
|
||||
return "Command exited with error code \(code) and error: \(error)"
|
||||
case let .signalled(command, code):
|
||||
return "The '\(command)' was interrupted with a signal \(code)"
|
||||
case let .terminated(command, code):
|
||||
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>> {
|
||||
Observable.create { (observer) -> Disposable in
|
||||
var errorData: [UInt8] = []
|
||||
let process = Process(arguments: arguments,
|
||||
environment: environment,
|
||||
outputRedirection: .stream(stdout: { bytes in
|
||||
observer.onNext(.standardOutput(Data(bytes)))
|
||||
}, stderr: { bytes in
|
||||
errorData.append(contentsOf: bytes)
|
||||
observer.onNext(.standardError(Data(bytes)))
|
||||
}),
|
||||
verbose: verbose,
|
||||
startNewProcessGroup: false)
|
||||
do {
|
||||
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()
|
||||
} catch {
|
||||
observer.onError(error)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Foundation
|
||||
|
||||
struct SystemCollectedOutput {
|
||||
public struct SystemCollectedOutput {
|
||||
/// Standard output.
|
||||
var standardOutput: String = ""
|
||||
public var standardOutput: String = ""
|
||||
|
||||
/// Standard error.
|
||||
var standardError: String = ""
|
||||
public var standardError: String = ""
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// https://github.com/rhodgkins/SwiftHTTPStatusCodes
|
||||
//
|
||||
// HTTPStatusCodes+Extensions.swift
|
||||
// HTTPStatusCodes
|
|
@ -33,10 +33,10 @@ public final class MockSystem: Systeming {
|
|||
public func run(_ arguments: [String]) throws {
|
||||
let command = arguments.joined(separator: " ")
|
||||
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 {
|
||||
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 {
|
||||
let command = arguments.joined(separator: " ")
|
||||
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 {
|
||||
throw TuistSupport.SystemError.terminated(code: 1, error: stub.stderror ?? "")
|
||||
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1)
|
||||
}
|
||||
return stub.stdout ?? ""
|
||||
}
|
||||
|
@ -91,13 +91,13 @@ public final class MockSystem: Systeming {
|
|||
) throws {
|
||||
let command = arguments.joined(separator: " ")
|
||||
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 let error = stub.stderror {
|
||||
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
|
||||
let command = arguments.joined(separator: " ")
|
||||
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()
|
||||
}
|
||||
guard stub.exitstatus == 0 else {
|
||||
if let error = stub.stderror {
|
||||
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()
|
||||
}
|
||||
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 {
|
||||
let command = arguments.joined(separator: " ")
|
||||
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 {
|
||||
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)
|
||||
releaseRequest.httpMethod = "HEAD"
|
||||
|
||||
let buildURL = GoogleCloudStorageClient.url(buildsPath: "tuist-\(version).zip")
|
||||
let buildURL = GoogleCloudStorageClient.url(buildsPath: "\(version).zip")
|
||||
var buildRequest = URLRequest(url: buildURL)
|
||||
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 {
|
||||
try sandbox("DEVELOPER_DIR", value: "/Applications/Xcode/Xcode-10.2.1.app/Contents/Developer/") {
|
||||
let result = try subject.capture("env")
|
||||
|
|
Loading…
Reference in New Issue