Skip to content

Commit 6322d4b

Browse files
committed
[WIP] Static versus exporting symbols for Windows
For Windows triples only, creates a parallel build graph for Swift modules, one for static linking using -static, and one for exporting linking which is the default. DLL products consume their direct target dependencies as exporting. All other dependencies use the static versions to eliminate unnecessary symbol exports. The bulk of this is managed by the SwiftModuleBuildDescription which will create a duplicate of itself for the exporting case and set it's own type to static linking. Both modules are fed to the planner to create llbuild swift command tasks. Code is added for dynamic libraries to hook up the correct inputs for exporting the symbols in the libraries targets.
1 parent 7c6da12 commit 6322d4b

File tree

4 files changed

+94
-4
lines changed

4 files changed

+94
-4
lines changed

Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,11 @@ public final class SwiftModuleBuildDescription {
127127

128128
var modulesPath: AbsolutePath {
129129
let suffix = self.buildParameters.suffix
130-
return self.buildParameters.buildPath.appending(component: "Modules\(suffix)")
130+
var path = self.buildParameters.buildPath.appending(component: "Modules\(suffix)")
131+
if self.windowsTargetType == .exporting {
132+
path = path.appending("exporting")
133+
}
134+
return path
131135
}
132136

133137
/// The path to the swiftmodule file after compilation.
@@ -264,6 +268,18 @@ public final class SwiftModuleBuildDescription {
264268
/// Whether to disable sandboxing (e.g. for macros).
265269
private let shouldDisableSandbox: Bool
266270

271+
/// For Windows we default to static objects and create copies for objects
272+
/// That export symbols. This will allow consumers to select which one they want.
273+
public enum WindowsTargetType {
274+
case `static`
275+
case exporting
276+
}
277+
/// The target type. Leave nil for non-Windows behavior.
278+
public let windowsTargetType: WindowsTargetType?
279+
280+
/// The corresponding target symbols exporting (not -static)
281+
public private(set) var windowsExportTarget: SwiftModuleBuildDescription? = nil
282+
267283
/// Create a new target description with target and build parameters.
268284
init(
269285
package: ResolvedPackage,
@@ -319,6 +335,14 @@ public final class SwiftModuleBuildDescription {
319335
observabilityScope: observabilityScope
320336
)
321337

338+
if buildParameters.triple.isWindows() {
339+
// Default to static and add another target for DLLs
340+
self.windowsTargetType = .static
341+
self.windowsExportTarget = .init(windowsExportFor: self)
342+
} else {
343+
self.windowsTargetType = nil
344+
}
345+
322346
if self.shouldEmitObjCCompatibilityHeader {
323347
self.moduleMap = try self.generateModuleMap()
324348
}
@@ -340,6 +364,31 @@ public final class SwiftModuleBuildDescription {
340364
try self.generateTestObservation()
341365
}
342366

367+
/// Private init to set up exporting version of this module
368+
private init(windowsExportFor parent: SwiftModuleBuildDescription) {
369+
self.windowsTargetType = .exporting
370+
self.windowsExportTarget = nil
371+
self.tempsPath = parent.tempsPath.appending("exporting")
372+
373+
// The rest of these are just copied from the parent
374+
self.package = parent.package
375+
self.target = parent.target
376+
self.swiftTarget = parent.swiftTarget
377+
self.toolsVersion = parent.toolsVersion
378+
self.buildParameters = parent.buildParameters
379+
self.macroBuildParameters = parent.macroBuildParameters
380+
self.derivedSources = parent.derivedSources
381+
self.pluginDerivedSources = parent.pluginDerivedSources
382+
self.pluginDerivedResources = parent.pluginDerivedResources
383+
self.testTargetRole = parent.testTargetRole
384+
self.fileSystem = parent.fileSystem
385+
self.buildToolPluginInvocationResults = parent.buildToolPluginInvocationResults
386+
self.prebuildCommandResults = parent.prebuildCommandResults
387+
self.observabilityScope = parent.observabilityScope
388+
self.shouldGenerateTestObservation = parent.shouldGenerateTestObservation
389+
self.shouldDisableSandbox = parent.shouldDisableSandbox
390+
}
391+
343392
private func generateTestObservation() throws {
344393
guard target.type == .test else {
345394
return
@@ -519,6 +568,18 @@ public final class SwiftModuleBuildDescription {
519568
args += ["-parse-as-library"]
520569
}
521570

571+
switch self.windowsTargetType {
572+
case .static:
573+
// Static on Windows
574+
args += ["-static"]
575+
case .exporting:
576+
// Add the static versions to the include path
577+
// FIXME: need to be much more deliberate about what we're including
578+
args += ["-I", self.modulesPath.parentDirectory.pathString]
579+
case .none:
580+
break
581+
}
582+
522583
// Only add the build path to the framework search path if there are binary frameworks to link against.
523584
if !self.libraryBinaryPaths.isEmpty {
524585
args += ["-F", self.buildParameters.buildPath.pathString]

Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ extension LLBuildManifestBuilder {
5353
)
5454
} else {
5555
try self.addCmdWithBuiltinSwiftTool(target, inputs: inputs, cmdOutputs: cmdOutputs)
56+
if let exportTarget = target.windowsExportTarget {
57+
// Generate dynamic module for Windows
58+
let inputs = try self.computeSwiftCompileCmdInputs(exportTarget)
59+
let objectNodes = exportTarget.buildParameters.prepareForIndexing == .off ? try exportTarget.objects.map(Node.file) : []
60+
let moduleNode = Node.file(exportTarget.moduleOutputPath)
61+
let cmdOutputs = objectNodes + [moduleNode]
62+
try self.addCmdWithBuiltinSwiftTool(exportTarget, inputs: inputs, cmdOutputs: cmdOutputs)
63+
self.addTargetCmd(exportTarget, cmdOutputs: cmdOutputs)
64+
try self.addModuleWrapCmd(exportTarget)
65+
}
5666
}
5767

5868
self.addTargetCmd(target, cmdOutputs: cmdOutputs)
@@ -532,7 +542,7 @@ extension LLBuildManifestBuilder {
532542
inputs: cmdOutputs,
533543
outputs: [targetOutput]
534544
)
535-
if self.plan.graph.isInRootPackages(target.target, satisfying: target.buildParameters.buildEnvironment) {
545+
if self.plan.graph.isInRootPackages(target.target, satisfying: target.buildParameters.buildEnvironment), target.windowsTargetType != .exporting {
536546
if !target.isTestTarget {
537547
self.addNode(targetOutput, toTarget: .main)
538548
}
@@ -636,6 +646,11 @@ extension SwiftModuleBuildDescription {
636646
}
637647

638648
public func getLLBuildTargetName() -> String {
639-
self.target.getLLBuildTargetName(buildParameters: self.buildParameters)
649+
let name = self.target.getLLBuildTargetName(buildParameters: self.buildParameters)
650+
if self.windowsTargetType == .exporting {
651+
return "export." + name
652+
} else {
653+
return name
654+
}
640655
}
641656
}

Sources/Build/BuildPlan/BuildPlan+Product.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,18 @@ extension BuildPlan {
101101

102102
buildProduct.staticTargets = dependencies.staticTargets.map(\.module)
103103
buildProduct.dylibs = dependencies.dylibs
104-
buildProduct.objects += try dependencies.staticTargets.flatMap { try $0.objects }
104+
buildProduct.objects += try dependencies.staticTargets.flatMap {
105+
if buildProduct.product.type == .library(.dynamic),
106+
case let .swift(swiftModule) = $0,
107+
let exporting = swiftModule.windowsExportTarget,
108+
buildProduct.product.modules.contains(id: swiftModule.target.id)
109+
{
110+
// On Windows, export symbols from the direct swift targets of the DLL product
111+
return try exporting.objects
112+
} else {
113+
return try $0.objects
114+
}
115+
}
105116
buildProduct.libraryBinaryPaths = dependencies.libraryBinaryPaths
106117
buildProduct.availableTools = dependencies.availableTools
107118
}

Sources/Build/BuildPlan/BuildPlan.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,9 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
537537
switch buildTarget {
538538
case .swift(let target):
539539
try self.plan(swiftTarget: target)
540+
if let exportTarget = target.windowsExportTarget {
541+
try self.plan(swiftTarget: exportTarget)
542+
}
540543
case .clang(let target):
541544
try self.plan(clangTarget: target)
542545
}

0 commit comments

Comments
 (0)