Skip to content

Commit dfd8a83

Browse files
committed
[Build] Fix debugging for products built by SwiftPM
This implements proper debugging support on both Darwin and Linux. On Darwin, this means SwiftPM will pass the swiftmodule file to the linker and on Linux, it'll link the wrapped module obtained using Swift compiler's modulewrap subtool. <rdar://problem/29228963> <rdar://problem/52118932> https://bugs.swift.org/browse/SR-3280
1 parent 9c5a13a commit dfd8a83

File tree

3 files changed

+138
-2
lines changed

3 files changed

+138
-2
lines changed

Sources/Build/BuildPlan.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,29 @@ public struct BuildParameters: Encodable {
229229
return BuildSettings.Scope(target.underlyingTarget.buildSettings, boundCondition: (currentPlatform, configuration))
230230
}
231231

232+
/// Represents the debugging strategy.
233+
///
234+
/// Swift binaries requires the swiftmodule files in order for lldb to work.
235+
/// On Darwin, linker can directly take the swiftmodule file path using the
236+
/// -add_ast_path flag. On other platforms, we convert the swiftmodule into
237+
/// an object file using Swift's modulewrap tool.
238+
enum DebuggingStrategy {
239+
case swiftAST
240+
case modulewrap
241+
}
242+
243+
/// The debugging strategy according to the current build parameters.
244+
var debuggingStrategy: DebuggingStrategy? {
245+
guard configuration == .debug else {
246+
return nil
247+
}
248+
249+
if triple.isDarwin() {
250+
return .swiftAST
251+
}
252+
return .modulewrap
253+
}
254+
232255
/// A shim struct for toolchain so we can encode it without having to write encode(to:) for
233256
/// entire BuildParameters by hand.
234257
struct _Toolchain: Encodable {
@@ -488,6 +511,13 @@ public final class SwiftTargetBuildDescription {
488511
return buildParameters.buildPath.appending(component: target.c99name + ".swiftmodule")
489512
}
490513

514+
/// The path to the wrapped swift module which is created using the modulewrap tool. This is required
515+
/// for supporting debugging on non-Darwin platforms (On Darwin, we just pass the swiftmodule to the linker
516+
/// using the `-add_ast_path` flag).
517+
var wrappedModuleOutputPath: AbsolutePath {
518+
return tempsPath.appending(component: target.c99name + ".swiftmodule.o")
519+
}
520+
491521
/// The path to the swifinterface file after compilation.
492522
var parseableModuleInterfaceOutputPath: AbsolutePath {
493523
return buildParameters.buildPath.appending(component: target.c99name + ".swiftinterface")
@@ -747,6 +777,9 @@ public final class ProductBuildDescription {
747777
/// The list of targets that are going to be linked statically in this product.
748778
fileprivate var staticTargets: [ResolvedTarget] = []
749779

780+
/// The list of Swift modules that should be passed to the linker. This is required for debugging to work.
781+
fileprivate var swiftASTs: [AbsolutePath] = []
782+
750783
/// Path to the temporary directory for this product.
751784
var tempsPath: AbsolutePath {
752785
return buildParameters.buildPath.appending(component: product.name + ".product")
@@ -870,6 +903,10 @@ public final class ProductBuildDescription {
870903
// Add arguments from declared build settings.
871904
args += self.buildSettingsFlags()
872905

906+
// Add AST paths to make the product debuggable. This array is only populated when we're
907+
// building for Darwin in debug configuration.
908+
args += swiftASTs.flatMap{ ["-Xlinker", "-add_ast_path", "-Xlinker", $0.pathString] }
909+
873910
// User arguments (from -Xlinker and -Xswiftc) should follow generated arguments to allow user overrides
874911
args += buildParameters.linkerFlags
875912
args += stripInvalidArguments(buildParameters.swiftCompilerFlags)
@@ -1184,6 +1221,28 @@ public class BuildPlan {
11841221
}
11851222
}
11861223

1224+
for target in dependencies.staticTargets {
1225+
switch target.underlyingTarget {
1226+
case is SwiftTarget:
1227+
// Swift targets are guaranteed to have a corresponding Swift description.
1228+
guard case .swift(let description) = targetMap[target]! else { fatalError() }
1229+
1230+
// Based on the debugging strategy, we either need to pass swiftmodule paths to the
1231+
// product or link in the wrapped module object. This is required for properly debugging
1232+
// Swift products. Debugging statergy is computed based on the current platform we're
1233+
// building for and is nil for the release configuration.
1234+
switch buildParameters.debuggingStrategy {
1235+
case .swiftAST:
1236+
buildProduct.swiftASTs.append(description.moduleOutputPath)
1237+
case .modulewrap:
1238+
buildProduct.objects += [description.wrappedModuleOutputPath]
1239+
case nil:
1240+
break
1241+
}
1242+
default: break
1243+
}
1244+
}
1245+
11871246
buildProduct.staticTargets = dependencies.staticTargets
11881247
buildProduct.dylibs = dependencies.dylibs.map({ productMap[$0]! })
11891248
buildProduct.objects += dependencies.staticTargets.flatMap({ targetMap[$0]!.objects })

Sources/Build/llbuild.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,20 @@ public final class LLBuildManifestGenerator {
296296
buildTarget.outputs.insert(target.moduleOutputPath.pathString)
297297
let tool = SwiftCompilerTool(target: target, inputs: inputs.values)
298298
buildTarget.cmds.insert(Command(name: target.target.getCommandName(config: buildConfig), tool: tool))
299+
300+
// Add commands to perform the module wrapping Swift modules when debugging statergy is `modulewrap`.
301+
if plan.buildParameters.debuggingStrategy == .modulewrap {
302+
let modulewrapTool = ShellTool(
303+
description: "Wrapping AST for \(target.target.name) for debugging",
304+
inputs: [target.moduleOutputPath.pathString],
305+
outputs: [target.wrappedModuleOutputPath.pathString],
306+
args: [target.buildParameters.toolchain.swiftCompiler.pathString,
307+
"-modulewrap", target.moduleOutputPath.pathString, "-o",
308+
target.wrappedModuleOutputPath.pathString],
309+
allowMissingInputs: false)
310+
buildTarget.cmds.insert(Command(name: target.wrappedModuleOutputPath.pathString, tool: modulewrapTool))
311+
}
312+
299313
return buildTarget
300314
}
301315

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ final class BuildPlanTests: XCTestCase {
114114
"-emit-executable",
115115
"@/path/to/build/debug/exe.product/Objects.LinkFileList",
116116
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
117-
"-target", "x86_64-apple-macosx10.10",
117+
"-target", "x86_64-apple-macosx10.10", "-Xlinker", "-add_ast_path",
118+
"-Xlinker", "/path/to/build/debug/exe.swiftmodule", "-Xlinker", "-add_ast_path",
119+
"-Xlinker", "/path/to/build/debug/lib.swiftmodule",
118120
]
119121
#else
120122
let linkArguments = [
@@ -461,6 +463,7 @@ final class BuildPlanTests: XCTestCase {
461463
"@/path/to/build/debug/exe.product/Objects.LinkFileList",
462464
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
463465
"-target", "x86_64-apple-macosx10.10",
466+
"-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/exe.swiftmodule",
464467
])
465468
#else
466469
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
@@ -604,6 +607,8 @@ final class BuildPlanTests: XCTestCase {
604607
"@/path/to/build/debug/PkgPackageTests.product/Objects.LinkFileList",
605608
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
606609
"-target", "x86_64-apple-macosx10.10",
610+
"-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/FooTests.swiftmodule",
611+
"-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/Foo.swiftmodule",
607612
])
608613
#else
609614
XCTAssertEqual(try result.buildProduct(for: "PkgPackageTests").linkArguments(), [
@@ -656,6 +661,7 @@ final class BuildPlanTests: XCTestCase {
656661
"@/path/to/build/debug/exe.product/Objects.LinkFileList",
657662
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
658663
"-target", "x86_64-apple-macosx10.10",
664+
"-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/exe.swiftmodule",
659665
])
660666
#else
661667
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
@@ -749,6 +755,7 @@ final class BuildPlanTests: XCTestCase {
749755
"@/path/to/build/debug/Foo.product/Objects.LinkFileList",
750756
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
751757
"-target", "x86_64-apple-macosx10.10",
758+
"-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/Foo.swiftmodule"
752759
])
753760

754761
XCTAssertEqual(barLinkArgs, [
@@ -757,6 +764,7 @@ final class BuildPlanTests: XCTestCase {
757764
"-module-name", "Bar_Baz", "-emit-library",
758765
"@/path/to/build/debug/Bar-Baz.product/Objects.LinkFileList",
759766
"-target", "x86_64-apple-macosx10.10",
767+
"-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/Bar.swiftmodule"
760768
])
761769
#else
762770
XCTAssertEqual(fooLinkArgs, [
@@ -824,6 +832,7 @@ final class BuildPlanTests: XCTestCase {
824832
"-emit-library",
825833
"@/path/to/build/debug/lib.product/Objects.LinkFileList",
826834
"-target", "x86_64-apple-macosx10.10",
835+
"-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/lib.swiftmodule",
827836
]
828837
#else
829838
let linkArguments = [
@@ -1393,7 +1402,7 @@ final class BuildPlanTests: XCTestCase {
13931402
XCTAssertMatch(exe, [.anySequence, "-DFOO", "-framework", "CoreData", .end])
13941403

13951404
let linkExe = try result.buildProduct(for: "exe").linkArguments()
1396-
XCTAssertMatch(linkExe, [.anySequence, "-lsqlite3", "-llibz", "-framework", "CoreData", "-Ilfoo", "-L", "lbar", .end])
1405+
XCTAssertMatch(linkExe, [.anySequence, "-lsqlite3", "-llibz", "-framework", "CoreData", "-Ilfoo", "-L", "lbar", .anySequence])
13971406
}
13981407
}
13991408

@@ -1665,6 +1674,60 @@ final class BuildPlanTests: XCTestCase {
16651674
"""))
16661675
}
16671676
}
1677+
1678+
func testModulewrap() throws {
1679+
let fs = InMemoryFileSystem(emptyFiles:
1680+
"/Pkg/Sources/exe/main.swift",
1681+
"/Pkg/Sources/lib/lib.swift"
1682+
)
1683+
1684+
let diagnostics = DiagnosticsEngine()
1685+
let graph = loadPackageGraph(root: "/Pkg", fs: fs, diagnostics: diagnostics,
1686+
manifests: [
1687+
Manifest.createV4Manifest(
1688+
name: "Pkg",
1689+
path: "/Pkg",
1690+
url: "/Pkg",
1691+
targets: [
1692+
TargetDescription(name: "exe", dependencies: ["lib"]),
1693+
TargetDescription(name: "lib", dependencies: []),
1694+
]
1695+
),
1696+
]
1697+
)
1698+
XCTAssertNoDiagnostics(diagnostics)
1699+
1700+
let result = BuildPlanResult(plan: try BuildPlan(
1701+
buildParameters: mockBuildParameters(destinationTriple: .x86_64Linux),
1702+
graph: graph, diagnostics: diagnostics, fileSystem: fs)
1703+
)
1704+
1705+
let objects = try result.buildProduct(for: "exe").objects
1706+
XCTAssertTrue(objects.contains(AbsolutePath("/path/to/build/debug/exe.build/exe.swiftmodule.o")), objects.description)
1707+
XCTAssertTrue(objects.contains(AbsolutePath("/path/to/build/debug/lib.build/lib.swiftmodule.o")), objects.description)
1708+
1709+
mktmpdir { path in
1710+
let yaml = path.appending(component: "debug.yaml")
1711+
let llbuild = LLBuildManifestGenerator(result.plan, client: "swift-build")
1712+
try llbuild.generateManifest(at: yaml)
1713+
let contents = try localFileSystem.readFileContents(yaml).description
1714+
XCTAssertMatch(contents, .contains("""
1715+
"/path/to/build/debug/exe.build/exe.swiftmodule.o":
1716+
tool: shell
1717+
description: "Wrapping AST for exe for debugging"
1718+
inputs: ["/path/to/build/debug/exe.swiftmodule"]
1719+
outputs: ["/path/to/build/debug/exe.build/exe.swiftmodule.o"]
1720+
args: ["/fake/path/to/swiftc","-modulewrap","/path/to/build/debug/exe.swiftmodule","-o","/path/to/build/debug/exe.build/exe.swiftmodule.o"]
1721+
1722+
"/path/to/build/debug/lib.build/lib.swiftmodule.o":
1723+
tool: shell
1724+
description: "Wrapping AST for lib for debugging"
1725+
inputs: ["/path/to/build/debug/lib.swiftmodule"]
1726+
outputs: ["/path/to/build/debug/lib.build/lib.swiftmodule.o"]
1727+
args: ["/fake/path/to/swiftc","-modulewrap","/path/to/build/debug/lib.swiftmodule","-o","/path/to/build/debug/lib.build/lib.swiftmodule.o"]
1728+
"""))
1729+
}
1730+
}
16681731
}
16691732

16701733
// MARK:- Test Helpers

0 commit comments

Comments
 (0)