Skip to content

Commit b7ce674

Browse files
authored
[6.1] Apply toolset's debugger property in swift run (#8257)
Cherry-pick of #8252, merged as 88e2af7. **Explanation**: Per [SE-0378 toolsets can specify an optional `debugger` property](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md#toolsetjson-files), which so far had no effect. We should respect this property in `swift run` invocations when any toolsets or Swift SDKs are selected. This allows running and debugging cross-compiled products in an environment other than the host, e.g. a container runtime when cross-compiled to Linux, QEMU or a firmware flasher and serial port setup script with Swift Embedded for microcontrollers, or in a Wasm runtime for a Wasm binary. **Scope**: Only impacts `swift-run` when a toolset or a Swift SDK are selected. **Risk**: Low, due to limited scope and automated testing. **Testing**: Added a new test cause to the existing XCTest suite. **Issue**: rdar://143814083 **Reviewer**: @dschaefer2
1 parent b597e16 commit b7ce674

File tree

5 files changed

+63
-9
lines changed

5 files changed

+63
-9
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh
2+
3+
echo "$@"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"debugger": { "path": "echo.sh" },
3+
"schemaVersion" : "1.0",
4+
"rootPath" : "."
5+
}

Sources/Commands/SwiftRunCommand.swift

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,17 +169,28 @@ public struct SwiftRunCommand: AsyncSwiftCommand {
169169
try await buildSystem.build(subset: .product(productName))
170170
}
171171

172-
let executablePath = try swiftCommandState.productsBuildParameters.buildPath.appending(component: productName)
172+
let productRelativePath = try swiftCommandState.productsBuildParameters.executablePath(for: productName)
173+
let productAbsolutePath = try swiftCommandState.productsBuildParameters.buildPath.appending(productRelativePath)
173174

174175
// Make sure we are running from the original working directory.
175176
let cwd: AbsolutePath? = swiftCommandState.fileSystem.currentWorkingDirectory
176177
if cwd == nil || swiftCommandState.originalWorkingDirectory != cwd {
177178
try ProcessEnv.chdir(swiftCommandState.originalWorkingDirectory)
178179
}
179180

180-
let pathRelativeToWorkingDirectory = executablePath.relative(to: swiftCommandState.originalWorkingDirectory)
181-
let lldbPath = try swiftCommandState.getTargetToolchain().getLLDB()
182-
try exec(path: lldbPath.pathString, args: ["--", pathRelativeToWorkingDirectory.pathString] + options.arguments)
181+
if let debugger = try swiftCommandState.getTargetToolchain().swiftSDK.toolset.knownTools[.debugger],
182+
let debuggerPath = debugger.path {
183+
try self.run(
184+
fileSystem: swiftCommandState.fileSystem,
185+
executablePath: debuggerPath,
186+
originalWorkingDirectory: swiftCommandState.originalWorkingDirectory,
187+
arguments: debugger.extraCLIOptions + [productAbsolutePath.pathString] + options.arguments
188+
)
189+
} else {
190+
let pathRelativeToWorkingDirectory = productAbsolutePath.relative(to: swiftCommandState.originalWorkingDirectory)
191+
let lldbPath = try swiftCommandState.getTargetToolchain().getLLDB()
192+
try exec(path: lldbPath.pathString, args: ["--", pathRelativeToWorkingDirectory.pathString] + options.arguments)
193+
}
183194
} catch let error as RunError {
184195
swiftCommandState.observabilityScope.emit(error)
185196
throw ExitCode.failure
@@ -215,11 +226,27 @@ public struct SwiftRunCommand: AsyncSwiftCommand {
215226
}
216227

217228
let executablePath = try swiftCommandState.productsBuildParameters.buildPath.appending(component: productName)
229+
230+
let productRelativePath = try swiftCommandState.productsBuildParameters.executablePath(for: productName)
231+
let productAbsolutePath = try swiftCommandState.productsBuildParameters.buildPath.appending(productRelativePath)
232+
233+
let runnerPath: AbsolutePath
234+
let arguments: [String]
235+
236+
if let debugger = try swiftCommandState.getTargetToolchain().swiftSDK.toolset.knownTools[.debugger],
237+
let debuggerPath = debugger.path {
238+
runnerPath = debuggerPath
239+
arguments = debugger.extraCLIOptions + [productAbsolutePath.pathString] + options.arguments
240+
} else {
241+
runnerPath = executablePath
242+
arguments = options.arguments
243+
}
244+
218245
try self.run(
219246
fileSystem: swiftCommandState.fileSystem,
220-
executablePath: executablePath,
247+
executablePath: runnerPath,
221248
originalWorkingDirectory: swiftCommandState.originalWorkingDirectory,
222-
arguments: options.arguments
249+
arguments: arguments
223250
)
224251
} catch Diagnostics.fatalError {
225252
throw ExitCode.failure
@@ -267,8 +294,8 @@ public struct SwiftRunCommand: AsyncSwiftCommand {
267294
fileSystem: FileSystem,
268295
executablePath: AbsolutePath,
269296
originalWorkingDirectory: AbsolutePath,
270-
arguments: [String]) throws
271-
{
297+
arguments: [String]
298+
) throws {
272299
// Make sure we are running from the original working directory.
273300
let cwd: AbsolutePath? = fileSystem.currentWorkingDirectory
274301
if cwd == nil || originalWorkingDirectory != cwd {

Sources/SPMBuildCore/BuildParameters/BuildParameters.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ public struct BuildParameters: Encodable {
286286
}
287287

288288
/// Returns the path to the executable of a product for the current build parameters.
289-
private func executablePath(for name: String) throws -> RelativePath {
289+
package func executablePath(for name: String) throws -> RelativePath {
290290
try RelativePath(validating: "\(name)\(self.suffix)\(self.triple.executableExtension)")
291291
}
292292

Tests/CommandsTests/RunCommandTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ final class RunCommandTests: CommandsTestCase {
4040
XCTAssert(stdout.contains("Swift Package Manager"), "got stdout:\n" + stdout)
4141
}
4242

43+
// echo.sh script from the toolset won't work on Windows
44+
#if !os(Windows)
45+
func testToolsetDebugger() async throws {
46+
try await fixture(name: "Miscellaneous/EchoExecutable") { fixturePath in
47+
let (stdout, stderr) = try await SwiftPM.Run.execute(
48+
["--toolset", "\(fixturePath)/toolset.json"], packagePath: fixturePath)
49+
50+
// We only expect tool's output on the stdout stream.
51+
XCTAssertMatch(stdout, .contains("""
52+
\(fixturePath)/.build
53+
"""))
54+
55+
// swift-build-tool output should go to stderr.
56+
XCTAssertMatch(stderr, .regex("Compiling"))
57+
XCTAssertMatch(stderr, .contains("Linking"))
58+
}
59+
}
60+
#endif
61+
4362
func testUnknownProductAndArgumentPassing() async throws {
4463
try await fixture(name: "Miscellaneous/EchoExecutable") { fixturePath in
4564
let (stdout, stderr) = try await SwiftPM.Run.execute(

0 commit comments

Comments
 (0)