6
6
7
7
import Foundation
8
8
import Dispatch
9
+ import Logging
10
+ import TSCBasic
11
+ import Algorithms
9
12
10
13
// MARK: - API
11
14
@@ -32,18 +35,23 @@ import Dispatch
32
35
to command: SafeString ,
33
36
arguments: [ Argument ] = [ ] ,
34
37
at path: String = " . " ,
35
- process : Process = . init ( ) ,
38
+ logger : Logger ? = nil ,
36
39
outputHandle: FileHandle ? = nil ,
37
40
errorHandle: FileHandle ? = nil ,
38
41
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: " " ) ) "
41
44
42
- return try process . launchBash (
45
+ return try await TSCBasic . Process . launchBash (
43
46
with: command,
47
+ logger: logger,
44
48
outputHandle: outputHandle,
45
49
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) )
47
55
)
48
56
}
49
57
@@ -68,16 +76,16 @@ import Dispatch
68
76
@discardableResult public func shellOut(
69
77
to command: ShellOutCommand ,
70
78
at path: String = " . " ,
71
- process : Process = . init ( ) ,
79
+ logger : Logger ? = nil ,
72
80
outputHandle: FileHandle ? = nil ,
73
81
errorHandle: FileHandle ? = nil ,
74
82
environment: [ String : String ] ? = nil
75
- ) throws -> String {
76
- try shellOut (
83
+ ) async throws -> ( stdout : String , stderr : String ) {
84
+ try await shellOut (
77
85
to: command. command,
78
86
arguments: command. arguments,
79
87
at: path,
80
- process : process ,
88
+ logger : logger ,
81
89
outputHandle: outputHandle,
82
90
errorHandle: errorHandle,
83
91
environment: environment
@@ -390,69 +398,49 @@ extension ShellOutCommand {
390
398
391
399
// MARK: - Private
392
400
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
452
433
}
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
+ )
455
439
}
440
+ return try (
441
+ stdout: String ( result. utf8Output ( ) . trimmingSuffix ( while: \. isNewline) ) ,
442
+ stderr: String ( result. utf8stderrOutput ( ) . trimmingSuffix ( while: \. isNewline) )
443
+ )
456
444
}
457
445
}
458
446
@@ -468,25 +456,3 @@ private extension Data {
468
456
469
457
}
470
458
}
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