Skip to content

Commit 27ea36b

Browse files
committed
Remove Platform.run methods in favour of more direct Subprocess usages
1 parent 88c3fa9 commit 27ea36b

File tree

16 files changed

+577
-644
lines changed

16 files changed

+577
-644
lines changed

Sources/LinuxPlatform/Linux.swift

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import Subprocess
23
import SwiftlyCore
34
import SystemPackage
45

@@ -255,7 +256,13 @@ public struct Linux: Platform {
255256
}
256257

257258
if requireSignatureValidation {
258-
guard (try? await self.runProgram("gpg", "--version", quiet: true)) != nil else {
259+
let result = try await run(
260+
.name("gpg"),
261+
arguments: ["--version"],
262+
output: .discarded
263+
)
264+
265+
if !result.terminationStatus.isSuccess {
259266
var msg = "gpg is not installed. "
260267
if let manager {
261268
msg += """
@@ -277,9 +284,9 @@ public struct Linux: Platform {
277284
if let mockedHomeDir = ctx.mockedHomeDir {
278285
var env = ProcessInfo.processInfo.environment
279286
env["GNUPGHOME"] = (mockedHomeDir / ".gnupg").string
280-
try await sys.gpg()._import(key: tmpFile).run(self, env: env, quiet: true)
287+
try await sys.gpg()._import(key: tmpFile).run(environment: .inherit.updating(["GNUPGHOME": (mockedHomeDir / ".gnupg").string]), quiet: true)
281288
} else {
282-
try await sys.gpg()._import(key: tmpFile).run(self, quiet: true)
289+
try await sys.gpg()._import(key: tmpFile).run(quiet: true)
283290
}
284291
}
285292
}
@@ -307,7 +314,12 @@ public struct Linux: Platform {
307314
do {
308315
switch manager {
309316
case "apt-get":
310-
if let pkgList = try await self.runProgramOutput("dpkg", "-l", package) {
317+
let result = try await run(.name("dpkg"), arguments: ["-l", package], output: .string(limit: 100 * 1024))
318+
if !result.terminationStatus.isSuccess {
319+
return false
320+
}
321+
322+
if let pkgList = result.standardOutput {
311323
// The package might be listed but not in an installed non-error state.
312324
//
313325
// Look for something like this:
@@ -321,8 +333,8 @@ public struct Linux: Platform {
321333
}
322334
return false
323335
case "yum":
324-
try await self.runProgram("yum", "list", "installed", package, quiet: true)
325-
return true
336+
let result = try await run(.name("yum"), arguments: ["list", "installed", package], output: .discarded)
337+
return result.terminationStatus.isSuccess
326338
default:
327339
return true
328340
}
@@ -382,7 +394,15 @@ public struct Linux: Platform {
382394
tmpDir / String(name)
383395
}
384396

385-
try await self.runProgram((tmpDir / "swiftly").string, "init")
397+
let config = Configuration(
398+
executable: .path(tmpDir / "swiftly"),
399+
arguments: ["init"]
400+
)
401+
402+
let result = try await run(config, output: .standardOutput, error: .standardError)
403+
if !result.terminationStatus.isSuccess {
404+
throw RunProgramError(terminationStatus: result.terminationStatus, config: config)
405+
}
386406
}
387407
}
388408

@@ -416,11 +436,9 @@ public struct Linux: Platform {
416436
await ctx.message("Verifying toolchain signature...")
417437
do {
418438
if let mockedHomeDir = ctx.mockedHomeDir {
419-
var env = ProcessInfo.processInfo.environment
420-
env["GNUPGHOME"] = (mockedHomeDir / ".gnupg").string
421-
try await sys.gpg().verify(detached_signature: sigFile, signed_data: archive).run(self, env: env, quiet: false)
439+
try await sys.gpg().verify(detached_signature: sigFile, signed_data: archive).run(environment: .inherit.updating(["GNUPGHOME": (mockedHomeDir / ".gnupg").string]), quiet: false)
422440
} else {
423-
try await sys.gpg().verify(detached_signature: sigFile, signed_data: archive).run(self, quiet: !verbose)
441+
try await sys.gpg().verify(detached_signature: sigFile, signed_data: archive).run(quiet: !verbose)
424442
}
425443
} catch {
426444
throw SwiftlyError(message: "Signature verification failed: \(error).")
@@ -445,11 +463,9 @@ public struct Linux: Platform {
445463
await ctx.message("Verifying swiftly signature...")
446464
do {
447465
if let mockedHomeDir = ctx.mockedHomeDir {
448-
var env = ProcessInfo.processInfo.environment
449-
env["GNUPGHOME"] = (mockedHomeDir / ".gnupg").string
450-
try await sys.gpg().verify(detached_signature: sigFile, signed_data: archive).run(self, env: env, quiet: false)
466+
try await sys.gpg().verify(detached_signature: sigFile, signed_data: archive).run(environment: .inherit.updating(["GNUPGHOME": (mockedHomeDir / ".gnupg").string]), quiet: false)
451467
} else {
452-
try await sys.gpg().verify(detached_signature: sigFile, signed_data: archive).run(self, quiet: !verbose)
468+
try await sys.gpg().verify(detached_signature: sigFile, signed_data: archive).run(quiet: !verbose)
453469
}
454470
} catch {
455471
throw SwiftlyError(message: "Signature verification failed: \(error).")
@@ -603,7 +619,7 @@ public struct Linux: Platform {
603619

604620
public func getShell() async throws -> String {
605621
let userName = ProcessInfo.processInfo.userName
606-
if let entry = try await sys.getent(database: "passwd", key: userName).entries(self).first {
622+
if let entry = try await sys.getent(database: "passwd", key: userName).entries().first {
607623
if let shell = entry.last { return shell }
608624
}
609625

Sources/MacOSPlatform/MacOS.swift

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import Foundation
2+
import Subprocess
23
import SwiftlyCore
4+
import System
35
import SystemPackage
46

57
typealias sys = SwiftlyCore.SystemCommand
@@ -17,21 +19,21 @@ public struct SwiftPkgInfo: Codable {
1719
public struct MacOS: Platform {
1820
public init() {}
1921

20-
public var defaultSwiftlyHomeDir: FilePath {
22+
public var defaultSwiftlyHomeDir: SystemPackage.FilePath {
2123
fs.home / ".swiftly"
2224
}
2325

24-
public var defaultToolchainsDirectory: FilePath {
26+
public var defaultToolchainsDirectory: SystemPackage.FilePath {
2527
fs.home / "Library/Developer/Toolchains"
2628
}
2729

28-
public func swiftlyBinDir(_ ctx: SwiftlyCoreContext) -> FilePath {
30+
public func swiftlyBinDir(_ ctx: SwiftlyCoreContext) -> SystemPackage.FilePath {
2931
ctx.mockedHomeDir.map { $0 / "bin" }
3032
?? ProcessInfo.processInfo.environment["SWIFTLY_BIN_DIR"].map { FilePath($0) }
3133
?? fs.home / ".swiftly/bin"
3234
}
3335

34-
public func swiftlyToolchainsDir(_ ctx: SwiftlyCoreContext) -> FilePath {
36+
public func swiftlyToolchainsDir(_ ctx: SwiftlyCoreContext) -> SystemPackage.FilePath {
3537
ctx.mockedHomeDir.map { $0 / "Toolchains" }
3638
?? ProcessInfo.processInfo.environment["SWIFTLY_TOOLCHAINS_DIR"].map { FilePath($0) }
3739
// This is where the installer will put the toolchains, and where Xcode can find them
@@ -55,7 +57,7 @@ public struct MacOS: Platform {
5557
}
5658

5759
public func install(
58-
_ ctx: SwiftlyCoreContext, from tmpFile: FilePath, version: ToolchainVersion, verbose: Bool
60+
_ ctx: SwiftlyCoreContext, from tmpFile: SystemPackage.FilePath, version: ToolchainVersion, verbose: Bool
5961
) async throws {
6062
guard try await fs.exists(atPath: tmpFile) else {
6163
throw SwiftlyError(message: "\(tmpFile) doesn't exist")
@@ -71,7 +73,7 @@ public struct MacOS: Platform {
7173
// If the toolchains go into the default user location then we use the installer to install them
7274
await ctx.message("Installing package in user home directory...")
7375

74-
try await sys.installer(.verbose, .pkg(tmpFile), .target("CurrentUserHomeDirectory")).run(self, quiet: !verbose)
76+
try await sys.installer(.verbose, .pkg(tmpFile), .target("CurrentUserHomeDirectory")).run()
7577
} else {
7678
// Otherwise, we extract the pkg into the requested toolchains directory.
7779
await ctx.message("Expanding pkg...")
@@ -84,7 +86,7 @@ public struct MacOS: Platform {
8486

8587
await ctx.message("Checking package signature...")
8688
do {
87-
try await sys.pkgutil().checksignature(pkg_path: tmpFile).run(self, quiet: !verbose)
89+
try await sys.pkgutil().checksignature(pkg_path: tmpFile).run(quiet: !verbose)
8890
} catch {
8991
// If this is not a test that uses mocked toolchains then we must throw this error and abort installation
9092
guard ctx.mockedHomeDir != nil else {
@@ -94,7 +96,7 @@ public struct MacOS: Platform {
9496
// We permit the signature verification to fail during testing
9597
await ctx.message("Signature verification failed, which is allowable during testing with mocked toolchains")
9698
}
97-
try await sys.pkgutil(.verbose).expand(pkg_path: tmpFile, dir_path: tmpDir).run(self, quiet: !verbose)
99+
try await sys.pkgutil(.verbose).expand(pkg_path: tmpFile, dir_path: tmpDir).run()
98100

99101
// There's a slight difference in the location of the special Payload file between official swift packages
100102
// and the ones that are mocked here in the test framework.
@@ -104,11 +106,11 @@ public struct MacOS: Platform {
104106
}
105107

106108
await ctx.message("Untarring pkg Payload...")
107-
try await sys.tar(.directory(toolchainDir)).extract(.verbose, .archive(payload)).run(self, quiet: !verbose)
109+
try await sys.tar(.directory(toolchainDir)).extract(.verbose, .archive(payload)).run(quiet: !verbose)
108110
}
109111
}
110112

111-
public func extractSwiftlyAndInstall(_ ctx: SwiftlyCoreContext, from archive: FilePath) async throws {
113+
public func extractSwiftlyAndInstall(_ ctx: SwiftlyCoreContext, from archive: SystemPackage.FilePath) async throws {
112114
guard try await fs.exists(atPath: archive) else {
113115
throw SwiftlyError(message: "\(archive) doesn't exist")
114116
}
@@ -120,16 +122,16 @@ public struct MacOS: Platform {
120122
try await sys.installer(
121123
.pkg(archive),
122124
.target("CurrentUserHomeDirectory")
123-
).run(self)
124-
try? await sys.pkgutil(.volume(userHomeDir)).forget(pkg_id: "org.swift.swiftly").run(self)
125+
).run()
126+
try? await sys.pkgutil(.volume(userHomeDir)).forget(pkg_id: "org.swift.swiftly").run()
125127
} else {
126128
let installDir = userHomeDir / ".swiftly"
127129
try await fs.mkdir(.parents, atPath: installDir)
128130

129131
// In the case of a mock for testing purposes we won't use the installer, perferring a manual process because
130132
// the installer will not install to an arbitrary path, only a volume or user home directory.
131133
let tmpDir = fs.mktemp()
132-
try await sys.pkgutil().expand(pkg_path: archive, dir_path: tmpDir).run(self)
134+
try await sys.pkgutil().expand(pkg_path: archive, dir_path: tmpDir).run()
133135

134136
// There's a slight difference in the location of the special Payload file between official swift packages
135137
// and the ones that are mocked here in the test framework.
@@ -139,10 +141,10 @@ public struct MacOS: Platform {
139141
}
140142

141143
await ctx.message("Extracting the swiftly package into \(installDir)...")
142-
try await sys.tar(.directory(installDir)).extract(.verbose, .archive(payload)).run(self, quiet: false)
144+
try await sys.tar(.directory(installDir)).extract(.verbose, .archive(payload)).run(quiet: false)
143145
}
144146

145-
try await self.runProgram((userHomeDir / ".swiftly/bin/swiftly").string, "init")
147+
_ = try await run(.path(System.FilePath((userHomeDir / ".swiftly/bin/swiftly").string)), arguments: ["init"], input: .standardInput, output: .standardOutput, error: .standardError)
146148
}
147149

148150
public func uninstall(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, verbose: Bool)
@@ -164,22 +166,22 @@ public struct MacOS: Platform {
164166

165167
try await fs.remove(atPath: toolchainDir)
166168

167-
try? await sys.pkgutil(.volume(fs.home)).forget(pkg_id: pkgInfo.CFBundleIdentifier).run(self, quiet: !verbose)
169+
try? await sys.pkgutil(.volume(fs.home)).forget(pkg_id: pkgInfo.CFBundleIdentifier).run(quiet: !verbose)
168170
}
169171

170172
public func getExecutableName() -> String {
171173
"swiftly-macos-osx"
172174
}
173175

174176
public func verifyToolchainSignature(
175-
_: SwiftlyCoreContext, toolchainFile _: ToolchainFile, archive _: FilePath, verbose _: Bool
177+
_: SwiftlyCoreContext, toolchainFile _: ToolchainFile, archive _: SystemPackage.FilePath, verbose _: Bool
176178
) async throws {
177179
// No signature verification is required on macOS since the pkg files have their own signing
178180
// mechanism and the swift.org downloadables are trusted by stock macOS installations.
179181
}
180182

181183
public func verifySwiftlySignature(
182-
_: SwiftlyCoreContext, archiveDownloadURL _: URL, archive _: FilePath, verbose _: Bool
184+
_: SwiftlyCoreContext, archiveDownloadURL _: URL, archive _: SystemPackage.FilePath, verbose _: Bool
183185
) async throws {
184186
// No signature verification is required on macOS since the pkg files have their own signing
185187
// mechanism and the swift.org downloadables are trusted by stock macOS installations.
@@ -201,11 +203,11 @@ public struct MacOS: Platform {
201203
return "/bin/zsh"
202204
}
203205

204-
public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> FilePath
206+
public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> SystemPackage.FilePath
205207
{
206208
if toolchain == .xcodeVersion {
207209
// Print the toolchain location with the help of xcrun
208-
if let xcrunLocation = try? await self.runProgramOutput("/usr/bin/xcrun", "-f", "swift") {
210+
if let xcrunLocation = try? await run(.path(SystemPackage.FilePath("/usr/bin/xcrun")), arguments: ["-f", "swift"], output: .string(limit: 1024 * 10)).standardOutput {
209211
return FilePath(xcrunLocation.replacingOccurrences(of: "\n", with: "")).removingLastComponent().removingLastComponent().removingLastComponent()
210212
}
211213
}

Sources/Swiftly/Proxy.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ArgumentParser
22
import Foundation
3+
import Subprocess
34
import SwiftlyCore
45

56
@main
@@ -67,11 +68,24 @@ public enum Proxy {
6768
guard ProcessInfo.processInfo.environment["SWIFTLY_PROXY_IN_PROGRESS"] == nil else {
6869
throw SwiftlyError(message: "Circular swiftly proxy invocation")
6970
}
70-
let env = ["SWIFTLY_PROXY_IN_PROGRESS": "1"]
7171

72-
try await Swiftly.currentPlatform.proxy(ctx, toolchain, binName, Array(CommandLine.arguments[1...]), env)
72+
let env = try await Swiftly.currentPlatform.proxyEnvironment(ctx, env: .inherit, toolchain: toolchain)
73+
74+
_ = try await Subprocess.run(
75+
.name(binName),
76+
arguments: Arguments(Array(CommandLine.arguments[1...])),
77+
environment: env.updating(["SWIFTLY_PROXY_IN_PROGRESS": "1"]),
78+
input: .standardInput,
79+
output: .standardOutput,
80+
error: .standardError
81+
)
7382
} catch let terminated as RunProgramError {
74-
exit(terminated.exitCode)
83+
switch terminated.terminationStatus {
84+
case let .exited(code):
85+
exit(code)
86+
case .unhandledException:
87+
exit(1)
88+
}
7589
} catch let error as SwiftlyError {
7690
await ctx.message(error.message)
7791
exit(1)

Sources/Swiftly/Run.swift

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import ArgumentParser
22
import Foundation
3+
import Subprocess
34
import SwiftlyCore
5+
import SystemPackage
46

57
struct Run: SwiftlyCommand {
68
public static let configuration = CommandConfiguration(
@@ -93,24 +95,47 @@ struct Run: SwiftlyCommand {
9395
throw SwiftlyError(message: "No installed swift toolchain is selected from either from a .swift-version file, or the default. You can try using one that's already installed with `swiftly use <toolchain version>` or install a new toolchain to use with `swiftly install --use <toolchain version>`.")
9496
}
9597

96-
do {
97-
if let outputHandler = ctx.outputHandler {
98-
if let output = try await Swiftly.currentPlatform.proxyOutput(ctx, toolchain, command[0], [String](command[1...])) {
99-
for line in output.split(separator: "\n") {
100-
await outputHandler.handleOutputLine(String(line))
101-
}
98+
let env: Environment = try await Swiftly.currentPlatform.proxyEnvironment(ctx, env: .inherit, toolchain: toolchain)
99+
100+
let commandPath = FilePath(command[0])
101+
let executable: Executable
102+
if try await fs.exists(atPath: commandPath) {
103+
executable = .path(commandPath)
104+
} else {
105+
// Search the toolchain ourselves to find the correct executable path. Subprocess's default search
106+
// paths will interfere with preferring the selected toolchain over system toolchains.
107+
let tcBinPath = try await Swiftly.currentPlatform.findToolchainLocation(ctx, toolchain) / "usr/bin"
108+
let toolPath = tcBinPath / command[0]
109+
if try await fs.exists(atPath: toolPath) && toolPath.isLexicallyNormal {
110+
executable = .path(toolPath)
111+
} else {
112+
executable = .name(command[0])
113+
}
114+
}
115+
116+
let processConfig = Configuration(
117+
executable: executable,
118+
arguments: Arguments([String](command[1...])),
119+
environment: env
120+
)
121+
122+
if let outputHandler = ctx.outputHandler {
123+
let result = try await Subprocess.run(processConfig) { _, output in
124+
for try await line in output.lines() {
125+
await outputHandler.handleOutputLine(line.replacing("\n", with: ""))
102126
}
103-
return
104127
}
105128

106-
try await Swiftly.currentPlatform.proxy(ctx, toolchain, command[0], [String](command[1...]))
107-
} catch let terminated as RunProgramError {
108-
if ctx.mockedHomeDir != nil {
109-
throw terminated
129+
if !result.terminationStatus.isSuccess {
130+
throw RunProgramError(terminationStatus: result.terminationStatus, config: processConfig)
110131
}
111-
Foundation.exit(terminated.exitCode)
112-
} catch {
113-
throw error
132+
133+
return
134+
}
135+
136+
let result = try await Subprocess.run(.path(FilePath(command[0])), arguments: Arguments([String](command[1...])), environment: env, input: .standardInput, output: .standardOutput, error: .standardError)
137+
if !result.terminationStatus.isSuccess {
138+
throw RunProgramError(terminationStatus: result.terminationStatus, config: processConfig)
114139
}
115140
}
116141

Sources/SwiftlyCore/Commands+Runnable+Output.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import Foundation
22
import SystemPackage
33

44
extension SystemCommand.dsclCommand.readCommand: Output {
5-
public func properties(_ p: Platform) async throws -> [(key: String, value: String)] {
6-
let output = try await self.output(p)
5+
public func properties(_: Platform) async throws -> [(key: String, value: String)] {
6+
let output = try await self.output(limit: 1024 * 100)
77
guard let output else { return [] }
88

99
var props: [(key: String, value: String)] = []
@@ -21,8 +21,8 @@ extension SystemCommand.lipoCommand.createCommand: Runnable {}
2121
extension SystemCommand.pkgbuildCommand: Runnable {}
2222

2323
extension SystemCommand.getentCommand: Output {
24-
public func entries(_ platform: Platform) async throws -> [[String]] {
25-
let output = try await output(platform)
24+
public func entries() async throws -> [[String]] {
25+
let output = try await output(limit: 1024 * 100)
2626
guard let output else { return [] }
2727

2828
var entries: [[String]] = []

0 commit comments

Comments
 (0)