66
77import Foundation
88import Dispatch
9+ import Logging
10+ import TSCBasic
11+ import Algorithms
912
1013// MARK: - API
1114
@@ -32,18 +35,23 @@ import Dispatch
3235 to command: SafeString ,
3336 arguments: [ Argument ] = [ ] ,
3437 at path: String = " . " ,
35- process : Process = . init ( ) ,
38+ logger : Logger ? = nil ,
3639 outputHandle: FileHandle ? = nil ,
3740 errorHandle: FileHandle ? = nil ,
3841 environment: [ String : String ] ? = nil
39- ) throws -> String {
40- let command = " cd \( path . escapingSpaces ) && \( command) \( arguments. map ( \. string) . joined ( separator: " " ) ) "
42+ ) async throws -> ( stdout : String , stderr : String ) {
43+ let command = " \( command) \( arguments. map ( \. string) . joined ( separator: " " ) ) "
4144
42- return try process . launchBash (
45+ return try await TSCBasic . Process . launchBash (
4346 with: command,
47+ logger: logger,
4448 outputHandle: outputHandle,
4549 errorHandle: errorHandle,
46- environment: environment
50+ environment: environment,
51+ at: path == " . " ? nil :
52+ ( path == " ~ " ? TSCBasic . localFileSystem. homeDirectory. pathString :
53+ ( path. starts ( with: " ~/ " ) ? " \( TSCBasic . localFileSystem. homeDirectory. pathString) / \( path. dropFirst ( 2 ) ) " :
54+ path) )
4755 )
4856}
4957
@@ -68,16 +76,16 @@ import Dispatch
6876@discardableResult public func shellOut(
6977 to command: ShellOutCommand ,
7078 at path: String = " . " ,
71- process : Process = . init ( ) ,
79+ logger : Logger ? = nil ,
7280 outputHandle: FileHandle ? = nil ,
7381 errorHandle: FileHandle ? = nil ,
7482 environment: [ String : String ] ? = nil
75- ) throws -> String {
76- try shellOut (
83+ ) async throws -> ( stdout : String , stderr : String ) {
84+ try await shellOut (
7785 to: command. command,
7886 arguments: command. arguments,
7987 at: path,
80- process : process ,
88+ logger : logger ,
8189 outputHandle: outputHandle,
8290 errorHandle: errorHandle,
8391 environment: environment
@@ -390,69 +398,49 @@ extension ShellOutCommand {
390398
391399// MARK: - Private
392400
393- private extension Process {
394- @discardableResult func launchBash( with command: String , outputHandle: FileHandle ? = nil , errorHandle: FileHandle ? = nil , environment: [ String : String ] ? = nil ) throws -> String {
395- executableURL = URL ( fileURLWithPath: " /bin/bash " )
396- arguments = [ " -c " , command]
397-
398- if let environment = environment {
399- self . environment = environment
400- }
401-
402- // Because FileHandle's readabilityHandler might be called from a
403- // different queue from the calling queue, avoid a data race by
404- // protecting reads and writes to outputData and errorData on
405- // a single dispatch queue.
406- let outputQueue = DispatchQueue ( label: " bash-output-queue " )
407-
408- var outputData = Data ( )
409- var errorData = Data ( )
410-
411- let outputPipe = Pipe ( )
412- standardOutput = outputPipe
413-
414- let errorPipe = Pipe ( )
415- standardError = errorPipe
416-
417- outputPipe. fileHandleForReading. readabilityHandler = { handler in
418- let data = handler. availableData
419- outputQueue. async {
420- outputData. append ( data)
421- outputHandle? . write ( data)
422- }
423- }
424-
425- errorPipe. fileHandleForReading. readabilityHandler = { handler in
426- let data = handler. availableData
427- outputQueue. async {
428- errorData. append ( data)
429- errorHandle? . write ( data)
430- }
431- }
432-
433- try run ( )
434-
435- waitUntilExit ( )
436-
437- outputHandle? . closeFile ( )
438- errorHandle? . closeFile ( )
439-
440- outputPipe. fileHandleForReading. readabilityHandler = nil
441- errorPipe. fileHandleForReading. readabilityHandler = nil
442-
443- // Block until all writes have occurred to outputData and errorData,
444- // and then read the data back out.
445- return try outputQueue. sync {
446- if terminationStatus != 0 {
447- throw ShellOutError (
448- terminationStatus: terminationStatus,
449- errorData: errorData,
450- outputData: outputData
451- )
401+ private extension TSCBasic . Process {
402+ @discardableResult static func launchBash(
403+ with command: String ,
404+ logger: Logger ? = nil ,
405+ outputHandle: FileHandle ? = nil ,
406+ errorHandle: FileHandle ? = nil ,
407+ environment: [ String : String ] ? = nil ,
408+ at: String ? = nil
409+ ) async throws -> ( stdout: String , stderr: String ) {
410+ let process = try Self . init (
411+ arguments: [ " /bin/bash " , " -c " , command] ,
412+ environment: environment ?? ProcessEnv . vars,
413+ workingDirectory: at. map { try . init( validating: $0) } ?? TSCBasic . localFileSystem. currentWorkingDirectory ?? . root,
414+ outputRedirection: . collect( redirectStderr: false ) ,
415+ startNewProcessGroup: false ,
416+ loggingHandler: nil
417+ )
418+
419+ try process. launch ( )
420+
421+ let result = try await process. waitUntilExit ( )
422+
423+ try outputHandle? . write ( contentsOf: ( try ? result. output. get ( ) ) ?? [ ] )
424+ try outputHandle? . close ( )
425+ try errorHandle? . write ( contentsOf: ( try ? result. stderrOutput. get ( ) ) ?? [ ] )
426+ try errorHandle? . close ( )
427+
428+ guard case . terminated( code: let code) = result. exitStatus, code == 0 else {
429+ let code : Int32
430+ switch result. exitStatus {
431+ case . terminated( code: let termCode) : code = termCode
432+ case . signalled( signal: let sigNo) : code = - sigNo
452433 }
453-
454- return outputData. shellOutput ( )
434+ throw ShellOutError (
435+ terminationStatus: code,
436+ errorData: Data ( ( try ? result. stderrOutput. get ( ) ) ?? [ ] ) ,
437+ outputData: Data ( ( try ? result. output. get ( ) ) ?? [ ] )
438+ )
455439 }
440+ return try (
441+ stdout: String ( result. utf8Output ( ) . trimmingSuffix ( while: \. isNewline) ) ,
442+ stderr: String ( result. utf8stderrOutput ( ) . trimmingSuffix ( while: \. isNewline) )
443+ )
456444 }
457445}
458446
@@ -468,25 +456,3 @@ private extension Data {
468456
469457 }
470458}
471-
472- private extension String {
473- var escapingSpaces : String {
474- return replacingOccurrences ( of: " " , with: " \\ " )
475- }
476-
477- func appending( argument: String ) -> String {
478- return " \( self ) \" \( argument) \" "
479- }
480-
481- func appending( arguments: [ String ] ) -> String {
482- return appending ( argument: arguments. joined ( separator: " \" \" " ) )
483- }
484-
485- mutating func append( argument: String ) {
486- self = appending ( argument: argument)
487- }
488-
489- mutating func append( arguments: [ String ] ) {
490- self = appending ( arguments: arguments)
491- }
492- }
0 commit comments