Skip to content

Commit c219f4d

Browse files
authored
Remove swiftly from the path during proxying (#321)
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 0f1aa94 commit c219f4d

File tree

2 files changed

+66
-10
lines changed

2 files changed

+66
-10
lines changed

Sources/SwiftlyCore/Platform.swift

+36-10
Original file line numberDiff line numberDiff line change
@@ -139,19 +139,25 @@ extension Platform {
139139
}
140140

141141
#if os(macOS) || os(Linux)
142-
internal func proxyEnv(_ toolchain: ToolchainVersion) throws -> [String: String] {
142+
internal func proxyEnv(env: [String: String], toolchain: ToolchainVersion) throws -> [String: String] {
143+
var newEnv = env
144+
143145
let tcPath = self.findToolchainLocation(toolchain).appendingPathComponent("usr/bin")
144146
guard tcPath.fileExists() else {
145147
throw SwiftlyError(message: "Toolchain \(toolchain) could not be located. You can try `swiftly uninstall \(toolchain)` to uninstall it and then `swiftly install \(toolchain)` to install it again.")
146148
}
147-
var newEnv = ProcessInfo.processInfo.environment
149+
150+
var pathComponents = (newEnv["PATH"] ?? "").split(separator: ":").map { String($0) }
148151

149152
// The toolchain goes to the beginning of the PATH
150-
var newPath = newEnv["PATH"] ?? ""
151-
if !newPath.hasPrefix(tcPath.path + ":") {
152-
newPath = "\(tcPath.path):\(newPath)"
153-
}
154-
newEnv["PATH"] = newPath
153+
pathComponents.removeAll(where: { $0 == tcPath.path })
154+
pathComponents = [tcPath.path] + pathComponents
155+
156+
// Remove swiftly bin directory from the PATH entirely
157+
let swiftlyBinDir = self.swiftlyBinDir
158+
pathComponents.removeAll(where: { $0 == swiftlyBinDir.path })
159+
160+
newEnv["PATH"] = String(pathComponents.joined(by: ":"))
155161

156162
return newEnv
157163
}
@@ -162,11 +168,22 @@ extension Platform {
162168
/// the exit code and program information.
163169
///
164170
public func proxy(_ toolchain: ToolchainVersion, _ command: String, _ arguments: [String], _ env: [String: String] = [:]) async throws {
165-
var newEnv = try self.proxyEnv(toolchain)
171+
var newEnv = try self.proxyEnv(env: ProcessInfo.processInfo.environment, toolchain: toolchain)
172+
173+
let tcPath = self.findToolchainLocation(toolchain).appendingPathComponent("usr/bin")
174+
175+
let commandTcPath = tcPath.appendingPathComponent(command)
176+
let commandToRun = if FileManager.default.fileExists(atPath: commandTcPath.path) {
177+
commandTcPath.path
178+
} else {
179+
command
180+
}
181+
166182
for (key, value) in env {
167183
newEnv[key] = value
168184
}
169-
try self.runProgram([command] + arguments, env: newEnv)
185+
186+
try self.runProgram([commandToRun] + arguments, env: newEnv)
170187
}
171188

172189
/// Proxy the invocation of the provided command to the chosen toolchain and capture the output.
@@ -175,7 +192,16 @@ extension Platform {
175192
/// the exit code and program information.
176193
///
177194
public func proxyOutput(_ toolchain: ToolchainVersion, _ command: String, _ arguments: [String]) async throws -> String? {
178-
try await self.runProgramOutput(command, arguments, env: self.proxyEnv(toolchain))
195+
let tcPath = self.findToolchainLocation(toolchain).appendingPathComponent("usr/bin")
196+
197+
let commandTcPath = tcPath.appendingPathComponent(command)
198+
let commandToRun = if FileManager.default.fileExists(atPath: commandTcPath.path) {
199+
commandTcPath.path
200+
} else {
201+
command
202+
}
203+
204+
return try await self.runProgramOutput(commandToRun, arguments, env: self.proxyEnv(env: ProcessInfo.processInfo.environment, toolchain: toolchain))
179205
}
180206

181207
/// Run a program.

Tests/SwiftlyTests/PlatformTests.swift

+30
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,34 @@ final class PlatformTests: SwiftlyTests {
7373
XCTAssertEqual(0, toolchains.count)
7474
}
7575
}
76+
77+
#if os(macOS) || os(Linux)
78+
func testProxyEnv() async throws {
79+
try await self.rollbackLocalChanges {
80+
var (mockedToolchainFile, version) = try await self.mockToolchainDownload(version: SwiftlyTests.newStable.name)
81+
try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version, verbose: true)
82+
83+
for path in [
84+
"/a/b/c:SWIFTLY_BIN_DIR:/d/e/f",
85+
"SWIFTLY_BIN_DIR:/abcde",
86+
"/defgh:SWIFTLY_BIN_DIR",
87+
"/xyzabc:/1/3/4",
88+
"",
89+
] {
90+
// GIVEN: a PATH that may contain the swiftly bin directory
91+
let env = ["PATH": path.replacing("SWIFTLY_BIN_DIR", with: Swiftly.currentPlatform.swiftlyBinDir.path)]
92+
93+
// WHEN: proxying to an installed toolchain
94+
let newEnv = try Swiftly.currentPlatform.proxyEnv(env: env, toolchain: SwiftlyTests.newStable)
95+
96+
// THEN: the toolchain's bin directory is added to the beginning of the PATH
97+
XCTAssert(newEnv["PATH"]!.hasPrefix(Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.newStable).appendingPathComponent("usr/bin").path))
98+
99+
// AND: the swiftly bin directory is removed from the PATH
100+
XCTAssert(!newEnv["PATH"]!.contains(Swiftly.currentPlatform.swiftlyBinDir.path))
101+
XCTAssert(!newEnv["PATH"]!.contains(Swiftly.currentPlatform.swiftlyBinDir.path))
102+
}
103+
}
104+
}
105+
#endif
76106
}

0 commit comments

Comments
 (0)