Skip to content

Commit 5638f21

Browse files
Merge pull request #5 from SwiftPackageIndex/async-with-tsc-version
Async with tsc version
2 parents 0e3009d + 27af7ad commit 5638f21

File tree

7 files changed

+237
-157
lines changed

7 files changed

+237
-157
lines changed

.devcontainer/devcontainer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,5 @@
1414
}
1515
}
1616
}
17-
},
18-
"forwardPorts": [8080]
17+
}
1918
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/Packages
44
/*.xcodeproj
55
.swiftpm
6+
.vscode/

Package.resolved

Lines changed: 56 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:4.2
1+
// swift-tools-version:5.8
22

33
/**
44
* ShellOut
@@ -10,23 +10,31 @@ import PackageDescription
1010

1111
let package = Package(
1212
name: "ShellOut",
13+
platforms: [.macOS("10.15.4")],
1314
products: [
1415
.library(name: "ShellOut", targets: ["ShellOut"])
1516
],
1617
dependencies: [
1718
.package(url: "https://github.com/SwiftPackageIndex/ShellQuote", from: "1.0.2"),
19+
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
20+
.package(url: "https://github.com/apple/swift-tools-support-core.git", from: "0.5.2"),
21+
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),
1822
],
1923
targets: [
2024
.target(
2125
name: "ShellOut",
2226
dependencies: [
23-
.product(name: "ShellQuote", package: "ShellQuote")
27+
.product(name: "ShellQuote", package: "ShellQuote"),
28+
.product(name: "Logging", package: "swift-log"),
29+
.product(name: "Algorithms", package: "swift-algorithms"),
30+
.product(name: "TSCBasic", package: "swift-tools-support-core"),
2431
],
2532
path: "Sources"
2633
),
2734
.testTarget(
2835
name: "ShellOutTests",
29-
dependencies: ["ShellOut"]
36+
dependencies: ["ShellOut"],
37+
exclude: ["Fixtures"]
3038
)
3139
]
3240
)

Sources/ShellOut.swift

Lines changed: 58 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
import Foundation
88
import 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-
}
87 KB
Binary file not shown.

0 commit comments

Comments
 (0)