Skip to content

PackageToJS: Add --debug-info-format option #302

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 35 additions & 15 deletions Plugins/PackageToJS/Sources/PackageToJS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,22 @@ struct PackageToJS {
var enableCodeCoverage: Bool = false
}

enum DebugInfoFormat: String, CaseIterable {
/// No debug info
case none
/// The all DWARF sections and "name" section
case dwarf
/// Only "name" section
case name
}

struct BuildOptions {
/// Product to build (default: executable target if there's only one)
var product: String?
/// Whether to apply wasm-opt optimizations in release mode (default: true)
var noOptimize: Bool
/// The format of debug info to keep in the final wasm file (default: none)
var debugInfoFormat: DebugInfoFormat
/// The options for packaging
var packageOptions: PackageOptions
}
Expand Down Expand Up @@ -388,7 +399,7 @@ struct PackagingPlanner {
buildOptions: PackageToJS.BuildOptions
) throws -> MiniMake.TaskKey {
let (allTasks, _, _, _) = try planBuildInternal(
make: &make, noOptimize: buildOptions.noOptimize
make: &make, noOptimize: buildOptions.noOptimize, debugInfoFormat: buildOptions.debugInfoFormat
)
return make.addTask(
inputTasks: allTasks, output: BuildPath(phony: "all"), attributes: [.phony, .silent]
Expand All @@ -397,7 +408,8 @@ struct PackagingPlanner {

private func planBuildInternal(
make: inout MiniMake,
noOptimize: Bool
noOptimize: Bool,
debugInfoFormat: PackageToJS.DebugInfoFormat
) throws -> (
allTasks: [MiniMake.TaskKey],
outputDirTask: MiniMake.TaskKey,
Expand Down Expand Up @@ -432,24 +444,32 @@ struct PackagingPlanner {
let finalWasmPath = outputDir.appending(path: wasmFilename)

if shouldOptimize {
// Optimize the wasm in release mode
let wasmWithoutDwarfPath = intermediatesDir.appending(path: wasmFilename + ".no-dwarf")

// First, strip DWARF sections as their existence enables DWARF preserving mode in wasm-opt
let wasmWithoutDwarf = make.addTask(
inputFiles: [selfPath, wasmProductArtifact], inputTasks: [outputDirTask, intermediatesDirTask],
output: wasmWithoutDwarfPath
) {
print("Stripping DWARF debug info...")
try system.wasmOpt(["--strip-dwarf", "--debuginfo"], input: $1.resolve(path: wasmProductArtifact).path, output: $1.resolve(path: $0.output).path)
let wasmOptInputFile: BuildPath
let wasmOptInputTask: MiniMake.TaskKey?
switch debugInfoFormat {
case .dwarf:
// Keep the original wasm file
wasmOptInputFile = wasmProductArtifact
wasmOptInputTask = nil
case .name, .none:
// Optimize the wasm in release mode
wasmOptInputFile = intermediatesDir.appending(path: wasmFilename + ".no-dwarf")
// First, strip DWARF sections as their existence enables DWARF preserving mode in wasm-opt
wasmOptInputTask = make.addTask(
inputFiles: [selfPath, wasmProductArtifact], inputTasks: [outputDirTask, intermediatesDirTask],
output: wasmOptInputFile
) {
print("Stripping DWARF debug info...")
try system.wasmOpt(["--strip-dwarf", "--debuginfo"], input: $1.resolve(path: wasmProductArtifact).path, output: $1.resolve(path: $0.output).path)
}
}
// Then, run wasm-opt with all optimizations
wasm = make.addTask(
inputFiles: [selfPath, wasmWithoutDwarfPath], inputTasks: [outputDirTask, wasmWithoutDwarf],
inputFiles: [selfPath, wasmOptInputFile], inputTasks: [outputDirTask] + (wasmOptInputTask.map { [$0] } ?? []),
output: finalWasmPath
) {
print("Optimizing the wasm file...")
try system.wasmOpt(["-Os", "--debuginfo"], input: $1.resolve(path: wasmWithoutDwarfPath).path, output: $1.resolve(path: $0.output).path)
try system.wasmOpt(["-Os"] + (debugInfoFormat != .none ? ["--debuginfo"] : []), input: $1.resolve(path: wasmOptInputFile).path, output: $1.resolve(path: $0.output).path)
}
} else {
// Copy the wasm product artifact
Expand Down Expand Up @@ -518,7 +538,7 @@ struct PackagingPlanner {
make: inout MiniMake
) throws -> (rootTask: MiniMake.TaskKey, binDir: BuildPath) {
var (allTasks, outputDirTask, intermediatesDirTask, packageJsonTask) = try planBuildInternal(
make: &make, noOptimize: false
make: &make, noOptimize: false, debugInfoFormat: .dwarf
)

// Install npm dependencies used in the test harness
Expand Down
11 changes: 10 additions & 1 deletion Plugins/PackageToJS/Sources/PackageToJSPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,16 @@ extension PackageToJS.BuildOptions {
static func parse(from extractor: inout ArgumentExtractor) -> PackageToJS.BuildOptions {
let product = extractor.extractOption(named: "product").last
let noOptimize = extractor.extractFlag(named: "no-optimize")
let rawDebugInfoFormat = extractor.extractOption(named: "debug-info-format").last
var debugInfoFormat: PackageToJS.DebugInfoFormat = .none
if let rawDebugInfoFormat = rawDebugInfoFormat {
guard let format = PackageToJS.DebugInfoFormat(rawValue: rawDebugInfoFormat) else {
fatalError("Invalid debug info format: \(rawDebugInfoFormat), expected one of \(PackageToJS.DebugInfoFormat.allCases.map(\.rawValue).joined(separator: ", "))")
}
debugInfoFormat = format
}
let packageOptions = PackageToJS.PackageOptions.parse(from: &extractor)
return PackageToJS.BuildOptions(product: product, noOptimize: noOptimize != 0, packageOptions: packageOptions)
return PackageToJS.BuildOptions(product: product, noOptimize: noOptimize != 0, debugInfoFormat: debugInfoFormat, packageOptions: packageOptions)
}

static func help() -> String {
Expand All @@ -313,6 +321,7 @@ extension PackageToJS.BuildOptions {
--no-optimize Whether to disable wasm-opt optimization (default: false)
--use-cdn Whether to use CDN for dependency packages (default: false)
--enable-code-coverage Whether to enable code coverage collection (default: false)
--debug-info-format The format of debug info to keep in the final wasm file (values: none, dwarf, name; default: none)

SUBCOMMANDS:
test Builds and runs tests
Expand Down
2 changes: 2 additions & 0 deletions Plugins/PackageToJS/Tests/ExampleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ extension Trait where Self == ConditionTrait {
let swiftSDKID = try #require(Self.getSwiftSDKID())
try withPackage(at: "Examples/Basic") { packageDir, runSwift in
try runSwift(["package", "--swift-sdk", swiftSDKID, "js"], [:])
try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "--debug-info-format", "dwarf"], [:])
try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "--debug-info-format", "name"], [:])
try runSwift(["package", "--swift-sdk", swiftSDKID, "-Xswiftc", "-DJAVASCRIPTKIT_WITHOUT_WEAKREFS", "js"], [:])
}
}
Expand Down
13 changes: 9 additions & 4 deletions Plugins/PackageToJS/Tests/PackagingPlannerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ import Testing
)
}

typealias DebugInfoFormat = PackageToJS.DebugInfoFormat

@Test(arguments: [
(variant: "debug", configuration: "debug", noOptimize: false),
(variant: "release", configuration: "release", noOptimize: false),
(variant: "release_no_optimize", configuration: "release", noOptimize: true),
(variant: "debug", configuration: "debug", noOptimize: false, debugInfoFormat: DebugInfoFormat.none),
(variant: "release", configuration: "release", noOptimize: false, debugInfoFormat: DebugInfoFormat.none),
(variant: "release_no_optimize", configuration: "release", noOptimize: true, debugInfoFormat: DebugInfoFormat.none),
(variant: "release_dwarf", configuration: "release", noOptimize: false, debugInfoFormat: DebugInfoFormat.dwarf),
(variant: "release_name", configuration: "release", noOptimize: false, debugInfoFormat: DebugInfoFormat.name),
])
func planBuild(variant: String, configuration: String, noOptimize: Bool) throws {
func planBuild(variant: String, configuration: String, noOptimize: Bool, debugInfoFormat: PackageToJS.DebugInfoFormat) throws {
let options = PackageToJS.PackageOptions()
let system = TestPackagingSystem()
let planner = PackagingPlanner(
Expand All @@ -60,6 +64,7 @@ import Testing
buildOptions: PackageToJS.BuildOptions(
product: "test",
noOptimize: noOptimize,
debugInfoFormat: debugInfoFormat,
packageOptions: options
)
)
Expand Down
Loading