Skip to content

[Explicit Module Builds] Move generation of explicit auto-link flags to a toolchain-specific method #1874

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
May 6, 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
Original file line number Diff line number Diff line change
Expand Up @@ -421,17 +421,7 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
allLinkLibraries.append(linkLibrary)
}
}

for linkLibrary in allLinkLibraries {
if !linkLibrary.isFramework {
commandLine.appendFlag("-l\(linkLibrary.linkName)")
} else {
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("-framework")
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag(linkLibrary.linkName)
}
}
toolchain.addAutoLinkFlags(for: allLinkLibraries, to: &commandLine)
}

/// Resolve all module dependencies of the main module and add them to the lists of
Expand Down
14 changes: 14 additions & 0 deletions Sources/SwiftDriver/Toolchains/DarwinToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,20 @@ public final class DarwinToolchain: Toolchain {
}
}

public func addAutoLinkFlags(for linkLibraries: [LinkLibraryInfo], to commandLine: inout [Job.ArgTemplate]) {
for linkLibrary in linkLibraries {
if !linkLibrary.isFramework {
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("-possible-l\(linkLibrary.linkName)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a rule for what is the linker compatibility from swift-driver? This is quite a new option that doesn't work with older linker and doesn't work with ld-classic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not, but since this entire code-path is currently behind an opt-in flag (-explicit-auto-linking), my thinking is to defer to the client to figure that out, at the moment. We have some precedent in swift-build of enabling compiler features based on the current system linker version.

On Darwin, with older linkers which do not support this option, this feature does not really make much sense anyway since using the non--possible flags changes the semantics too much compared to the current auto-linking mechanism (load directives in object files).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with defer to swift-build to figure out but if you use the option from command-line, there is no link that the error message of unknown linker flag has anything to do with -explicit-auto-linking?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there's not really a link there. I'm not sure I see an obvious way to establish such a link, especially given that we use clang as the linker driver. Maybe we could emit a warning when the user enables this feature, but that's got its own obvious downsides. And I'm not sure it's worth it, given that this is a fully opt-in behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have better idea either.

} else {
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("-possible_framework")
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag(linkLibrary.linkName)
}
}
}

public func defaultSDKPath(_ target: Triple?) throws -> AbsolutePath? {
let hostIsMacOS: Bool
#if os(macOS)
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ public final class GenericUnixToolchain: Toolchain {
}
}

public func addAutoLinkFlags(for linkLibraries: [LinkLibraryInfo], to commandLine: inout [Job.ArgTemplate]) {
for linkLibrary in linkLibraries {
commandLine.appendFlag("-l\(linkLibrary.linkName)")
}
}

/// Retrieve the absolute path for a given tool.
public func getToolPath(_ tool: Tool) throws -> AbsolutePath {
// Check the cache
Expand Down
3 changes: 3 additions & 0 deletions Sources/SwiftDriver/Toolchains/Toolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ public protocol Toolchain {
/// Constructs a proper output file name for a linker product.
func makeLinkerOutputFilename(moduleName: String, type: LinkOutputType) -> String

/// Adds linker flags corresponding to the specified set of link libraries
func addAutoLinkFlags(for linkLibraries: [LinkLibraryInfo], to commandLine: inout [Job.ArgTemplate])

/// Perform platform-specific argument validation.
func validateArgs(_ parsedOptions: inout ParsedOptions,
targetTriple: Triple,
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftDriver/Toolchains/WebAssemblyToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ public final class WebAssemblyToolchain: Toolchain {
}
}

public func addAutoLinkFlags(for linkLibraries: [LinkLibraryInfo], to commandLine: inout [Job.ArgTemplate]) {
for linkLibrary in linkLibraries {
commandLine.appendFlag("-l\(linkLibrary.linkName)")
}
}

/// Retrieve the absolute path for a given tool.
public func getToolPath(_ tool: Tool) throws -> AbsolutePath {
// Check the cache
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftDriver/Toolchains/WindowsToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ extension WindowsToolchain.ToolchainValidationError {
}
}

public func addAutoLinkFlags(for linkLibraries: [LinkLibraryInfo], to commandLine: inout [Job.ArgTemplate]) {
for linkLibrary in linkLibraries {
commandLine.appendFlag("-l\(linkLibrary.linkName)")
}
}

public func defaultSDKPath(_ target: Triple?) throws -> AbsolutePath? {
// TODO(compnerd): replicate the SPM processing of the SDKInfo.plist
if let SDKROOT = env["SDKROOT"] {
Expand Down
54 changes: 54 additions & 0 deletions Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,60 @@ final class ExplicitModuleBuildTests: XCTestCase {
}
}

func testExplicitLinkFlags() throws {
try withTemporaryDirectory { path in
let (_, _, toolchain, _) = try getDriverArtifactsForScanning()

let main = path.appending(component: "testExplicitLinkLibraries.swift")
try localFileSystem.writeFileContents(main, bytes:
"""
import C;import E;import G;
"""
)

let cHeadersPath: AbsolutePath =
try testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "CHeaders")
let bridgingHeaderpath: AbsolutePath =
cHeadersPath.appending(component: "Bridging.h")
let swiftModuleInterfacesPath: AbsolutePath =
try testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "Swift")
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []

// Verify the dependency scanner supports link library reporting
let dependencyOracle = InterModuleDependencyOracle()
let scanLibPath = try XCTUnwrap(toolchain.lookupSwiftScanLib())
try dependencyOracle.verifyOrCreateScannerInstance(swiftScanLibPath: scanLibPath)
guard try dependencyOracle.supportsLinkLibraries() else {
throw XCTSkip("libSwiftScan does not support link library reporting.")
}

let args = ["swiftc",
"-I", cHeadersPath.nativePathString(escaped: true),
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
"-explicit-module-build", "-explicit-auto-linking",
"-import-objc-header", bridgingHeaderpath.nativePathString(escaped: true),
main.nativePathString(escaped: true)] + sdkArgumentsForTesting
var driver = try Driver(args: args)
let jobs = try driver.planBuild()

let linkJob = try jobs.findJob(.link)
if driver.targetTriple.isDarwin {
XCTAssertTrue(linkJob.commandLine.contains("-possible-lswiftCore"))
XCTAssertTrue(linkJob.commandLine.contains("-possible-lswift_StringProcessing"))
XCTAssertTrue(linkJob.commandLine.contains("-possible-lobjc"))
XCTAssertTrue(linkJob.commandLine.contains("-possible-lswift_Concurrency"))
XCTAssertTrue(linkJob.commandLine.contains("-possible-lswiftSwiftOnoneSupport"))
} else {
XCTAssertTrue(linkJob.commandLine.contains("-lswiftCore"))
XCTAssertTrue(linkJob.commandLine.contains("-lswift_StringProcessing"))
XCTAssertTrue(linkJob.commandLine.contains("-lswift_Concurrency"))
XCTAssertTrue(linkJob.commandLine.contains("-lswiftSwiftOnoneSupport"))
}
}
}

func testExplicitLinkLibraries() throws {
try withTemporaryDirectory { path in
let (_, _, toolchain, _) = try getDriverArtifactsForScanning()
Expand Down