Skip to content

Commit 63cb10f

Browse files
authored
Remove swiftly from the path during proxying (#311)
Retaining swiftly in the path increases the probability that a tool might run the proxy again, resulting in circularities. Also, tools will locate programs in the path to try and find adjacent toolchain files that cannot be discovered in the swiftly bin directory. Remove the swiftly bin directory from the path when proxying to help improve the experience with other tools.
1 parent 5645873 commit 63cb10f

File tree

3 files changed

+66
-21
lines changed

3 files changed

+66
-21
lines changed

Sources/Swiftly/Run.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ struct Run: SwiftlyCommand {
105105

106106
try await Swiftly.currentPlatform.proxy(ctx, toolchain, command[0], [String](command[1...]))
107107
} catch let terminated as RunProgramError {
108+
if ctx.mockedHomeDir != nil {
109+
throw terminated
110+
}
108111
Foundation.exit(terminated.exitCode)
109112
} catch {
110113
throw error

Sources/SwiftlyCore/Platform.swift

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -164,25 +164,28 @@ extension Platform {
164164
}
165165

166166
#if os(macOS) || os(Linux)
167-
func proxyEnv(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) throws -> [
168-
String:
169-
String
170-
] {
167+
func proxyEnv(_ ctx: SwiftlyCoreContext, env: [String: String], toolchain: ToolchainVersion) throws -> [String: String] {
168+
var newEnv = env
169+
171170
let tcPath = self.findToolchainLocation(ctx, toolchain).appendingPathComponent("usr/bin")
172171
guard tcPath.fileExists() else {
173172
throw SwiftlyError(
174173
message:
175174
"Toolchain \(toolchain) could not be located. You can try `swiftly uninstall \(toolchain)` to uninstall it and then `swiftly install \(toolchain)` to install it again."
176175
)
177176
}
178-
var newEnv = ProcessInfo.processInfo.environment
177+
178+
var pathComponents = (newEnv["PATH"] ?? "").split(separator: ":").map { String($0) }
179179

180180
// The toolchain goes to the beginning of the PATH
181-
var newPath = newEnv["PATH"] ?? ""
182-
if !newPath.hasPrefix(tcPath.path + ":") {
183-
newPath = "\(tcPath.path):\(newPath)"
184-
}
185-
newEnv["PATH"] = newPath
181+
pathComponents.removeAll(where: { $0 == tcPath.path })
182+
pathComponents = [tcPath.path] + pathComponents
183+
184+
// Remove swiftly bin directory from the PATH entirely
185+
let swiftlyBinDir = self.swiftlyBinDir(ctx)
186+
pathComponents.removeAll(where: { $0 == swiftlyBinDir.path })
187+
188+
newEnv["PATH"] = String(pathComponents.joined(by: ":"))
186189

187190
return newEnv
188191
}
@@ -192,27 +195,40 @@ extension Platform {
192195
/// In the case where the command exit with a non-zero exit code a RunProgramError is thrown with
193196
/// the exit code and program information.
194197
///
195-
public func proxy(
196-
_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, _ command: String,
197-
_ arguments: [String], _ env: [String: String] = [:]
198-
) async throws {
199-
var newEnv = try self.proxyEnv(ctx, toolchain)
198+
public func proxy(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, _ command: String, _ arguments: [String], _ env: [String: String] = [:]) async throws {
199+
let tcPath = self.findToolchainLocation(ctx, toolchain).appendingPathComponent("usr/bin")
200+
201+
let commandTcPath = tcPath.appendingPathComponent(command)
202+
let commandToRun = if FileManager.default.fileExists(atPath: commandTcPath.path) {
203+
commandTcPath.path
204+
} else {
205+
command
206+
}
207+
208+
var newEnv = try self.proxyEnv(ctx, env: ProcessInfo.processInfo.environment, toolchain: toolchain)
200209
for (key, value) in env {
201210
newEnv[key] = value
202211
}
203-
try self.runProgram([command] + arguments, env: newEnv)
212+
213+
try self.runProgram([commandToRun] + arguments, env: newEnv)
204214
}
205215

206216
/// Proxy the invocation of the provided command to the chosen toolchain and capture the output.
207217
///
208218
/// In the case where the command exit with a non-zero exit code a RunProgramError is thrown with
209219
/// the exit code and program information.
210220
///
211-
public func proxyOutput(
212-
_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, _ command: String,
213-
_ arguments: [String]
214-
) async throws -> String? {
215-
try await self.runProgramOutput(command, arguments, env: self.proxyEnv(ctx, toolchain))
221+
public func proxyOutput(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, _ command: String, _ arguments: [String]) async throws -> String? {
222+
let tcPath = self.findToolchainLocation(ctx, toolchain).appendingPathComponent("usr/bin")
223+
224+
let commandTcPath = tcPath.appendingPathComponent(command)
225+
let commandToRun = if FileManager.default.fileExists(atPath: commandTcPath.path) {
226+
commandTcPath.path
227+
} else {
228+
command
229+
}
230+
231+
return try await self.runProgramOutput(commandToRun, arguments, env: self.proxyEnv(ctx, env: ProcessInfo.processInfo.environment, toolchain: toolchain))
216232
}
217233

218234
/// Run a program.

Tests/SwiftlyTests/PlatformTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,30 @@ import Testing
8585
toolchains = try FileManager.default.contentsOfDirectory(at: Swiftly.currentPlatform.swiftlyToolchainsDir(SwiftlyTests.ctx), includingPropertiesForKeys: nil)
8686
#expect(0 == toolchains.count)
8787
}
88+
89+
#if os(macOS) || os(Linux)
90+
@Test(
91+
.mockHomeToolchains(),
92+
arguments: [
93+
"/a/b/c:SWIFTLY_BIN_DIR:/d/e/f",
94+
"SWIFTLY_BIN_DIR:/abcde",
95+
"/defgh:SWIFTLY_BIN_DIR",
96+
"/xyzabc:/1/3/4",
97+
"",
98+
]
99+
) func proxyEnv(_ path: String) async throws {
100+
// GIVEN: a PATH that may contain the swiftly bin directory
101+
let env = ["PATH": path.replacing("SWIFTLY_BIN_DIR", with: Swiftly.currentPlatform.swiftlyBinDir(SwiftlyTests.ctx).path)]
102+
103+
// WHEN: proxying to an installed toolchain
104+
let newEnv = try Swiftly.currentPlatform.proxyEnv(SwiftlyTests.ctx, env: env, toolchain: .newStable)
105+
106+
// THEN: the toolchain's bin directory is added to the beginning of the PATH
107+
#expect(newEnv["PATH"]!.hasPrefix(Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, .newStable).appendingPathComponent("usr/bin").path))
108+
109+
// AND: the swiftly bin directory is removed from the PATH
110+
#expect(!newEnv["PATH"]!.contains(Swiftly.currentPlatform.swiftlyBinDir(SwiftlyTests.ctx).path))
111+
#expect(!newEnv["PATH"]!.contains(Swiftly.currentPlatform.swiftlyBinDir(SwiftlyTests.ctx).path))
112+
}
113+
#endif
88114
}

0 commit comments

Comments
 (0)