Skip to content

Commit 1a1e849

Browse files
authored
Merge pull request #2348 from aciidb0mb3r/debugging
[Build] Fix debugging for products built by SwiftPM
2 parents 9c5a13a + dfd8a83 commit 1a1e849

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)