Replace RxSwift for XcodeBuildController and System (#4029)

This commit is contained in:
Alfredo Delli Bovi 2022-01-22 23:16:45 +01:00 committed by GitHub
parent d06735adcb
commit 0ecea33054
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 479 additions and 1172 deletions

View File

@ -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",

View File

@ -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(

View File

@ -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"),

View File

@ -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()
}
}
}
}

View File

@ -1,5 +1,4 @@
import Foundation
import RxSwift
import TSCBasic
import TuistCore
import TuistGraph

View File

@ -1,5 +1,4 @@
import Foundation
import RxSwift
import TSCBasic
import TuistSupport

View File

@ -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
}

View File

@ -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])
}
}

View File

@ -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])
}
}
}

View File

@ -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")
})

View File

@ -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):

View File

@ -137,7 +137,6 @@ final class CacheController: CacheControlling {
)
}
// swiftlint:disable:next function_body_length
private func archive(
_ graph: Graph,
projectPath: AbsolutePath,

View File

@ -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

View File

@ -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)")

View File

@ -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,
])
}
}

View File

@ -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!

View File

@ -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
}
}
}
}

View File

@ -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()
}
}
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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!
}

View File

@ -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: "=")

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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"
)

View File

@ -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(

View File

@ -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)

View File

@ -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(

View File

@ -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)

View File

@ -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

View File

@ -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"))
}
}

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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")),

View File

@ -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",