Replace RxSwift for XcodeBuildController and System (#4029)
This commit is contained in:
parent
d06735adcb
commit
0ecea33054
|
@ -127,15 +127,6 @@
|
|||
"version": "2.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "RxSwift",
|
||||
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b4307ba0b6425c0ba4178e138799946c3da594f8",
|
||||
"version": "6.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "ShellOut",
|
||||
"repositoryURL": "https://github.com/JohnSundell/ShellOut.git",
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
import PackageDescription
|
||||
|
||||
let signalsDependency: Target.Dependency = .byName(name: "Signals")
|
||||
let rxSwiftDependency: Target.Dependency = .product(name: "RxSwift", package: "RxSwift")
|
||||
let rxBlockingDependency: Target.Dependency = .product(name: "RxBlocking", package: "RxSwift")
|
||||
let rxTestDependency: Target.Dependency = .product(name: "RxTest", package: "RxSwift")
|
||||
let swiftToolsSupportDependency: Target.Dependency = .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core")
|
||||
let loggingDependency: Target.Dependency = .product(name: "Logging", package: "swift-log")
|
||||
let argumentParserDependency: Target.Dependency = .product(name: "ArgumentParser", package: "swift-argument-parser")
|
||||
|
@ -52,7 +49,6 @@ let package = Package(
|
|||
dependencies: [
|
||||
.package(url: "https://github.com/tuist/XcodeProj.git", .upToNextMajor(from: "8.7.1")),
|
||||
.package(name: "Signals", url: "https://github.com/tuist/BlueSignals.git", .upToNextMajor(from: "1.0.21")),
|
||||
.package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.5.0")),
|
||||
.package(url: "https://github.com/rnine/Checksum.git", .upToNextMajor(from: "1.0.2")),
|
||||
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")),
|
||||
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMajor(from: "1.4.1")),
|
||||
|
@ -87,7 +83,6 @@ let package = Package(
|
|||
.target(
|
||||
name: "TuistCore",
|
||||
dependencies: [
|
||||
rxSwiftDependency,
|
||||
swiftToolsSupportDependency,
|
||||
"ProjectDescription",
|
||||
"TuistSupport",
|
||||
|
@ -121,7 +116,6 @@ let package = Package(
|
|||
"ProjectDescription",
|
||||
"ProjectAutomation",
|
||||
signalsDependency,
|
||||
rxSwiftDependency,
|
||||
"TuistLoader",
|
||||
"TuistScaffold",
|
||||
"TuistSigning",
|
||||
|
@ -190,7 +184,6 @@ let package = Package(
|
|||
argumentParserDependency,
|
||||
swiftToolsSupportDependency,
|
||||
"TuistSupport",
|
||||
rxSwiftDependency,
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
|
@ -226,7 +219,6 @@ let package = Package(
|
|||
dependencies: [
|
||||
combineExtDependency,
|
||||
swiftToolsSupportDependency,
|
||||
rxSwiftDependency,
|
||||
loggingDependency,
|
||||
"KeychainAccess",
|
||||
swifterDependency,
|
||||
|
@ -307,7 +299,6 @@ let package = Package(
|
|||
"TuistCore",
|
||||
"TuistGraph",
|
||||
"TuistSupport",
|
||||
rxSwiftDependency,
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
|
@ -326,8 +317,6 @@ let package = Package(
|
|||
"TuistCache",
|
||||
swiftToolsSupportDependency,
|
||||
"TuistCore",
|
||||
rxTestDependency,
|
||||
rxSwiftDependency,
|
||||
"TuistSupportTesting",
|
||||
"TuistGraphTesting",
|
||||
"TuistCoreTesting",
|
||||
|
@ -341,7 +330,6 @@ let package = Package(
|
|||
"TuistCore",
|
||||
"TuistGraph",
|
||||
"TuistSupport",
|
||||
rxSwiftDependency,
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
|
@ -359,8 +347,6 @@ let package = Package(
|
|||
"TuistCloud",
|
||||
swiftToolsSupportDependency,
|
||||
"TuistCore",
|
||||
rxTestDependency,
|
||||
rxSwiftDependency,
|
||||
"TuistGraphTesting",
|
||||
]
|
||||
),
|
||||
|
@ -540,7 +526,6 @@ let package = Package(
|
|||
"TuistGraph",
|
||||
"TuistSupport",
|
||||
signalsDependency,
|
||||
rxSwiftDependency,
|
||||
]
|
||||
),
|
||||
.target(
|
||||
|
|
|
@ -28,7 +28,6 @@ func modulesTargetsAndSchemes() -> [(targets: [Target], scheme: Scheme)] {
|
|||
.external(name: "Logging"),
|
||||
.external(name: "PathKit"),
|
||||
.external(name: "Queuer"),
|
||||
.external(name: "RxSwift"),
|
||||
.external(name: "Signals"),
|
||||
.external(name: "Stencil"),
|
||||
.external(name: "StencilSwiftKit"),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
import RxSwift
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
@ -144,7 +144,6 @@ public final class XcodeBuildController: XcodeBuildControlling {
|
|||
return run(command: command, isVerbose: environment.isVerbose)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
public func showBuildSettings(
|
||||
_ target: XcodeBuildTarget,
|
||||
scheme: String,
|
||||
|
@ -161,118 +160,101 @@ public final class XcodeBuildController: XcodeBuildControlling {
|
|||
// Target
|
||||
command.append(contentsOf: target.xcodebuildArguments)
|
||||
|
||||
return try await System.shared.observable(command)
|
||||
let values = System.shared.publisher(command)
|
||||
.mapToString()
|
||||
.collectAndMergeOutput()
|
||||
// xcodebuild has a bug where xcodebuild -showBuildSettings
|
||||
// can sometimes hang indefinitely on projects that don't
|
||||
// share any schemes, so automatically bail out if it looks
|
||||
// like that's happening.
|
||||
.timeout(DispatchTimeInterval.seconds(20), scheduler: ConcurrentDispatchQueueScheduler(queue: .global()))
|
||||
.timeout(.seconds(20), scheduler: DispatchQueue.global())
|
||||
.retry(5)
|
||||
.flatMap { string -> Observable<XcodeBuildSettings> in
|
||||
Observable.create { observer -> Disposable in
|
||||
var currentSettings: [String: String] = [:]
|
||||
var currentTarget: String?
|
||||
.values
|
||||
var buildSettingsByTargetName = [String: XcodeBuildSettings]()
|
||||
for try await string in values {
|
||||
var currentSettings: [String: String] = [:]
|
||||
var currentTarget: String?
|
||||
|
||||
let flushTarget = { () -> Void in
|
||||
if let currentTarget = currentTarget {
|
||||
let buildSettings = XcodeBuildSettings(
|
||||
currentSettings,
|
||||
target: currentTarget,
|
||||
configuration: configuration
|
||||
)
|
||||
observer.onNext(buildSettings)
|
||||
}
|
||||
let flushTarget = { () -> Void in
|
||||
if let currentTarget = currentTarget {
|
||||
let buildSettings = XcodeBuildSettings(
|
||||
currentSettings,
|
||||
target: currentTarget,
|
||||
configuration: configuration
|
||||
)
|
||||
buildSettingsByTargetName[buildSettings.target] = buildSettings
|
||||
}
|
||||
|
||||
currentTarget = nil
|
||||
currentSettings = [:]
|
||||
}
|
||||
currentTarget = nil
|
||||
currentSettings = [:]
|
||||
}
|
||||
|
||||
string.enumerateLines { line, _ in
|
||||
if let result = XcodeBuildController.targetSettingsRegex.firstMatch(
|
||||
in: line,
|
||||
range: NSRange(line.startIndex..., in: line)
|
||||
) {
|
||||
let targetRange = Range(result.range(at: 1), in: line)!
|
||||
|
||||
flushTarget()
|
||||
currentTarget = String(line[targetRange])
|
||||
return
|
||||
}
|
||||
|
||||
let trimSet = CharacterSet.whitespacesAndNewlines
|
||||
let components = line
|
||||
.split(maxSplits: 1) { $0 == "=" }
|
||||
.map { $0.trimmingCharacters(in: trimSet) }
|
||||
|
||||
if components.count == 2 {
|
||||
currentSettings[components[0]] = components[1]
|
||||
}
|
||||
}
|
||||
string.enumerateLines { line, _ in
|
||||
if let result = XcodeBuildController.targetSettingsRegex.firstMatch(
|
||||
in: line,
|
||||
range: NSRange(line.startIndex..., in: line)
|
||||
) {
|
||||
let targetRange = Range(result.range(at: 1), in: line)!
|
||||
|
||||
flushTarget()
|
||||
observer.onCompleted()
|
||||
return Disposables.create()
|
||||
currentTarget = String(line[targetRange])
|
||||
return
|
||||
}
|
||||
|
||||
let trimSet = CharacterSet.whitespacesAndNewlines
|
||||
let components = line
|
||||
.split(maxSplits: 1) { $0 == "=" }
|
||||
.map { $0.trimmingCharacters(in: trimSet) }
|
||||
|
||||
if components.count == 2 {
|
||||
currentSettings[components[0]] = components[1]
|
||||
}
|
||||
}
|
||||
.reduce([String: XcodeBuildSettings](), accumulator: { acc, buildSettings -> [String: XcodeBuildSettings] in
|
||||
var acc = acc
|
||||
acc[buildSettings.target] = buildSettings
|
||||
return acc
|
||||
})
|
||||
.asSingle()
|
||||
.value
|
||||
flushTarget()
|
||||
}
|
||||
return buildSettingsByTargetName
|
||||
}
|
||||
|
||||
fileprivate func run(command: [String], isVerbose: Bool) -> AsyncThrowingStream<SystemEvent<XcodeBuildOutput>, Error> {
|
||||
if isVerbose {
|
||||
return run(command: command)
|
||||
return System.shared.publisher(command).values
|
||||
.mapAsXcodeBuildOutput()
|
||||
} else {
|
||||
// swiftlint:disable:next force_try
|
||||
return run(command: command, pipedToArguments: try! formatter.buildArguments())
|
||||
return System.shared.publisher(
|
||||
command,
|
||||
// swiftlint:disable:next force_try
|
||||
pipeTo: try! formatter.buildArguments()
|
||||
).values.mapAsXcodeBuildOutput()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func run(command: [String]) -> AsyncThrowingStream<SystemEvent<XcodeBuildOutput>, Error> {
|
||||
System.shared.observable(command)
|
||||
.flatMap { event -> Observable<SystemEvent<XcodeBuildOutput>> in
|
||||
switch event {
|
||||
case let .standardError(errorData):
|
||||
guard let line = String(data: errorData, encoding: .utf8) else { return Observable.empty() }
|
||||
let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
|
||||
SystemEvent.standardError(XcodeBuildOutput(raw: "\(String(line))\n"))
|
||||
}
|
||||
return Observable.from(output)
|
||||
case let .standardOutput(outputData):
|
||||
guard let line = String(data: outputData, encoding: .utf8) else { return Observable.empty() }
|
||||
let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
|
||||
SystemEvent.standardOutput(XcodeBuildOutput(raw: "\(String(line))\n"))
|
||||
}
|
||||
return Observable.from(output)
|
||||
}
|
||||
}.values
|
||||
}
|
||||
extension AsyncThrowingStream where Element == SystemEvent<Data> {
|
||||
fileprivate func mapAsXcodeBuildOutput() -> AsyncThrowingStream<SystemEvent<XcodeBuildOutput>, Error> {
|
||||
var iterator = makeAsyncIterator()
|
||||
var subIterator: IndexingIterator<[SystemEvent<XcodeBuildOutput>]>?
|
||||
return AsyncThrowingStream<SystemEvent<XcodeBuildOutput>, Error> {
|
||||
if let output = subIterator?.next() {
|
||||
return output
|
||||
}
|
||||
|
||||
fileprivate func run(command: [String],
|
||||
pipedToArguments: [String]) -> AsyncThrowingStream<SystemEvent<XcodeBuildOutput>, Error>
|
||||
{
|
||||
System.shared.observable(command, pipedToArguments: pipedToArguments)
|
||||
.flatMap { event -> Observable<SystemEvent<XcodeBuildOutput>> in
|
||||
switch event {
|
||||
case let .standardError(errorData):
|
||||
guard let line = String(data: errorData, encoding: .utf8) else { return Observable.empty() }
|
||||
let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
|
||||
SystemEvent.standardError(XcodeBuildOutput(raw: "\(String(line))\n"))
|
||||
}
|
||||
return Observable.from(output)
|
||||
case let .standardOutput(outputData):
|
||||
guard let line = String(data: outputData, encoding: .utf8) else { return Observable.empty() }
|
||||
let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
|
||||
SystemEvent.standardOutput(XcodeBuildOutput(raw: "\(String(line))\n"))
|
||||
}
|
||||
return Observable.from(output)
|
||||
guard let event = try await iterator.next() else { return nil }
|
||||
switch event {
|
||||
case let .standardError(errorData):
|
||||
guard let line = String(data: errorData, encoding: .utf8) else { return nil }
|
||||
let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
|
||||
SystemEvent.standardError(XcodeBuildOutput(raw: "\(String(line))\n"))
|
||||
}
|
||||
}.values
|
||||
subIterator = output.makeIterator()
|
||||
return subIterator?.next()
|
||||
case let .standardOutput(outputData):
|
||||
guard let line = String(data: outputData, encoding: .utf8) else { return nil }
|
||||
let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
|
||||
SystemEvent.standardOutput(XcodeBuildOutput(raw: "\(String(line))\n"))
|
||||
}
|
||||
subIterator = output.makeIterator()
|
||||
return subIterator?.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import Foundation
|
||||
import RxSwift
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistGraph
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import Foundation
|
||||
import RxSwift
|
||||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ public final class CarthageController: CarthageControlling {
|
|||
return cached
|
||||
}
|
||||
|
||||
guard let output = try? System.shared.capture("/usr/bin/env", "carthage", "version").spm_chomp() else {
|
||||
guard let output = try? System.shared.capture(["/usr/bin/env", "carthage", "version"]).spm_chomp() else {
|
||||
throw CarthageControllerError.carthageNotFound
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,11 @@ final class CocoaPodsInteractor: CocoaPodsInteracting {
|
|||
|
||||
func install(path: AbsolutePath) throws {
|
||||
let executablePath = try binaryLocator.cocoapodsInteractorPath()
|
||||
try System.shared.runAndPrint(executablePath.pathString, "install", path.pathString)
|
||||
try System.shared.runAndPrint([executablePath.pathString, "install", path.pathString])
|
||||
}
|
||||
|
||||
func update(path: AbsolutePath) throws {
|
||||
let executablePath = try binaryLocator.cocoapodsInteractorPath()
|
||||
try System.shared.runAndPrint(executablePath.pathString, "update", path.pathString)
|
||||
try System.shared.runAndPrint([executablePath.pathString, "update", path.pathString])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,9 @@ class BuildCopier: BuildCopying {
|
|||
let filePath = from.appending(component: file)
|
||||
let toPath = to.appending(component: file)
|
||||
if !FileHandler.shared.exists(filePath) { return }
|
||||
try System.shared.run("/bin/cp", "-rf", filePath.pathString, toPath.pathString)
|
||||
try System.shared.run(["/bin/cp", "-rf", filePath.pathString, toPath.pathString])
|
||||
if file == "tuist" {
|
||||
try System.shared.run("/bin/chmod", "+x", toPath.pathString)
|
||||
try System.shared.run(["/bin/chmod", "+x", toPath.pathString])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,11 +90,11 @@ final class Installer: Installing {
|
|||
logger.notice("Downloading version \(version)")
|
||||
|
||||
let downloadPath = temporaryDirectory.appending(component: Constants.bundleName)
|
||||
try System.shared.run("/usr/bin/curl", "-LSs", "--output", downloadPath.pathString, bundleURL.absoluteString)
|
||||
try System.shared.run(["/usr/bin/curl", "-LSs", "--output", downloadPath.pathString, bundleURL.absoluteString])
|
||||
|
||||
// Unzip
|
||||
logger.notice("Installing...")
|
||||
try System.shared.run("/usr/bin/unzip", "-q", downloadPath.pathString, "-d", installationDirectory.pathString)
|
||||
try System.shared.run(["/usr/bin/unzip", "-q", downloadPath.pathString, "-d", installationDirectory.pathString])
|
||||
|
||||
logger.notice("Version \(version) installed")
|
||||
})
|
||||
|
|
|
@ -77,7 +77,7 @@ public class SwiftPackageManagerInteractor: SwiftPackageManagerInteracting {
|
|||
|
||||
arguments.append(contentsOf: ["-workspace", workspacePath.pathString, "-list"])
|
||||
|
||||
let events = System.shared.observable(arguments).mapToString().values
|
||||
let events = System.shared.publisher(arguments).mapToString().values
|
||||
for try await event in events {
|
||||
switch event {
|
||||
case let .standardError(error):
|
||||
|
|
|
@ -137,7 +137,6 @@ final class CacheController: CacheControlling {
|
|||
)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
private func archive(
|
||||
_ graph: Graph,
|
||||
projectPath: AbsolutePath,
|
||||
|
|
|
@ -36,7 +36,7 @@ public final class CodeLinter: CodeLinting {
|
|||
)
|
||||
let environment = buildEnvironment(sources: sources)
|
||||
|
||||
let events = System.shared.observable(
|
||||
let events = System.shared.publisher(
|
||||
swiftLintArguments,
|
||||
verbose: false,
|
||||
environment: environment
|
||||
|
|
|
@ -98,7 +98,7 @@ final class CertificateParser: CertificateParsing {
|
|||
|
||||
private func subject(at path: AbsolutePath) throws -> String {
|
||||
do {
|
||||
return try System.shared.capture("openssl", "x509", "-inform", "der", "-in", path.pathString, "-noout", "-subject")
|
||||
return try System.shared.capture(["openssl", "x509", "-inform", "der", "-in", path.pathString, "-noout", "-subject"])
|
||||
} catch let TuistSupport.SystemError.terminated(_, _, standardError) {
|
||||
if let string = String(data: standardError, encoding: .utf8) {
|
||||
logger.warning("Parsing subject of \(path) failed with: \(string)")
|
||||
|
@ -112,7 +112,7 @@ final class CertificateParser: CertificateParsing {
|
|||
private func fingerprint(at path: AbsolutePath) throws -> String {
|
||||
do {
|
||||
return try System.shared
|
||||
.capture("openssl", "x509", "-inform", "der", "-in", path.pathString, "-noout", "-fingerprint").spm_chomp()
|
||||
.capture(["openssl", "x509", "-inform", "der", "-in", path.pathString, "-noout", "-fingerprint"]).spm_chomp()
|
||||
} catch let TuistSupport.SystemError.terminated(_, _, standardError) {
|
||||
if let string = String(data: standardError, encoding: .utf8) {
|
||||
logger.warning("Parsing fingerprint of \(path) failed with: \(string)")
|
||||
|
|
|
@ -12,7 +12,7 @@ protocol SecurityControlling {
|
|||
|
||||
final class SecurityController: SecurityControlling {
|
||||
func decodeFile(at path: AbsolutePath) throws -> String {
|
||||
try System.shared.capture("/usr/bin/security", "cms", "-D", "-i", path.pathString)
|
||||
try System.shared.capture(["/usr/bin/security", "cms", "-D", "-i", path.pathString])
|
||||
}
|
||||
|
||||
func importCertificate(_ certificate: Certificate, keychainPath: AbsolutePath) throws {
|
||||
|
@ -29,17 +29,17 @@ final class SecurityController: SecurityControlling {
|
|||
}
|
||||
|
||||
func createKeychain(at path: AbsolutePath, password: String) throws {
|
||||
try System.shared.run("/usr/bin/security", "create-keychain", "-p", password, path.pathString)
|
||||
try System.shared.run(["/usr/bin/security", "create-keychain", "-p", password, path.pathString])
|
||||
logger.debug("Created keychain at \(path.pathString)")
|
||||
}
|
||||
|
||||
func unlockKeychain(at path: AbsolutePath, password: String) throws {
|
||||
try System.shared.run("/usr/bin/security", "unlock-keychain", "-p", password, path.pathString)
|
||||
try System.shared.run(["/usr/bin/security", "unlock-keychain", "-p", password, path.pathString])
|
||||
logger.debug("Unlocked keychain at \(path.pathString)")
|
||||
}
|
||||
|
||||
func lockKeychain(at path: AbsolutePath, password: String) throws {
|
||||
try System.shared.run("/usr/bin/security", "lock-keychain", "-p", password, path.pathString)
|
||||
try System.shared.run(["/usr/bin/security", "lock-keychain", "-p", password, path.pathString])
|
||||
logger.debug("Locked keychain at \(path.pathString)")
|
||||
}
|
||||
|
||||
|
@ -47,14 +47,14 @@ final class SecurityController: SecurityControlling {
|
|||
|
||||
private func certificateExists(_ certificate: Certificate, keychainPath: AbsolutePath) throws -> Bool {
|
||||
do {
|
||||
let existingCertificates = try System.shared.capture(
|
||||
let existingCertificates = try System.shared.capture([
|
||||
"/usr/bin/security",
|
||||
"find-certificate",
|
||||
"-c",
|
||||
certificate.name,
|
||||
"-a",
|
||||
keychainPath.pathString
|
||||
)
|
||||
keychainPath.pathString,
|
||||
])
|
||||
return !existingCertificates.isEmpty
|
||||
} catch {
|
||||
return false
|
||||
|
@ -62,13 +62,13 @@ final class SecurityController: SecurityControlling {
|
|||
}
|
||||
|
||||
private func importToKeychain(at path: AbsolutePath, keychainPath: AbsolutePath) throws {
|
||||
try System.shared.run(
|
||||
try System.shared.run([
|
||||
"/usr/bin/security",
|
||||
"import", path.pathString,
|
||||
"-P", "",
|
||||
"-T", "/usr/bin/codesign",
|
||||
"-T", "/usr/bin/security",
|
||||
"-k", keychainPath.pathString
|
||||
)
|
||||
"-k", keychainPath.pathString,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ public class MachineEnvironment: MachineEnvironmentRetrieving {
|
|||
|
||||
/// The `swiftVersion` of the machine running Tuist
|
||||
public lazy var swiftVersion = try! System.shared // swiftlint:disable:this force_try
|
||||
.capture("/usr/bin/xcrun", "swift", "-version")
|
||||
.capture(["/usr/bin/xcrun", "swift", "-version"])
|
||||
.components(separatedBy: "Swift version ").last!
|
||||
.components(separatedBy: " ").first!
|
||||
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
extension Observable where Element == SystemEvent<Data> {
|
||||
/// Returns another observable where the standard output and error data are mapped
|
||||
/// to a string.
|
||||
public func mapToString() -> Observable<SystemEvent<String>> {
|
||||
map { $0.mapToString() }
|
||||
}
|
||||
}
|
||||
|
||||
extension Observable where Element == SystemEvent<String> {
|
||||
public func print() -> Observable<SystemEvent<String>> {
|
||||
`do`(onNext: { (event: SystemEvent<String>) in
|
||||
switch event {
|
||||
case let .standardError(error):
|
||||
logger.error("\(error)")
|
||||
case let .standardOutput(output):
|
||||
logger.info("\(output)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an observable that prints the standard error.
|
||||
public func printStandardError() -> Observable<SystemEvent<String>> {
|
||||
`do`(onNext: { event in
|
||||
switch event {
|
||||
case let .standardError(error):
|
||||
if let data = error.data(using: .utf8) {
|
||||
FileHandle.standardError.write(data)
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an observable that collects and merges the standard output and error into a single string.
|
||||
public func collectAndMergeOutput() -> Observable<String> {
|
||||
reduce("") { collected, event -> String in
|
||||
var collected = collected
|
||||
switch event {
|
||||
case let .standardError(error):
|
||||
collected.append(error)
|
||||
case let .standardOutput(output):
|
||||
collected.append(output)
|
||||
}
|
||||
return collected
|
||||
}
|
||||
}
|
||||
|
||||
/// It collects the standard output and error into an object that is sent
|
||||
/// as a single event when the process completes.
|
||||
public func collectOutput() -> Observable<SystemCollectedOutput> {
|
||||
reduce(SystemCollectedOutput()) { collected, event -> SystemCollectedOutput in
|
||||
var collected = collected
|
||||
switch event {
|
||||
case let .standardError(error):
|
||||
collected.standardError.append(error)
|
||||
case let .standardOutput(output):
|
||||
collected.standardOutput.append(output)
|
||||
}
|
||||
return collected
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an observable that forwards the system events filtering the standard output ones using the given function.
|
||||
/// - Parameter filter: Function to filter the standard output events.
|
||||
public func filterStandardOutput(_ filter: @escaping (String) -> Bool) -> Observable<SystemEvent<String>> {
|
||||
self.filter {
|
||||
if case let SystemEvent.standardOutput(output) = $0 {
|
||||
return filter(output)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an observable that forwards all the system except the standard output ones rejected by the given function.
|
||||
/// - Parameter rejector: Function to reject standard output events.
|
||||
public func rejectStandardOutput(_ rejector: @escaping (String) -> Bool) -> Observable<SystemEvent<String>> {
|
||||
filter {
|
||||
if case let SystemEvent.standardOutput(output) = $0 {
|
||||
return !rejector(output)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an observable that forwards the system events filtering the standard error ones using the given function.
|
||||
/// - Parameter filter: Function to filter the standard error events.
|
||||
public func filterStandardError(_ filter: @escaping (String) -> Bool) -> Observable<SystemEvent<String>> {
|
||||
self.filter {
|
||||
if case let SystemEvent.standardError(error) = $0 {
|
||||
return filter(error)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an observable that forwards all the system except the standard error ones rejected by the given function.
|
||||
/// - Parameter rejector: Function to reject standard error events.
|
||||
public func rejectStandardError(_ rejector: @escaping (String) -> Bool) -> Observable<SystemEvent<String>> {
|
||||
filter {
|
||||
if case let SystemEvent.standardError(error) = $0 {
|
||||
return !rejector(error)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -66,52 +66,25 @@ extension Publisher where Output == SystemEvent<String>, Failure == Error {
|
|||
return collected
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an observable that forwards the system events filtering the standard output ones using the given function.
|
||||
/// - Parameter filter: Function to filter the standard output events.
|
||||
public func filterStandardOutput(_ filter: @escaping (String) -> Bool) -> AnyPublisher<SystemEvent<String>, Error> {
|
||||
self.filter {
|
||||
if case let SystemEvent.standardOutput(output) = $0 {
|
||||
return filter(output)
|
||||
} else {
|
||||
return true
|
||||
extension Publisher {
|
||||
public var values: AsyncThrowingStream<Output, Error> {
|
||||
AsyncThrowingStream(Output.self) { continuation in
|
||||
let cancellable = sink { completion in
|
||||
switch completion {
|
||||
case .finished:
|
||||
continuation.finish()
|
||||
case let .failure(error):
|
||||
continuation.finish(throwing: error)
|
||||
}
|
||||
} receiveValue: { output in
|
||||
continuation.yield(output)
|
||||
}
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
/// Returns an observable that forwards all the system except the standard output ones rejected by the given function.
|
||||
/// - Parameter rejector: Function to reject standard output events.
|
||||
public func rejectStandardOutput(_ rejector: @escaping (String) -> Bool) -> AnyPublisher<SystemEvent<String>, Error> {
|
||||
filter {
|
||||
if case let SystemEvent.standardOutput(output) = $0 {
|
||||
return !rejector(output)
|
||||
} else {
|
||||
return true
|
||||
continuation.onTermination = { @Sendable [cancellable] _ in
|
||||
cancellable.cancel()
|
||||
}
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
/// Returns an observable that forwards the system events filtering the standard error ones using the given function.
|
||||
/// - Parameter filter: Function to filter the standard error events.
|
||||
public func filterStandardError(_ filter: @escaping (String) -> Bool) -> AnyPublisher<SystemEvent<String>, Error> {
|
||||
self.filter {
|
||||
if case let SystemEvent.standardError(error) = $0 {
|
||||
return filter(error)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
/// Returns an observable that forwards all the system except the standard error ones rejected by the given function.
|
||||
/// - Parameter rejector: Function to reject standard error events.
|
||||
public func rejectStandardError(_ rejector: @escaping (String) -> Bool) -> AnyPublisher<SystemEvent<String>, Error> {
|
||||
filter {
|
||||
if case let SystemEvent.standardError(error) = $0 {
|
||||
return !rejector(error)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,188 +1,8 @@
|
|||
import Combine
|
||||
import CombineExt
|
||||
import Foundation
|
||||
import RxSwift
|
||||
import TSCBasic
|
||||
|
||||
public protocol Systeming {
|
||||
/// System environment.
|
||||
var env: [String: String] { get }
|
||||
|
||||
/// Runs a command without collecting output nor printing anything.
|
||||
///
|
||||
/// - Parameter arguments: Command.
|
||||
/// - Throws: An error if the command fails
|
||||
func run(_ arguments: [String]) throws
|
||||
|
||||
/// Runs a command without collecting output nor printing anything.
|
||||
///
|
||||
/// - Parameter arguments: Command.
|
||||
/// - Throws: An error if the command fails
|
||||
func run(_ arguments: String...) throws
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
func capture(_ arguments: String...) throws -> String
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
func capture(_ arguments: [String]) throws -> String
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
func capture(_ arguments: String..., verbose: Bool, environment: [String: String]) throws -> String
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
func capture(_ arguments: [String], verbose: Bool, environment: [String: String]) throws -> String
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Throws: An error if the command fails.
|
||||
func runAndPrint(_ arguments: String...) throws
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Throws: An error if the command fails.
|
||||
func runAndPrint(_ arguments: [String]) throws
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Throws: An error if the command fails.
|
||||
func runAndPrint(_ arguments: String..., verbose: Bool, environment: [String: String]) throws
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Throws: An error if the command fails.
|
||||
func runAndPrint(_ arguments: [String], verbose: Bool, environment: [String: String]) throws
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - redirection: Instance through which the output will be redirected.
|
||||
/// - Throws: An error if the command fails.
|
||||
func runAndPrint(
|
||||
_ arguments: [String],
|
||||
verbose: Bool,
|
||||
environment: [String: String],
|
||||
redirection: TSCBasic.Process.OutputRedirection
|
||||
) throws
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
func runAndCollectOutput(_ arguments: [String]) async throws -> SystemCollectedOutput
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a observable.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
func observable(_ arguments: [String]) -> Observable<SystemEvent<Data>>
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a observable.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
func observable(_ arguments: [String], verbose: Bool) -> Observable<SystemEvent<Data>>
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a publisher.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
func publisher(_ arguments: [String]) -> AnyPublisher<SystemEvent<Data>, Error>
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a publisher.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
func publisher(_ arguments: [String], verbose: Bool) -> AnyPublisher<SystemEvent<Data>, Error>
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a observable.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the command.
|
||||
func observable(_ arguments: [String], verbose: Bool, environment: [String: String]) -> Observable<SystemEvent<Data>>
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a observable.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - pipedToArguments: Second Command.
|
||||
func observable(_ arguments: [String], pipedToArguments: [String]) -> Observable<SystemEvent<Data>>
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a observable.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - environment: Environment that should be used when running the command.
|
||||
/// - secondArguments: Second Command.
|
||||
func observable(_ arguments: [String], environment: [String: String], pipeTo secondArguments: [String])
|
||||
-> Observable<SystemEvent<Data>>
|
||||
|
||||
/// Runs a command in the shell asynchronously.
|
||||
/// When the process that triggers the command gets killed, the command continues its execution.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Throws: An error if the command fails.
|
||||
func async(_ arguments: [String]) throws
|
||||
|
||||
/// Runs a command in the shell asynchronously.
|
||||
/// When the process that triggers the command gets killed, the command continues its execution.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the command.
|
||||
/// - Throws: An error if the command fails.
|
||||
func async(_ arguments: [String], verbose: Bool, environment: [String: String]) throws
|
||||
|
||||
/// Returns the Swift version.
|
||||
///
|
||||
/// - Returns: Swift version.
|
||||
/// - Throws: An error if Swift is not installed or it exists unsuccessfully.
|
||||
func swiftVersion() throws -> String
|
||||
|
||||
/// Runs /usr/bin/which passing the given tool.
|
||||
///
|
||||
/// - Parameter name: Tool whose path will be obtained using which.
|
||||
/// - Returns: The output of running 'which' with the given tool name.
|
||||
/// - Throws: An error if which exits unsuccessfully.
|
||||
func which(_ name: String) throws -> String
|
||||
}
|
||||
|
||||
extension ProcessResult {
|
||||
/// Throws a SystemError if the result is unsuccessful.
|
||||
///
|
||||
|
@ -245,7 +65,6 @@ public enum SystemError: FatalError, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next type_body_length
|
||||
public final class System: Systeming {
|
||||
/// Shared system instance.
|
||||
public static var shared: Systeming = System()
|
||||
|
@ -267,83 +86,14 @@ public final class System: Systeming {
|
|||
|
||||
// MARK: - Systeming
|
||||
|
||||
/// Runs a command without collecting output nor printing anything.
|
||||
///
|
||||
/// - Parameter arguments: Command.
|
||||
/// - Throws: An error if the command fails
|
||||
public func run(_ arguments: [String]) throws {
|
||||
let process = Process(
|
||||
arguments: arguments,
|
||||
environment: env,
|
||||
outputRedirection: .collect,
|
||||
verbose: false,
|
||||
startNewProcessGroup: false
|
||||
)
|
||||
|
||||
logger.debug("\(escaped(arguments: arguments))")
|
||||
|
||||
try process.launch()
|
||||
let result = try process.waitUntilExit()
|
||||
let output = try result.utf8Output()
|
||||
|
||||
logger.debug("\(output)")
|
||||
|
||||
try result.throwIfErrored()
|
||||
_ = try capture(arguments)
|
||||
}
|
||||
|
||||
/// Runs a command without collecting output nor printing anything.
|
||||
///
|
||||
/// - Parameter arguments: Command.
|
||||
/// - Throws: An error if the command fails
|
||||
public func run(_ arguments: String...) throws {
|
||||
try run(arguments)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Arguments to be passed.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func capture(_ arguments: String...) throws -> String {
|
||||
try capture(arguments)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func capture(_ arguments: [String]) throws -> String {
|
||||
try capture(arguments, verbose: false, environment: env)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Arguments to be passed.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func capture(_ arguments: String...,
|
||||
verbose: Bool,
|
||||
environment: [String: String]) throws -> String
|
||||
{
|
||||
try capture(arguments, verbose: verbose, environment: environment)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Arguments to be passed.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func capture(_ arguments: [String],
|
||||
verbose: Bool,
|
||||
environment: [String: String]) throws -> String
|
||||
|
@ -369,49 +119,10 @@ public final class System: Systeming {
|
|||
return try result.utf8Output()
|
||||
}
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func runAndPrint(_ arguments: String...) throws {
|
||||
try runAndPrint(arguments)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func runAndPrint(_ arguments: [String]) throws {
|
||||
try runAndPrint(arguments, verbose: false, environment: env)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Arguments to be passed.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func runAndPrint(_ arguments: String...,
|
||||
verbose: Bool,
|
||||
environment: [String: String]) throws
|
||||
{
|
||||
try runAndPrint(
|
||||
arguments,
|
||||
verbose: verbose,
|
||||
environment: environment
|
||||
)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command arguments
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func runAndPrint(_ arguments: [String],
|
||||
verbose: Bool,
|
||||
environment: [String: String]) throws
|
||||
|
@ -424,62 +135,24 @@ public final class System: Systeming {
|
|||
)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - redirection: Instance through which the output will be redirected.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func runAndPrint(_ arguments: [String],
|
||||
verbose: Bool,
|
||||
environment: [String: String],
|
||||
redirection: TSCBasic.Process.OutputRedirection) throws
|
||||
{
|
||||
let process = Process(
|
||||
arguments: arguments,
|
||||
environment: environment,
|
||||
outputRedirection: .stream(stdout: { bytes in
|
||||
FileHandle.standardOutput.write(Data(bytes))
|
||||
redirection.outputClosures?.stdoutClosure(bytes)
|
||||
}, stderr: { bytes in
|
||||
FileHandle.standardError.write(Data(bytes))
|
||||
redirection.outputClosures?.stderrClosure(bytes)
|
||||
}),
|
||||
verbose: verbose,
|
||||
startNewProcessGroup: false
|
||||
)
|
||||
|
||||
logger.debug("\(escaped(arguments: arguments))")
|
||||
|
||||
try process.launch()
|
||||
let result = try process.waitUntilExit()
|
||||
let output = try result.utf8Output()
|
||||
|
||||
logger.debug("\(output)")
|
||||
|
||||
try result.throwIfErrored()
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String]) -> Observable<SystemEvent<Data>> {
|
||||
observable(arguments, verbose: false)
|
||||
}
|
||||
|
||||
public func runAndCollectOutput(_ arguments: [String]) async throws -> SystemCollectedOutput {
|
||||
try await observable(arguments)
|
||||
var values = publisher(arguments)
|
||||
.mapToString()
|
||||
.collectOutput()
|
||||
.asSingle()
|
||||
.value
|
||||
.eraseToAnyPublisher()
|
||||
.values.makeAsyncIterator()
|
||||
|
||||
return try await values.next()!
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String], verbose: Bool) -> Observable<SystemEvent<Data>> {
|
||||
observable(arguments, verbose: verbose, environment: env)
|
||||
public func publisher(_ arguments: [String]) -> AnyPublisher<SystemEvent<Data>, Error> {
|
||||
publisher(arguments, verbose: false, environment: env)
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String], verbose: Bool, environment: [String: String]) -> Observable<SystemEvent<Data>> {
|
||||
Observable.create { observer -> Disposable in
|
||||
public func publisher(_ arguments: [String], verbose: Bool,
|
||||
environment: [String: String]) -> AnyPublisher<SystemEvent<Data>, Error>
|
||||
{
|
||||
.create { subscriber -> Cancellable in
|
||||
let synchronizationQueue = DispatchQueue(label: "io.tuist.support.system")
|
||||
var errorData: [UInt8] = []
|
||||
let process = Process(
|
||||
|
@ -487,12 +160,12 @@ public final class System: Systeming {
|
|||
environment: environment,
|
||||
outputRedirection: .stream(stdout: { bytes in
|
||||
synchronizationQueue.async {
|
||||
observer.onNext(.standardOutput(Data(bytes)))
|
||||
subscriber.send(.standardOutput(Data(bytes)))
|
||||
}
|
||||
}, stderr: { bytes in
|
||||
synchronizationQueue.async {
|
||||
errorData.append(contentsOf: bytes)
|
||||
observer.onNext(.standardError(Data(bytes)))
|
||||
subscriber.send(.standardError(Data(bytes)))
|
||||
}
|
||||
}),
|
||||
verbose: verbose,
|
||||
|
@ -510,112 +183,33 @@ public final class System: Systeming {
|
|||
)
|
||||
try result.throwIfErrored()
|
||||
synchronizationQueue.sync {
|
||||
observer.onCompleted()
|
||||
subscriber.send(completion: .finished)
|
||||
}
|
||||
} catch {
|
||||
synchronizationQueue.sync {
|
||||
observer.onError(error)
|
||||
subscriber.send(completion: .failure(error))
|
||||
}
|
||||
}
|
||||
return Disposables.create {
|
||||
return AnyCancellable {
|
||||
if process.launched {
|
||||
process.signal(9) // SIGKILL
|
||||
}
|
||||
}
|
||||
}
|
||||
.subscribe(on: ConcurrentDispatchQueueScheduler(queue: DispatchQueue.global()))
|
||||
}.subscribe(on: DispatchQueue.global()).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String], pipedToArguments: [String]) -> Observable<SystemEvent<Data>> {
|
||||
observable(arguments, environment: env, pipeTo: pipedToArguments)
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String],
|
||||
environment: [String: String],
|
||||
pipeTo secondArguments: [String]) -> Observable<SystemEvent<Data>>
|
||||
public func publisher(_ arguments: [String],
|
||||
pipeTo secondArguments: [String]) -> AnyPublisher<SystemEvent<Data>, Error>
|
||||
{
|
||||
Observable.create { observer -> Disposable in
|
||||
let synchronizationQueue = DispatchQueue(label: "io.tuist.support.system")
|
||||
var errorData: [UInt8] = []
|
||||
var processOne = System.process(arguments, environment: environment)
|
||||
var processTwo = System.process(secondArguments, environment: environment)
|
||||
|
||||
System.pipe(&processOne, &processTwo)
|
||||
|
||||
let pipes = System.pipeOutput(&processTwo)
|
||||
|
||||
pipes.stdOut.fileHandleForReading.readabilityHandler = { fileHandle in
|
||||
synchronizationQueue.async {
|
||||
let data: Data = fileHandle.availableData
|
||||
observer.onNext(.standardOutput(data))
|
||||
}
|
||||
}
|
||||
|
||||
pipes.stdErr.fileHandleForReading.readabilityHandler = { fileHandle in
|
||||
synchronizationQueue.async {
|
||||
let data: Data = fileHandle.availableData
|
||||
errorData.append(contentsOf: data)
|
||||
observer.onNext(.standardError(data))
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try processOne.run()
|
||||
try processTwo.run()
|
||||
processOne.waitUntilExit()
|
||||
|
||||
let exitStatus = ProcessResult.ExitStatus.terminated(code: processOne.terminationStatus)
|
||||
let result = ProcessResult(
|
||||
arguments: arguments,
|
||||
environment: environment,
|
||||
exitStatus: exitStatus,
|
||||
output: .success([]),
|
||||
stderrOutput: .success(errorData)
|
||||
)
|
||||
try result.throwIfErrored()
|
||||
synchronizationQueue.sync {
|
||||
observer.onCompleted()
|
||||
}
|
||||
} catch {
|
||||
synchronizationQueue.sync {
|
||||
observer.onError(error)
|
||||
}
|
||||
}
|
||||
return Disposables.create {
|
||||
pipes.stdOut.fileHandleForReading.readabilityHandler = nil
|
||||
pipes.stdErr.fileHandleForReading.readabilityHandler = nil
|
||||
if processOne.isRunning {
|
||||
processOne.terminate()
|
||||
}
|
||||
}
|
||||
}
|
||||
.subscribe(on: ConcurrentDispatchQueueScheduler(queue: DispatchQueue.global()))
|
||||
publisher(arguments, environment: env, pipeTo: secondArguments)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell asynchronously.
|
||||
/// When the process that triggers the command gets killed, the command continues its execution.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func async(_ arguments: [String]) throws {
|
||||
try async(arguments, verbose: false, environment: env)
|
||||
}
|
||||
|
||||
/// Runs a command in the shell asynchronously.
|
||||
/// When the process that triggers the command gets killed, the command continues its execution.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the command.
|
||||
/// - Throws: An error if the command fails.
|
||||
public func async(_ arguments: [String], verbose: Bool, environment: [String: String]) throws {
|
||||
let process = Process(
|
||||
arguments: arguments,
|
||||
environment: environment,
|
||||
environment: env,
|
||||
outputRedirection: .none,
|
||||
verbose: verbose,
|
||||
verbose: false,
|
||||
startNewProcessGroup: true
|
||||
)
|
||||
|
||||
|
@ -627,15 +221,11 @@ public final class System: Systeming {
|
|||
@Atomic
|
||||
var cachedSwiftVersion: String?
|
||||
|
||||
/// Returns the Swift version.
|
||||
///
|
||||
/// - Returns: Swift version.
|
||||
/// - Throws: An error if Swift is not installed or it exists unsuccessfully.
|
||||
public func swiftVersion() throws -> String {
|
||||
if let cachedSwiftVersion = cachedSwiftVersion {
|
||||
return cachedSwiftVersion
|
||||
}
|
||||
let output = try capture("/usr/bin/xcrun", "swift", "--version")
|
||||
let output = try capture(["/usr/bin/xcrun", "swift", "--version"])
|
||||
let range = NSRange(location: 0, length: output.count)
|
||||
guard let match = System.swiftVersionRegex.firstMatch(in: output, options: [], range: range) else {
|
||||
throw SystemError.parseSwiftVersion(output)
|
||||
|
@ -644,13 +234,8 @@ public final class System: Systeming {
|
|||
return cachedSwiftVersion!
|
||||
}
|
||||
|
||||
/// Runs /usr/bin/which passing the given tool.
|
||||
///
|
||||
/// - Parameter name: Tool whose path will be obtained using which.
|
||||
/// - Returns: The output of running 'which' with the given tool name.
|
||||
/// - Throws: An error if which exits unsuccessfully.
|
||||
public func which(_ name: String) throws -> String {
|
||||
try capture("/usr/bin/env", "which", name).spm_chomp()
|
||||
try capture(["/usr/bin/env", "which", name]).spm_chomp()
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
@ -700,60 +285,99 @@ public final class System: Systeming {
|
|||
|
||||
return (stdOut, stdErr)
|
||||
}
|
||||
}
|
||||
|
||||
extension Systeming {
|
||||
public func publisher(_ arguments: [String], pipedToArguments: [String]) -> AnyPublisher<SystemEvent<Data>, Error> {
|
||||
AnyPublisher.create { subscriber -> Cancellable in
|
||||
let disposable = self.observable(arguments, pipedToArguments: pipedToArguments).subscribe { event in
|
||||
switch event {
|
||||
case .completed:
|
||||
subscriber.send(completion: .finished)
|
||||
case let .error(error):
|
||||
subscriber.send(completion: .failure(error))
|
||||
case let .next(event):
|
||||
subscriber.send(event)
|
||||
}
|
||||
}
|
||||
return AnyCancellable {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
private func runAndPrint(_ arguments: [String],
|
||||
verbose: Bool,
|
||||
environment: [String: String],
|
||||
redirection: TSCBasic.Process.OutputRedirection) throws
|
||||
{
|
||||
let process = Process(
|
||||
arguments: arguments,
|
||||
environment: environment,
|
||||
outputRedirection: .stream(stdout: { bytes in
|
||||
FileHandle.standardOutput.write(Data(bytes))
|
||||
redirection.outputClosures?.stdoutClosure(bytes)
|
||||
}, stderr: { bytes in
|
||||
FileHandle.standardError.write(Data(bytes))
|
||||
redirection.outputClosures?.stderrClosure(bytes)
|
||||
}),
|
||||
verbose: verbose,
|
||||
startNewProcessGroup: false
|
||||
)
|
||||
|
||||
logger.debug("\(escaped(arguments: arguments))")
|
||||
|
||||
try process.launch()
|
||||
let result = try process.waitUntilExit()
|
||||
let output = try result.utf8Output()
|
||||
|
||||
logger.debug("\(output)")
|
||||
|
||||
try result.throwIfErrored()
|
||||
}
|
||||
|
||||
public func publisher(_ arguments: [String]) -> AnyPublisher<SystemEvent<Data>, Error> {
|
||||
AnyPublisher.create { subscriber -> Cancellable in
|
||||
let disposable = self.observable(arguments).subscribe { event in
|
||||
switch event {
|
||||
case .completed:
|
||||
subscriber.send(completion: .finished)
|
||||
case let .error(error):
|
||||
subscriber.send(completion: .failure(error))
|
||||
case let .next(event):
|
||||
subscriber.send(event)
|
||||
}
|
||||
}
|
||||
return AnyCancellable {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
private func publisher(_ arguments: [String],
|
||||
environment: [String: String],
|
||||
pipeTo secondArguments: [String]) -> AnyPublisher<SystemEvent<Data>, Error>
|
||||
{
|
||||
.create { subscriber -> Cancellable in
|
||||
let synchronizationQueue = DispatchQueue(label: "io.tuist.support.system")
|
||||
var errorData: [UInt8] = []
|
||||
var processOne = System.process(arguments, environment: environment)
|
||||
var processTwo = System.process(secondArguments, environment: environment)
|
||||
|
||||
public func publisher(_ arguments: [String], verbose: Bool) -> AnyPublisher<SystemEvent<Data>, Error> {
|
||||
AnyPublisher.create { subscriber -> Cancellable in
|
||||
let disposable = self.observable(arguments, verbose: verbose).subscribe { event in
|
||||
switch event {
|
||||
case .completed:
|
||||
System.pipe(&processOne, &processTwo)
|
||||
|
||||
let pipes = System.pipeOutput(&processTwo)
|
||||
|
||||
pipes.stdOut.fileHandleForReading.readabilityHandler = { fileHandle in
|
||||
synchronizationQueue.async {
|
||||
let data: Data = fileHandle.availableData
|
||||
if !data.isEmpty {
|
||||
subscriber.send(.standardOutput(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pipes.stdErr.fileHandleForReading.readabilityHandler = { fileHandle in
|
||||
synchronizationQueue.async {
|
||||
let data: Data = fileHandle.availableData
|
||||
errorData.append(contentsOf: data)
|
||||
if !data.isEmpty {
|
||||
subscriber.send(.standardError(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try processOne.run()
|
||||
try processTwo.run()
|
||||
processOne.waitUntilExit()
|
||||
|
||||
let exitStatus = ProcessResult.ExitStatus.terminated(code: processOne.terminationStatus)
|
||||
let result = ProcessResult(
|
||||
arguments: arguments,
|
||||
environment: environment,
|
||||
exitStatus: exitStatus,
|
||||
output: .success([]),
|
||||
stderrOutput: .success(errorData)
|
||||
)
|
||||
try result.throwIfErrored()
|
||||
synchronizationQueue.sync {
|
||||
subscriber.send(completion: .finished)
|
||||
case let .error(error):
|
||||
}
|
||||
} catch {
|
||||
synchronizationQueue.sync {
|
||||
subscriber.send(completion: .failure(error))
|
||||
case let .next(event):
|
||||
subscriber.send(event)
|
||||
}
|
||||
}
|
||||
return AnyCancellable {
|
||||
disposable.dispose()
|
||||
pipes.stdOut.fileHandleForReading.readabilityHandler = nil
|
||||
pipes.stdErr.fileHandleForReading.readabilityHandler = nil
|
||||
if processOne.isRunning {
|
||||
processOne.terminate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.subscribe(on: DispatchQueue.global()).eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
|
||||
public protocol Systeming {
|
||||
/// System environment.
|
||||
var env: [String: String] { get }
|
||||
|
||||
/// Runs a command without collecting output nor printing anything.
|
||||
///
|
||||
/// - Parameter arguments: Command.
|
||||
/// - Throws: An error if the command fails
|
||||
func run(_ arguments: [String]) throws
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
func capture(_ arguments: [String]) throws -> String
|
||||
|
||||
/// Runs a command in the shell and returns the standard output string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Returns: Standard output string.
|
||||
/// - Throws: An error if the command fails.
|
||||
func capture(_ arguments: [String], verbose: Bool, environment: [String: String]) throws -> String
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Throws: An error if the command fails.
|
||||
func runAndPrint(_ arguments: [String]) throws
|
||||
|
||||
/// Runs a command in the shell printing its output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the task.
|
||||
/// - Throws: An error if the command fails.
|
||||
func runAndPrint(_ arguments: [String], verbose: Bool, environment: [String: String]) throws
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
func runAndCollectOutput(_ arguments: [String]) async throws -> SystemCollectedOutput
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a publisher.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
func publisher(_ arguments: [String]) -> AnyPublisher<SystemEvent<Data>, Error>
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a publisher.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - verbose: When true it prints the command that will be executed before executing it.
|
||||
/// - environment: Environment that should be used when running the command.
|
||||
func publisher(_ arguments: [String], verbose: Bool, environment: [String: String]) -> AnyPublisher<SystemEvent<Data>, Error>
|
||||
|
||||
/// Runs a command in the shell and wraps the standard output and error in a publisher.
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - pipeTo: Second Command.
|
||||
func publisher(_ arguments: [String], pipeTo secondArguments: [String]) -> AnyPublisher<SystemEvent<Data>, Error>
|
||||
|
||||
/// Runs a command in the shell asynchronously.
|
||||
/// When the process that triggers the command gets killed, the command continues its execution.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - arguments: Command.
|
||||
/// - Throws: An error if the command fails.
|
||||
func async(_ arguments: [String]) throws
|
||||
|
||||
/// Returns the Swift version.
|
||||
///
|
||||
/// - Returns: Swift version.
|
||||
/// - Throws: An error if Swift is not installed or it exists unsuccessfully.
|
||||
func swiftVersion() throws -> String
|
||||
|
||||
/// Runs /usr/bin/which passing the given tool.
|
||||
///
|
||||
/// - Parameter name: Tool whose path will be obtained using which.
|
||||
/// - Returns: The output of running 'which' with the given tool name.
|
||||
/// - Throws: An error if which exits unsuccessfully.
|
||||
func which(_ name: String) throws -> String
|
||||
}
|
|
@ -34,11 +34,11 @@ public final class DeveloperEnvironment: DeveloperEnvironmenting {
|
|||
return _derivedDataDirectory
|
||||
}
|
||||
let location: AbsolutePath
|
||||
if let customLocation = try? System.shared.capture(
|
||||
if let customLocation = try? System.shared.capture([
|
||||
"/usr/bin/defaults",
|
||||
"read",
|
||||
"com.apple.dt.Xcode IDECustomDerivedDataLocation"
|
||||
) {
|
||||
"com.apple.dt.Xcode IDECustomDerivedDataLocation",
|
||||
]) {
|
||||
location = AbsolutePath(customLocation.chomp())
|
||||
} else {
|
||||
// Default location
|
||||
|
@ -54,7 +54,7 @@ public final class DeveloperEnvironment: DeveloperEnvironmenting {
|
|||
return _architecture
|
||||
}
|
||||
// swiftlint:disable:next force_try
|
||||
let output = try! System.shared.capture("/usr/bin/uname", "-m").chomp()
|
||||
let output = try! System.shared.capture(["/usr/bin/uname", "-m"]).chomp()
|
||||
_architecture = MacArchitecture(rawValue: output)
|
||||
return _architecture!
|
||||
}
|
||||
|
|
|
@ -69,15 +69,15 @@ public class GitEnvironment: GitEnvironmenting {
|
|||
public func githubCredentials() -> AnyPublisher<GithubCredentials?, Error> {
|
||||
System.shared.publisher(
|
||||
["/usr/bin/env", "echo", "url=https://github.com"],
|
||||
pipedToArguments: ["/usr/bin/env", "git", "credential", "fill"]
|
||||
pipeTo: ["/usr/bin/env", "git", "credential", "fill"]
|
||||
)
|
||||
.mapToString()
|
||||
.collectAndMergeOutput()
|
||||
.flatMap { (output: String) -> AnyPublisher<GithubCredentials?, Error> in
|
||||
// protocol=https
|
||||
// host=github.com
|
||||
// username=pepibumur
|
||||
// password=foo
|
||||
// protocol=https
|
||||
// host=github.com
|
||||
// username=pepibumur
|
||||
// password=foo
|
||||
let lines = output.split(separator: "\n")
|
||||
let values = lines.reduce(into: [String: String]()) { result, next in
|
||||
let components = next.split(separator: "=")
|
||||
|
|
|
@ -1,64 +1,39 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
import RxSwift
|
||||
import TSCBasic
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
@testable import TuistSupport
|
||||
|
||||
public final class MockSystem: Systeming {
|
||||
public var env: [String: String] = ProcessInfo.processInfo.environment
|
||||
public var env: [String: String] = [:]
|
||||
|
||||
// swiftlint:disable:next large_tuple
|
||||
public var stubs: [String: (stderror: String?, stdout: String?, exitstatus: Int?)] = [:]
|
||||
private var calls: [String] = []
|
||||
var whichStub: ((String) throws -> String?)?
|
||||
var swiftVersionStub: (() throws -> String)?
|
||||
|
||||
public init() {}
|
||||
|
||||
public func errorCommand(_ arguments: String..., error: String? = nil) {
|
||||
errorCommand(arguments, error: error)
|
||||
public func succeedCommand(_ arguments: [String], output: String? = nil) {
|
||||
stubs[arguments.joined(separator: " ")] = (stderror: nil, stdout: output, exitstatus: 0)
|
||||
}
|
||||
|
||||
public func errorCommand(_ arguments: [String], error: String? = nil) {
|
||||
stubs[arguments.joined(separator: " ")] = (stderror: error, stdout: nil, exitstatus: 1)
|
||||
}
|
||||
|
||||
public func succeedCommand(_ arguments: String..., output: String? = nil) {
|
||||
succeedCommand(arguments, output: output)
|
||||
}
|
||||
|
||||
public func succeedCommand(_ arguments: [String], output: String? = nil) {
|
||||
stubs[arguments.joined(separator: " ")] = (stderror: nil, stdout: output, exitstatus: 0)
|
||||
public func called(_ args: [String]) -> Bool {
|
||||
let command = args.joined(separator: " ")
|
||||
return calls.contains(command)
|
||||
}
|
||||
|
||||
public func run(_ arguments: [String]) throws {
|
||||
let command = arguments.joined(separator: " ")
|
||||
guard let stub = stubs[command] else {
|
||||
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1, standardError: Data())
|
||||
}
|
||||
if stub.exitstatus != 0 {
|
||||
let standardError = (stub.stderror?.data(using: .utf8)) ?? Data()
|
||||
throw TuistSupport.SystemError.terminated(
|
||||
command: arguments.first!,
|
||||
code: Int32(stub.exitstatus ?? 1),
|
||||
standardError: standardError
|
||||
)
|
||||
}
|
||||
calls.append(arguments.joined(separator: " "))
|
||||
}
|
||||
|
||||
public func run(_ arguments: String...) throws {
|
||||
try run(arguments)
|
||||
_ = try capture(arguments)
|
||||
}
|
||||
|
||||
public func capture(_ arguments: [String]) throws -> String {
|
||||
try capture(arguments, verbose: false, environment: [:])
|
||||
}
|
||||
|
||||
public func capture(_ arguments: String...) throws -> String {
|
||||
try capture(arguments, verbose: false, environment: [:])
|
||||
}
|
||||
|
||||
public func capture(_ arguments: String..., verbose: Bool, environment: [String: String]) throws -> String {
|
||||
try capture(arguments, verbose: verbose, environment: environment)
|
||||
try capture(arguments, verbose: false, environment: env)
|
||||
}
|
||||
|
||||
public func capture(_ arguments: [String], verbose _: Bool, environment _: [String: String]) throws -> String {
|
||||
|
@ -73,114 +48,93 @@ public final class MockSystem: Systeming {
|
|||
return stub.stdout ?? ""
|
||||
}
|
||||
|
||||
public func runAndPrint(_ arguments: String...) throws {
|
||||
try runAndPrint(arguments)
|
||||
}
|
||||
|
||||
public func runAndPrint(_ arguments: [String]) throws {
|
||||
try runAndPrint(arguments, verbose: false, environment: [:])
|
||||
_ = try capture(arguments, verbose: false, environment: env)
|
||||
}
|
||||
|
||||
public func runAndPrint(_ arguments: String..., verbose: Bool, environment: [String: String]) throws {
|
||||
try runAndPrint(arguments, verbose: verbose, environment: environment)
|
||||
}
|
||||
|
||||
public func runAndPrint(_ arguments: [String], verbose: Bool, environment: [String: String]) throws {
|
||||
try runAndPrint(arguments, verbose: verbose, environment: environment, redirection: .none)
|
||||
}
|
||||
|
||||
public func runAndPrint(
|
||||
_ arguments: [String],
|
||||
verbose _: Bool,
|
||||
environment _: [String: String],
|
||||
redirection: TSCBasic.Process.OutputRedirection
|
||||
) throws {
|
||||
let command = arguments.joined(separator: " ")
|
||||
guard let stub = stubs[command] else {
|
||||
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1, standardError: Data())
|
||||
}
|
||||
if stub.exitstatus != 0 {
|
||||
if let error = stub.stderror {
|
||||
redirection.outputClosures?.stderrClosure([UInt8](error.data(using: .utf8)!))
|
||||
}
|
||||
throw TuistSupport.SystemError.terminated(command: arguments.first!, code: 1, standardError: Data())
|
||||
}
|
||||
calls.append(arguments.joined(separator: " "))
|
||||
public func runAndPrint(_ arguments: [String], verbose _: Bool, environment _: [String: String]) throws {
|
||||
_ = try capture(arguments, verbose: false, environment: env)
|
||||
}
|
||||
|
||||
public func runAndCollectOutput(_ arguments: [String]) async throws -> SystemCollectedOutput {
|
||||
try await observable(arguments, verbose: false)
|
||||
var values = publisher(arguments)
|
||||
.mapToString()
|
||||
.collectOutput()
|
||||
.asSingle()
|
||||
.value
|
||||
.eraseToAnyPublisher()
|
||||
.values.makeAsyncIterator()
|
||||
|
||||
return try await values.next()!
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String]) -> Observable<SystemEvent<Data>> {
|
||||
observable(arguments, verbose: false)
|
||||
public func publisher(_ arguments: [String]) -> AnyPublisher<SystemEvent<Data>, Error> {
|
||||
publisher(arguments, verbose: false, environment: env)
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String], verbose: Bool) -> Observable<SystemEvent<Data>> {
|
||||
observable(arguments, verbose: verbose, environment: [:])
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String], verbose _: Bool,
|
||||
environment _: [String: String]) -> Observable<SystemEvent<Data>>
|
||||
public func publisher(_ arguments: [String], verbose _: Bool,
|
||||
environment _: [String: String]) -> AnyPublisher<SystemEvent<Data>, Error>
|
||||
{
|
||||
Observable.create { observer -> Disposable in
|
||||
AnyPublisher<SystemEvent<Data>, Error>.create { subscriber in
|
||||
let command = arguments.joined(separator: " ")
|
||||
guard let stub = self.stubs[command] else {
|
||||
observer.onError(TuistSupport.SystemError.terminated(command: arguments.first!, code: 1, standardError: Data()))
|
||||
return Disposables.create()
|
||||
subscriber
|
||||
.send(completion: .failure(
|
||||
TuistSupport.SystemError
|
||||
.terminated(command: arguments.first!, code: 1, standardError: Data())
|
||||
))
|
||||
return AnyCancellable {}
|
||||
}
|
||||
guard stub.exitstatus == 0 else {
|
||||
if let error = stub.stderror {
|
||||
observer.onNext(.standardError(error.data(using: .utf8)!))
|
||||
subscriber.send(.standardError(error.data(using: .utf8)!))
|
||||
}
|
||||
observer.onError(TuistSupport.SystemError.terminated(command: arguments.first!, code: 1, standardError: Data()))
|
||||
return Disposables.create()
|
||||
subscriber
|
||||
.send(completion: .failure(
|
||||
TuistSupport.SystemError
|
||||
.terminated(command: arguments.first!, code: 1, standardError: Data())
|
||||
))
|
||||
|
||||
return AnyCancellable {}
|
||||
}
|
||||
if let stdout = stub.stdout {
|
||||
observer.onNext(.standardOutput(stdout.data(using: .utf8)!))
|
||||
subscriber.send(.standardOutput(stdout.data(using: .utf8)!))
|
||||
}
|
||||
observer.onCompleted()
|
||||
return Disposables.create()
|
||||
subscriber.send(completion: .finished)
|
||||
return AnyCancellable {}
|
||||
}
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String], pipedToArguments: [String]) -> Observable<SystemEvent<Data>> {
|
||||
observable(arguments, environment: [:], pipeTo: pipedToArguments)
|
||||
}
|
||||
|
||||
public func observable(_ arguments: [String], environment _: [String: String],
|
||||
pipeTo _: [String]) -> Observable<SystemEvent<Data>>
|
||||
{
|
||||
Observable.create { observer -> Disposable in
|
||||
public func publisher(_ arguments: [String], pipeTo _: [String]) -> AnyPublisher<SystemEvent<Data>, Error> {
|
||||
AnyPublisher<SystemEvent<Data>, Error>.create { subscriber in
|
||||
let command = arguments.joined(separator: " ")
|
||||
guard let stub = self.stubs[command] else {
|
||||
observer.onError(TuistSupport.SystemError.terminated(command: arguments.first!, code: 1, standardError: Data()))
|
||||
return Disposables.create()
|
||||
subscriber
|
||||
.send(completion: .failure(
|
||||
TuistSupport.SystemError
|
||||
.terminated(command: arguments.first!, code: 1, standardError: Data())
|
||||
))
|
||||
return AnyCancellable {}
|
||||
}
|
||||
guard stub.exitstatus == 0 else {
|
||||
if let error = stub.stderror {
|
||||
observer.onNext(.standardError(error.data(using: .utf8)!))
|
||||
subscriber.send(.standardError(error.data(using: .utf8)!))
|
||||
}
|
||||
observer.onError(TuistSupport.SystemError.terminated(command: arguments.first!, code: 1, standardError: Data()))
|
||||
return Disposables.create()
|
||||
subscriber
|
||||
.send(completion: .failure(
|
||||
TuistSupport.SystemError
|
||||
.terminated(command: arguments.first!, code: 1, standardError: Data())
|
||||
))
|
||||
|
||||
return AnyCancellable {}
|
||||
}
|
||||
if let stdout = stub.stdout {
|
||||
observer.onNext(.standardOutput(stdout.data(using: .utf8)!))
|
||||
subscriber.send(.standardOutput(stdout.data(using: .utf8)!))
|
||||
}
|
||||
observer.onCompleted()
|
||||
return Disposables.create()
|
||||
subscriber.send(completion: .finished)
|
||||
return AnyCancellable {}
|
||||
}
|
||||
}
|
||||
|
||||
public func async(_ arguments: [String]) throws {
|
||||
try async(arguments, verbose: false, environment: [:])
|
||||
}
|
||||
|
||||
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(command: arguments.first!, code: 1, standardError: Data())
|
||||
|
@ -190,7 +144,6 @@ public final class MockSystem: Systeming {
|
|||
}
|
||||
}
|
||||
|
||||
var swiftVersionStub: (() throws -> String)?
|
||||
public func swiftVersion() throws -> String {
|
||||
if let swiftVersionStub = swiftVersionStub {
|
||||
return try swiftVersionStub()
|
||||
|
@ -203,16 +156,7 @@ public final class MockSystem: Systeming {
|
|||
if let path = try whichStub?(name) {
|
||||
return path
|
||||
} else {
|
||||
throw NSError.test()
|
||||
throw TestError("Call to non-stubbed method which")
|
||||
}
|
||||
}
|
||||
|
||||
public func called(_ args: [String]) -> Bool {
|
||||
let command = args.joined(separator: " ")
|
||||
return calls.contains(command)
|
||||
}
|
||||
|
||||
public func called(_ args: String...) -> Bool {
|
||||
called(args)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ final class TargetRunnerTests: TuistUnitTestCase {
|
|||
let path = try temporaryPath()
|
||||
let workspacePath = path.appending(component: "App.xcworkspace")
|
||||
fileHandler.stubExists = { _ in true }
|
||||
system.succeedCommand("/path/to/proj.xcworkspace/Target")
|
||||
system.succeedCommand(["/path/to/proj.xcworkspace/Target"])
|
||||
|
||||
let expectation = self.expectation(description: "locates with default configuration")
|
||||
xcodeProjectBuildDirectoryLocator.locateStub = { _, _, _configuration in
|
||||
|
|
|
@ -211,7 +211,7 @@ final class SimulatorControllerTests: TuistUnitTestCase {
|
|||
let deviceAndRuntime = createSystemStubs(devices: true, runtimes: true)
|
||||
let bundleId = "bundleId"
|
||||
let udid = deviceAndRuntime.device.udid
|
||||
system.succeedCommand("/usr/bin/xcrun", "simctl", "boot", udid)
|
||||
system.succeedCommand(["/usr/bin/xcrun", "simctl", "boot", udid])
|
||||
let openSimAppCommand = ["/usr/bin/open", "-a", "Simulator"]
|
||||
system.succeedCommand(openSimAppCommand)
|
||||
|
||||
|
@ -227,8 +227,8 @@ final class SimulatorControllerTests: TuistUnitTestCase {
|
|||
let deviceAndRuntime = createSystemStubs(devices: true, runtimes: true)
|
||||
let bundleId = "bundleId"
|
||||
let udid = deviceAndRuntime.device.udid
|
||||
system.succeedCommand("/usr/bin/xcrun", "simctl", "boot", udid)
|
||||
system.succeedCommand("/usr/bin/open", "-a", "Simulator")
|
||||
system.succeedCommand(["/usr/bin/xcrun", "simctl", "boot", udid])
|
||||
system.succeedCommand(["/usr/bin/open", "-a", "Simulator"])
|
||||
let launchAppCommand = ["/usr/bin/xcrun", "simctl", "launch", udid, bundleId]
|
||||
system.succeedCommand(launchAppCommand)
|
||||
|
||||
|
@ -245,8 +245,8 @@ final class SimulatorControllerTests: TuistUnitTestCase {
|
|||
let bundleId = "bundleId"
|
||||
let udid = deviceAndRuntime.device.udid
|
||||
let arguments = ["-arg1", "--arg2", "SomeArg"]
|
||||
system.succeedCommand("/usr/bin/xcrun", "simctl", "boot", udid)
|
||||
system.succeedCommand("/usr/bin/open", "-a", "Simulator")
|
||||
system.succeedCommand(["/usr/bin/xcrun", "simctl", "boot", udid])
|
||||
system.succeedCommand(["/usr/bin/open", "-a", "Simulator"])
|
||||
let launchAppCommand = ["/usr/bin/xcrun", "simctl", "launch", udid, bundleId] + arguments
|
||||
system.succeedCommand(launchAppCommand)
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ final class CarthageControllerTests: TuistUnitTestCase {
|
|||
|
||||
func test_carthageVersion_carthageNotFound() {
|
||||
// Given
|
||||
system.errorCommand("/usr/bin/env", "carthage", "version")
|
||||
system.errorCommand(["/usr/bin/env", "carthage", "version"])
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(try subject.carthageVersion(), CarthageControllerError.carthageNotFound)
|
||||
|
|
|
@ -56,7 +56,7 @@ final class CommandRunnerTests: TuistUnitTestCase {
|
|||
arguments = ["tuist", "--help"]
|
||||
|
||||
versionResolver.resolveStub = { _ in ResolvedVersion.bin(temporaryPath) }
|
||||
system.succeedCommand(binaryPath.pathString, "--help", output: "output")
|
||||
system.succeedCommand([binaryPath.pathString, "--help"], output: "output")
|
||||
try subject.run()
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ final class CommandRunnerTests: TuistUnitTestCase {
|
|||
arguments = ["tuist", "--help"]
|
||||
|
||||
versionResolver.resolveStub = { _ in ResolvedVersion.bin(temporaryPath) }
|
||||
system.errorCommand(binaryPath.pathString, "--help", error: "error")
|
||||
system.errorCommand([binaryPath.pathString, "--help"], error: "error")
|
||||
|
||||
try subject.run()
|
||||
XCTAssertTrue(exited == 1)
|
||||
|
@ -86,7 +86,7 @@ final class CommandRunnerTests: TuistUnitTestCase {
|
|||
|
||||
var installArgs: [String] = []
|
||||
installer.installStub = { version in installArgs.append(version) }
|
||||
system.succeedCommand(binaryPath.pathString, "--help", output: "")
|
||||
system.succeedCommand([binaryPath.pathString, "--help"], output: "")
|
||||
|
||||
try subject.run()
|
||||
|
||||
|
@ -125,7 +125,7 @@ final class CommandRunnerTests: TuistUnitTestCase {
|
|||
versionResolver.resolveStub = { _ in ResolvedVersion.versionFile(temporaryPath, "3.2.1")
|
||||
}
|
||||
|
||||
system.errorCommand(binaryPath.pathString, "--help", error: "error")
|
||||
system.errorCommand([binaryPath.pathString, "--help"], error: "error")
|
||||
|
||||
try subject.run()
|
||||
XCTAssertTrue(exited == 1)
|
||||
|
@ -143,7 +143,7 @@ final class CommandRunnerTests: TuistUnitTestCase {
|
|||
$0 == "3.2.1" ? temporaryPath : AbsolutePath("/invalid")
|
||||
}
|
||||
|
||||
system.succeedCommand(binaryPath.pathString, "--help", output: "")
|
||||
system.succeedCommand([binaryPath.pathString, "--help"], output: "")
|
||||
|
||||
try subject.run()
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ final class CommandRunnerTests: TuistUnitTestCase {
|
|||
$0 == "3.2.1" ? temporaryPath : AbsolutePath("/invalid")
|
||||
}
|
||||
|
||||
system.succeedCommand(binaryPath.pathString, "--help", output: "")
|
||||
system.succeedCommand([binaryPath.pathString, "--help"], output: "")
|
||||
|
||||
try subject.run()
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ final class CommandRunnerTests: TuistUnitTestCase {
|
|||
$0 == "3.2.1" ? temporaryPath : AbsolutePath("/invalid")
|
||||
}
|
||||
|
||||
system.errorCommand(binaryPath.pathString, "--help", error: "error")
|
||||
system.errorCommand([binaryPath.pathString, "--help"], error: "error")
|
||||
|
||||
try subject.run()
|
||||
XCTAssertTrue(exited == 1)
|
||||
|
|
|
@ -44,20 +44,20 @@ final class InstallerTests: TuistUnitTestCase {
|
|||
let downloadPath = temporaryDirectory
|
||||
.path
|
||||
.appending(component: Constants.bundleName)
|
||||
system.succeedCommand(
|
||||
system.succeedCommand([
|
||||
"/usr/bin/curl",
|
||||
"-LSs",
|
||||
"--output",
|
||||
downloadPath.pathString,
|
||||
downloadURL.absoluteString
|
||||
)
|
||||
system.succeedCommand(
|
||||
downloadURL.absoluteString,
|
||||
])
|
||||
system.succeedCommand([
|
||||
"/usr/bin/unzip",
|
||||
"-q",
|
||||
downloadPath.pathString,
|
||||
"-d",
|
||||
temporaryPath.pathString
|
||||
)
|
||||
temporaryPath.pathString,
|
||||
])
|
||||
|
||||
try subject.install(
|
||||
version: version,
|
||||
|
@ -86,11 +86,13 @@ final class InstallerTests: TuistUnitTestCase {
|
|||
.path
|
||||
.appending(component: Constants.bundleName)
|
||||
system.errorCommand(
|
||||
"/usr/bin/curl",
|
||||
"-LSs",
|
||||
"--output",
|
||||
downloadPath.pathString,
|
||||
downloadURL.absoluteString,
|
||||
[
|
||||
"/usr/bin/curl",
|
||||
"-LSs",
|
||||
"--output",
|
||||
downloadPath.pathString,
|
||||
downloadURL.absoluteString,
|
||||
],
|
||||
error: "download_error"
|
||||
)
|
||||
|
||||
|
@ -111,18 +113,20 @@ final class InstallerTests: TuistUnitTestCase {
|
|||
let downloadPath = temporaryDirectory
|
||||
.path
|
||||
.appending(component: Constants.bundleName)
|
||||
system.succeedCommand(
|
||||
system.succeedCommand([
|
||||
"/usr/bin/curl",
|
||||
"-LSs",
|
||||
"--output",
|
||||
downloadPath.pathString,
|
||||
downloadURL.absoluteString
|
||||
)
|
||||
downloadURL.absoluteString,
|
||||
])
|
||||
system.errorCommand(
|
||||
"/usr/bin/unzip",
|
||||
downloadPath.pathString,
|
||||
"-d",
|
||||
temporaryPath.pathString,
|
||||
[
|
||||
"/usr/bin/unzip",
|
||||
downloadPath.pathString,
|
||||
"-d",
|
||||
temporaryPath.pathString,
|
||||
],
|
||||
error: "unzip_error"
|
||||
)
|
||||
|
||||
|
|
|
@ -196,12 +196,12 @@ final class TaskServiceTests: TuistUnitTestCase {
|
|||
$0.pathString,
|
||||
]
|
||||
}
|
||||
system.succeedCommand(
|
||||
system.succeedCommand([
|
||||
"load",
|
||||
taskPath.pathString,
|
||||
"--tuist-task",
|
||||
"{\"option-a\":\"Value\"}"
|
||||
)
|
||||
"{\"option-a\":\"Value\"}",
|
||||
])
|
||||
|
||||
// When / Then
|
||||
XCTAssertNoThrow(
|
||||
|
@ -240,12 +240,12 @@ final class TaskServiceTests: TuistUnitTestCase {
|
|||
$0.pathString,
|
||||
]
|
||||
}
|
||||
system.succeedCommand(
|
||||
system.succeedCommand([
|
||||
"load",
|
||||
taskPath.pathString,
|
||||
"--tuist-task",
|
||||
"{}"
|
||||
)
|
||||
"{}",
|
||||
])
|
||||
|
||||
// When / Then
|
||||
XCTAssertNoThrow(
|
||||
|
|
|
@ -60,11 +60,11 @@ final class CodeLinterTests: TuistUnitTestCase {
|
|||
]
|
||||
let fakePath = AbsolutePath("/foo/bar")
|
||||
binaryLocator.stubbedSwiftLintPathResult = "/swiftlint"
|
||||
system.succeedCommand(
|
||||
system.succeedCommand([
|
||||
binaryLocator.stubbedSwiftLintPathResult.pathString,
|
||||
"lint",
|
||||
"--use-script-input-files"
|
||||
)
|
||||
"--use-script-input-files",
|
||||
])
|
||||
|
||||
// When
|
||||
try await subject.lint(sources: fakeSources, path: fakePath, strict: false)
|
||||
|
@ -79,12 +79,12 @@ final class CodeLinterTests: TuistUnitTestCase {
|
|||
]
|
||||
let fakePath = AbsolutePath("/foo/bar")
|
||||
binaryLocator.stubbedSwiftLintPathResult = "/swiftlint"
|
||||
system.succeedCommand(
|
||||
system.succeedCommand([
|
||||
binaryLocator.stubbedSwiftLintPathResult.pathString,
|
||||
"lint",
|
||||
"--use-script-input-files",
|
||||
"--strict"
|
||||
)
|
||||
"--strict",
|
||||
])
|
||||
|
||||
// When
|
||||
try await subject.lint(sources: fakeSources, path: fakePath, strict: true)
|
||||
|
@ -112,13 +112,13 @@ final class CodeLinterTests: TuistUnitTestCase {
|
|||
"SCRIPT_INPUT_FILE_1": fakeSources[1].pathString,
|
||||
"SCRIPT_INPUT_FILE_2": fakeSources[2].pathString,
|
||||
]
|
||||
system.succeedCommand(
|
||||
system.succeedCommand([
|
||||
binaryLocator.stubbedSwiftLintPathResult.pathString,
|
||||
"lint",
|
||||
"--use-script-input-files",
|
||||
"--config",
|
||||
swiftLintConfigPath.pathString
|
||||
)
|
||||
swiftLintConfigPath.pathString,
|
||||
])
|
||||
|
||||
// When
|
||||
try await subject.lint(sources: fakeSources, path: fakePath, strict: false)
|
||||
|
@ -139,13 +139,13 @@ final class CodeLinterTests: TuistUnitTestCase {
|
|||
rootDirectoryLocator.locateStub = fakeRoot
|
||||
binaryLocator.stubbedSwiftLintPathResult = fakeSwiftLintPath
|
||||
fileHandler.stubExists = { $0 == swiftLintConfigPath }
|
||||
system.succeedCommand(
|
||||
system.succeedCommand([
|
||||
binaryLocator.stubbedSwiftLintPathResult.pathString,
|
||||
"lint",
|
||||
"--use-script-input-files",
|
||||
"--config",
|
||||
swiftLintConfigPath.pathString
|
||||
)
|
||||
swiftLintConfigPath.pathString,
|
||||
])
|
||||
|
||||
// When
|
||||
try await subject.lint(sources: fakeSources, path: fakePath, strict: false)
|
||||
|
|
|
@ -23,12 +23,12 @@ final class CertificateParserTests: TuistUnitTestCase {
|
|||
let privateKey = try temporaryPath()
|
||||
let subjectOutput = "subject= /UID=VD55TKL3V6/OU=QH95ER52SG/O=Name/C=US\n"
|
||||
system.succeedCommand(
|
||||
"openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject",
|
||||
["openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject"],
|
||||
output: subjectOutput
|
||||
)
|
||||
let fingerprintOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US\n"
|
||||
system.succeedCommand(
|
||||
"openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint",
|
||||
["openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint"],
|
||||
output: fingerprintOutput
|
||||
)
|
||||
|
||||
|
@ -45,12 +45,12 @@ final class CertificateParserTests: TuistUnitTestCase {
|
|||
let privateKey = try temporaryPath()
|
||||
let subjectOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/O=Name/C=US\n"
|
||||
system.succeedCommand(
|
||||
"openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject",
|
||||
["openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject"],
|
||||
output: subjectOutput
|
||||
)
|
||||
let fingerprintOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US\n"
|
||||
system.succeedCommand(
|
||||
"openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint",
|
||||
["openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint"],
|
||||
output: fingerprintOutput
|
||||
)
|
||||
|
||||
|
@ -67,12 +67,12 @@ final class CertificateParserTests: TuistUnitTestCase {
|
|||
let privateKey = try temporaryPath()
|
||||
let subjectOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US\n"
|
||||
system.succeedCommand(
|
||||
"openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject",
|
||||
["openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject"],
|
||||
output: subjectOutput
|
||||
)
|
||||
let fingerprintOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US\n"
|
||||
system.succeedCommand(
|
||||
"openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint",
|
||||
["openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint"],
|
||||
output: fingerprintOutput
|
||||
)
|
||||
let expectedCertificate = Certificate(
|
||||
|
@ -98,12 +98,12 @@ final class CertificateParserTests: TuistUnitTestCase {
|
|||
let subjectOutput =
|
||||
"subject=UID = VD55TKL3V6, CN = Apple Development: Name (54GSF6G47V), OU = QH95ER52SG, O = Name, C = US"
|
||||
system.succeedCommand(
|
||||
"openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject",
|
||||
["openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-subject"],
|
||||
output: subjectOutput
|
||||
)
|
||||
let fingerprintOutput = "subject= /UID=VD55TKL3V6/CN=Apple Development: Name (54GSF6G47V)/OU=QH95ER52SG/O=Name/C=US\n"
|
||||
system.succeedCommand(
|
||||
"openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint",
|
||||
["openssl", "x509", "-inform", "der", "-in", publicKey.pathString, "-noout", "-fingerprint"],
|
||||
output: fingerprintOutput
|
||||
)
|
||||
let expectedCertificate = Certificate(
|
||||
|
|
|
@ -25,7 +25,7 @@ final class SecurityControllerTests: TuistUnitTestCase {
|
|||
let decodeFilePath = try temporaryPath()
|
||||
|
||||
let expectedOutput = "output"
|
||||
system.succeedCommand("/usr/bin/security", "cms", "-D", "-i", decodeFilePath.pathString, output: expectedOutput)
|
||||
system.succeedCommand(["/usr/bin/security", "cms", "-D", "-i", decodeFilePath.pathString], output: expectedOutput)
|
||||
|
||||
// When
|
||||
let output = try subject.decodeFile(at: decodeFilePath)
|
||||
|
@ -41,17 +41,17 @@ final class SecurityControllerTests: TuistUnitTestCase {
|
|||
let certificate = Certificate.test(publicKey: certificatePath, privateKey: privateKeyPath)
|
||||
let keychainPath = try temporaryPath()
|
||||
|
||||
system.errorCommand(
|
||||
system.errorCommand([
|
||||
"/usr/bin/security",
|
||||
"find-certificate",
|
||||
certificatePath.pathString,
|
||||
"-P",
|
||||
"",
|
||||
"-k",
|
||||
keychainPath.pathString
|
||||
)
|
||||
system.errorCommand("/usr/bin/security", "find-key", privateKeyPath.pathString, "-P", "", "-k", keychainPath.pathString)
|
||||
system.succeedCommand(
|
||||
keychainPath.pathString,
|
||||
])
|
||||
system.errorCommand(["/usr/bin/security", "find-key", privateKeyPath.pathString, "-P", "", "-k", keychainPath.pathString])
|
||||
system.succeedCommand([
|
||||
"/usr/bin/security",
|
||||
"import",
|
||||
certificatePath.pathString,
|
||||
|
@ -62,9 +62,9 @@ final class SecurityControllerTests: TuistUnitTestCase {
|
|||
"-T",
|
||||
"/usr/bin/security",
|
||||
"-k",
|
||||
keychainPath.pathString
|
||||
)
|
||||
system.succeedCommand(
|
||||
keychainPath.pathString,
|
||||
])
|
||||
system.succeedCommand([
|
||||
"/usr/bin/security",
|
||||
"import",
|
||||
privateKeyPath.pathString,
|
||||
|
@ -75,8 +75,8 @@ final class SecurityControllerTests: TuistUnitTestCase {
|
|||
"-T",
|
||||
"/usr/bin/security",
|
||||
"-k",
|
||||
keychainPath.pathString
|
||||
)
|
||||
keychainPath.pathString,
|
||||
])
|
||||
|
||||
// When
|
||||
try subject.importCertificate(certificate, keychainPath: keychainPath)
|
||||
|
@ -94,12 +94,14 @@ final class SecurityControllerTests: TuistUnitTestCase {
|
|||
let keychainPath = try temporaryPath()
|
||||
|
||||
system.succeedCommand(
|
||||
"/usr/bin/security",
|
||||
"find-certificate",
|
||||
"-c",
|
||||
certificate.name,
|
||||
"-a",
|
||||
keychainPath.pathString,
|
||||
[
|
||||
"/usr/bin/security",
|
||||
"find-certificate",
|
||||
"-c",
|
||||
certificate.name,
|
||||
"-a",
|
||||
keychainPath.pathString,
|
||||
],
|
||||
output: "Some output"
|
||||
)
|
||||
|
||||
|
@ -117,7 +119,7 @@ final class SecurityControllerTests: TuistUnitTestCase {
|
|||
// Given
|
||||
let keychainPath = try temporaryPath()
|
||||
let password = ""
|
||||
system.succeedCommand("/usr/bin/security", "create-keychain", "-p", password, keychainPath.pathString)
|
||||
system.succeedCommand(["/usr/bin/security", "create-keychain", "-p", password, keychainPath.pathString])
|
||||
|
||||
// When
|
||||
try subject.createKeychain(at: keychainPath, password: password)
|
||||
|
@ -130,7 +132,7 @@ final class SecurityControllerTests: TuistUnitTestCase {
|
|||
// Given
|
||||
let keychainPath = try temporaryPath()
|
||||
let password = ""
|
||||
system.succeedCommand("/usr/bin/security", "unlock-keychain", "-p", password, keychainPath.pathString)
|
||||
system.succeedCommand(["/usr/bin/security", "unlock-keychain", "-p", password, keychainPath.pathString])
|
||||
|
||||
// When
|
||||
try subject.unlockKeychain(at: keychainPath, password: password)
|
||||
|
@ -143,7 +145,7 @@ final class SecurityControllerTests: TuistUnitTestCase {
|
|||
// Given
|
||||
let keychainPath = try temporaryPath()
|
||||
let password = ""
|
||||
system.succeedCommand("/usr/bin/security", "lock-keychain", "-p", password, keychainPath.pathString)
|
||||
system.succeedCommand(["/usr/bin/security", "lock-keychain", "-p", password, keychainPath.pathString])
|
||||
|
||||
// When
|
||||
try subject.lockKeychain(at: keychainPath, password: password)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import RxSwift
|
||||
import TSCBasic
|
||||
import XCTest
|
||||
@testable import TuistSupport
|
||||
|
@ -29,25 +28,25 @@ final class SystemIntegrationTests: TuistTestCase {
|
|||
XCTAssertThrowsError(try subject.run(["ls", "abcdefghi"]))
|
||||
}
|
||||
|
||||
func test_observable() async throws {
|
||||
func test_observable() throws {
|
||||
// Given
|
||||
let observable = subject.observable(["echo", "hola"]).mapToString()
|
||||
let publisher = subject.publisher(["echo", "hola"]).mapToString()
|
||||
|
||||
// When
|
||||
let elements = try await observable.toArray().value
|
||||
let elements = try publisher.toBlocking()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(elements.count, 1)
|
||||
XCTAssertTrue(elements.first?.value.spm_chomp() == "hola")
|
||||
}
|
||||
|
||||
func test_observable_when_it_errors() async throws {
|
||||
func test_observable_when_it_errors() throws {
|
||||
// Given
|
||||
let observable = subject.observable(["/usr/bin/xcrun", "invalid"]).mapToString()
|
||||
let publisher = subject.publisher(["/usr/bin/xcrun", "invalid"]).mapToString()
|
||||
|
||||
do {
|
||||
// When
|
||||
_ = try await observable.toArray().value
|
||||
_ = try publisher.toBlocking()
|
||||
XCTFail("expected command to fail but it did not")
|
||||
} catch {
|
||||
// Then
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
import Foundation
|
||||
import RxSwift
|
||||
import XCTest
|
||||
@testable import TuistSupport
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class ObservableSystemTests: TuistUnitTestCase {
|
||||
func test_mapToString() {
|
||||
// Given
|
||||
let subject = PublishSubject<SystemEvent<Data>>()
|
||||
var got: [SystemEvent<String>] = []
|
||||
|
||||
// When
|
||||
_ = subject.asObservable().mapToString().subscribe(onNext: {
|
||||
got.append($0)
|
||||
})
|
||||
subject.on(.next(.standardOutput("a".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardOutput("b".data(using: .utf8)!)))
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got.count, 2)
|
||||
XCTAssertEqual(got.first, .standardOutput("a"))
|
||||
XCTAssertEqual(got.last, .standardOutput("b"))
|
||||
}
|
||||
|
||||
func test_collectAndMergeOutput() {
|
||||
// Given
|
||||
let subject = PublishSubject<SystemEvent<Data>>()
|
||||
var got: String = ""
|
||||
|
||||
// When
|
||||
_ = subject.asObservable()
|
||||
.mapToString()
|
||||
.collectAndMergeOutput()
|
||||
.subscribe(onNext: {
|
||||
got.append($0)
|
||||
})
|
||||
subject.on(.next(.standardOutput("a\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardOutput("b\n".data(using: .utf8)!)))
|
||||
subject.on(.completed)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, "a\nb\n")
|
||||
}
|
||||
|
||||
func test_collectOutput() {
|
||||
// Given
|
||||
let subject = PublishSubject<SystemEvent<Data>>()
|
||||
var got: SystemCollectedOutput?
|
||||
|
||||
// When
|
||||
_ = subject.asObservable()
|
||||
.mapToString()
|
||||
.collectOutput()
|
||||
.subscribe(onNext: {
|
||||
got = $0
|
||||
})
|
||||
subject.on(.next(.standardOutput("a\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardOutput("b\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardError("c\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardError("d\n".data(using: .utf8)!)))
|
||||
subject.on(.completed)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got?.standardOutput, "a\nb\n")
|
||||
XCTAssertEqual(got?.standardError, "c\nd\n")
|
||||
}
|
||||
|
||||
func test_filterStandardOutput() {
|
||||
// Given
|
||||
let subject = PublishSubject<SystemEvent<Data>>()
|
||||
var got: [SystemEvent<String>] = []
|
||||
|
||||
// When
|
||||
_ = subject.asObservable()
|
||||
.mapToString()
|
||||
.filterStandardOutput { $0.contains("a") }
|
||||
.subscribe(onNext: {
|
||||
got.append($0)
|
||||
})
|
||||
subject.on(.next(.standardOutput("a\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardOutput("b\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardError("d\n".data(using: .utf8)!)))
|
||||
subject.on(.completed)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got.count, 2)
|
||||
XCTAssertEqual(got.first, .standardOutput("a\n"))
|
||||
XCTAssertEqual(got.last, .standardError("d\n"))
|
||||
}
|
||||
|
||||
func test_rejectStandardOutput() {
|
||||
// Given
|
||||
let subject = PublishSubject<SystemEvent<Data>>()
|
||||
var got: [SystemEvent<String>] = []
|
||||
|
||||
// When
|
||||
_ = subject.asObservable()
|
||||
.mapToString()
|
||||
.rejectStandardOutput { $0.contains("a") }
|
||||
.subscribe(onNext: {
|
||||
got.append($0)
|
||||
})
|
||||
subject.on(.next(.standardOutput("a\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardOutput("b\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardError("d\n".data(using: .utf8)!)))
|
||||
subject.on(.completed)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got.count, 2)
|
||||
XCTAssertEqual(got.first, .standardOutput("b\n"))
|
||||
XCTAssertEqual(got.last, .standardError("d\n"))
|
||||
}
|
||||
|
||||
func test_filterStandardError() {
|
||||
// Given
|
||||
let subject = PublishSubject<SystemEvent<Data>>()
|
||||
var got: [SystemEvent<String>] = []
|
||||
|
||||
// When
|
||||
_ = subject.asObservable()
|
||||
.mapToString()
|
||||
.filterStandardError { $0.contains("c") }
|
||||
.subscribe(onNext: {
|
||||
got.append($0)
|
||||
})
|
||||
subject.on(.next(.standardOutput("a\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardError("c\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardError("d\n".data(using: .utf8)!)))
|
||||
subject.on(.completed)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got.count, 2)
|
||||
XCTAssertEqual(got.first, .standardOutput("a\n"))
|
||||
XCTAssertEqual(got.last, .standardError("c\n"))
|
||||
}
|
||||
|
||||
func test_rejectStandardError() {
|
||||
// Given
|
||||
let subject = PublishSubject<SystemEvent<Data>>()
|
||||
var got: [SystemEvent<String>] = []
|
||||
|
||||
// When
|
||||
_ = subject.asObservable()
|
||||
.mapToString()
|
||||
.rejectStandardError { $0.contains("c") }
|
||||
.subscribe(onNext: {
|
||||
got.append($0)
|
||||
})
|
||||
subject.on(.next(.standardOutput("a\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardError("c\n".data(using: .utf8)!)))
|
||||
subject.on(.next(.standardError("d\n".data(using: .utf8)!)))
|
||||
subject.on(.completed)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got.count, 2)
|
||||
XCTAssertEqual(got.first, .standardOutput("a\n"))
|
||||
XCTAssertEqual(got.last, .standardError("d\n"))
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ final class GitHandlerTests: TuistUnitTestCase {
|
|||
system.stubs["git -C \(path.pathString) clone \(url)"] = (stderror: nil, stdout: nil, exitstatus: 0)
|
||||
|
||||
XCTAssertNoThrow(try subject.clone(url: url, into: path))
|
||||
XCTAssertTrue(system.called("git", "-C", path.pathString, "clone", url))
|
||||
XCTAssertTrue(system.called(["git", "-C", path.pathString, "clone", url]))
|
||||
}
|
||||
|
||||
func test_cloneTo() throws {
|
||||
|
@ -33,7 +33,7 @@ final class GitHandlerTests: TuistUnitTestCase {
|
|||
system.stubs["git clone \(url)"] = (stderror: nil, stdout: nil, exitstatus: 0)
|
||||
|
||||
XCTAssertNoThrow(try subject.clone(url: url))
|
||||
XCTAssertTrue(system.called("git", "clone", url))
|
||||
XCTAssertTrue(system.called(["git", "clone", url]))
|
||||
}
|
||||
|
||||
func test_cloneTo_WITH_path() throws {
|
||||
|
@ -43,7 +43,7 @@ final class GitHandlerTests: TuistUnitTestCase {
|
|||
system.stubs["git clone \(url) \(path.pathString)"] = (stderror: nil, stdout: nil, exitstatus: 0)
|
||||
|
||||
XCTAssertNoThrow(try subject.clone(url: url, to: path))
|
||||
XCTAssertTrue(system.called("git", "clone", url, path.pathString))
|
||||
XCTAssertTrue(system.called(["git", "clone", url, path.pathString]))
|
||||
}
|
||||
|
||||
func test_checkout() throws {
|
||||
|
@ -71,15 +71,15 @@ final class GitHandlerTests: TuistUnitTestCase {
|
|||
system.stubs[expectedCommand] = (stderror: nil, stdout: nil, exitstatus: 0)
|
||||
|
||||
XCTAssertNoThrow(try subject.checkout(id: id, in: path))
|
||||
XCTAssertTrue(system.called(
|
||||
XCTAssertTrue(system.called([
|
||||
"git",
|
||||
"--git-dir",
|
||||
path.appending(component: ".git").pathString,
|
||||
"--work-tree",
|
||||
path.pathString,
|
||||
"checkout",
|
||||
id
|
||||
))
|
||||
id,
|
||||
]))
|
||||
}
|
||||
|
||||
func test_parsed_versions() throws {
|
||||
|
|
|
@ -41,7 +41,7 @@ final class OpenerTests: TuistUnitTestCase {
|
|||
let temporaryPath = try temporaryPath()
|
||||
let path = temporaryPath.appending(component: "tool")
|
||||
try FileHandler.shared.touch(path)
|
||||
system.succeedCommand("/usr/bin/open", path.pathString)
|
||||
system.succeedCommand(["/usr/bin/open", path.pathString])
|
||||
try subject.open(path: path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ let dependencies = Dependencies(
|
|||
.package(url: "https://github.com/tuist/XcodeProj.git", .upToNextMajor(from: "8.5.0")),
|
||||
.package(url: "https://github.com/CombineCommunity/CombineExt.git", .upToNextMajor(from: "1.3.0")),
|
||||
.package(url: "https://github.com/apple/swift-tools-support-core.git", .upToNextMinor(from: "0.2.0")),
|
||||
.package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.5.0")),
|
||||
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")),
|
||||
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMajor(from: "4.2.2")),
|
||||
.package(url: "https://github.com/fortmarek/swifter.git", .branch("stable")),
|
||||
|
|
|
@ -127,15 +127,6 @@
|
|||
"version": "2.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "RxSwift",
|
||||
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b4307ba0b6425c0ba4178e138799946c3da594f8",
|
||||
"version": "6.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "ShellOut",
|
||||
"repositoryURL": "https://github.com/JohnSundell/ShellOut.git",
|
||||
|
|
Loading…
Reference in New Issue