Skip to content

Commit 78e7e1c

Browse files
authored
Merge pull request #622 from swiftlang/owenv/auto-linker-driver
Allow setting LINKER_DRIVER=auto to choose an appropriate value for the target sources
2 parents 9612ab4 + b860b49 commit 78e7e1c

File tree

6 files changed

+196
-43
lines changed

6 files changed

+196
-43
lines changed

Sources/SWBCore/PlannedTaskAction.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,14 @@ public struct FileCopyTaskActionContext {
264264
extension FileCopyTaskActionContext {
265265
public init(_ cbc: CommandBuildContext) {
266266
let compilerPath = cbc.producer.clangSpec.resolveExecutablePath(cbc, forLanguageOfFileType: cbc.producer.lookupFileType(languageDialect: .c))
267-
let linkerPath = cbc.producer.ldLinkerSpec.resolveExecutablePath(cbc.producer, Path(cbc.producer.ldLinkerSpec.computeExecutablePath(cbc)))
267+
let linkerPath = cbc.producer.ldLinkerSpec.resolveExecutablePath(cbc.producer, cbc.producer.ldLinkerSpec.computeLinkerPath(cbc, usedCXX: false, lookup: { macro in
268+
switch macro {
269+
case BuiltinMacros.LINKER_DRIVER:
270+
return cbc.scope.namespace.parseString("clang")
271+
default:
272+
return nil
273+
}
274+
}))
268275
let lipoPath = cbc.producer.lipoSpec.resolveExecutablePath(cbc.producer, Path(cbc.producer.lipoSpec.computeExecutablePath(cbc)))
269276

270277
// If we couldn't find clang, skip the special stub binary handling. We may be using an Open Source toolchain which only has Swift. Also skip it for installLoc builds.

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2705,6 +2705,7 @@ public enum LinkerDriverChoice: String, Equatable, Hashable, EnumerationMacroTyp
27052705

27062706
case clang
27072707
case swiftc
2708+
case auto
27082709
}
27092710

27102711
/// Enumeration macro type for the value of the `INFOPLIST_KEY_LSApplicationCategoryType` build setting.

Sources/SWBCore/SpecImplementations/ProductTypes.swift

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -260,31 +260,35 @@ public class ProductTypeSpec : Spec, SpecType, @unchecked Sendable {
260260
}
261261

262262
/// Computes and returns additional arguments to pass to the linker appropriate for the product type. Also returns a list of additional paths to treat as inputs to the link command, if appropriate.
263-
func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope) -> (args: [String], inputs: [Path]) {
263+
func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> (args: [String], inputs: [Path]) {
264264
return ([], [])
265265
}
266266

267-
fileprivate func computeDylibArgs(_ producer: any CommandProducer, _ scope: MacroEvaluationScope) -> [String] {
267+
fileprivate func computeDylibArgs(_ producer: any CommandProducer, _ scope: MacroEvaluationScope, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> [String] {
268268
var args = [String]()
269269

270270
if producer.isApplePlatform {
271-
let compatibilityVersion = scope.evaluate(BuiltinMacros.DYLIB_COMPATIBILITY_VERSION)
271+
let compatibilityVersion = scope.evaluate(BuiltinMacros.DYLIB_COMPATIBILITY_VERSION, lookup: lookup)
272272
if !compatibilityVersion.isEmpty {
273-
switch scope.evaluate(BuiltinMacros.LINKER_DRIVER) {
273+
switch scope.evaluate(BuiltinMacros.LINKER_DRIVER, lookup: lookup) {
274274
case .clang:
275275
args += ["-compatibility_version", compatibilityVersion]
276276
case .swiftc:
277277
args += ["-Xlinker", "-compatibility_version", "-Xlinker", compatibilityVersion]
278+
case .auto:
279+
preconditionFailure("Expected LINKER_DRIVER to be bound to a concrete value")
278280
}
279281
}
280282

281-
let currentVersion = scope.evaluate(BuiltinMacros.DYLIB_CURRENT_VERSION)
283+
let currentVersion = scope.evaluate(BuiltinMacros.DYLIB_CURRENT_VERSION, lookup: lookup)
282284
if !currentVersion.isEmpty {
283-
switch scope.evaluate(BuiltinMacros.LINKER_DRIVER) {
285+
switch scope.evaluate(BuiltinMacros.LINKER_DRIVER, lookup: lookup) {
284286
case .clang:
285287
args += ["-current_version", currentVersion]
286288
case .swiftc:
287289
args += ["-Xlinker", "-current_version", "-Xlinker", currentVersion]
290+
case .auto:
291+
preconditionFailure("Expected LINKER_DRIVER to be bound to a concrete value")
288292
}
289293
}
290294
}
@@ -563,9 +567,9 @@ public class FrameworkProductTypeSpec : BundleProductTypeSpec, @unchecked Sendab
563567
])
564568
*/
565569

566-
override func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope) -> (args: [String], inputs: [Path]) {
570+
override func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> (args: [String], inputs: [Path]) {
567571
if scope.evaluate(BuiltinMacros.MACH_O_TYPE) != "staticlib" {
568-
return (computeDylibArgs(producer, scope), [])
572+
return (computeDylibArgs(producer, scope, lookup: lookup), [])
569573
}
570574
return ([], [])
571575
}
@@ -801,9 +805,9 @@ public final class DynamicLibraryProductTypeSpec : LibraryProductTypeSpec, @unch
801805
return true
802806
}
803807

804-
override func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope) -> (args: [String], inputs: [Path]) {
808+
override func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> (args: [String], inputs: [Path]) {
805809
if scope.evaluate(BuiltinMacros.MACH_O_TYPE) != "staticlib" {
806-
return (computeDylibArgs(producer, scope), [])
810+
return (computeDylibArgs(producer, scope, lookup: lookup), [])
807811
}
808812
return ([], [])
809813
}

Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -233,16 +233,6 @@ public struct DiscoveredLdLinkerToolSpecInfo: DiscoveredCommandLineToolSpecInfo
233233
public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchecked Sendable {
234234
public static let identifier = "com.apple.pbx.linkers.ld"
235235

236-
public override func computeExecutablePath(_ cbc: CommandBuildContext) -> String {
237-
// TODO: We should also provide an "auto" option which chooses based on the source files in the target
238-
switch cbc.scope.evaluate(BuiltinMacros.LINKER_DRIVER) {
239-
case .clang:
240-
return cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "clang")
241-
case .swiftc:
242-
return cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "swiftc")
243-
}
244-
}
245-
246236
override public var toolBasenameAliases: [String] {
247237
// We use clang as our linker, so return ld and libtool in aliases in
248238
// order to parse the errors from the actual linker.
@@ -281,7 +271,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
281271
}
282272

283273
// FIXME: Is there a better way to figure out if we are linking Swift?
284-
private func isUsingSwift(_ usedTools: [CommandLineToolSpec: Set<FileTypeSpec>]) -> Bool {
274+
private static func isUsingSwift(_ usedTools: [CommandLineToolSpec: Set<FileTypeSpec>]) -> Bool {
285275
return usedTools.keys.map({ type(of: $0) }).contains(where: { $0 == SwiftCompilerSpec.self })
286276
}
287277

@@ -304,10 +294,35 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
304294
return runpathSearchPaths
305295
}
306296

297+
static func resolveLinkerDriver(_ cbc: CommandBuildContext, usedTools: [CommandLineToolSpec: Set<FileTypeSpec>]) -> LinkerDriverChoice {
298+
switch cbc.scope.evaluate(BuiltinMacros.LINKER_DRIVER) {
299+
case .clang:
300+
return .clang
301+
case .swiftc:
302+
return.swiftc
303+
case .auto:
304+
if Self.isUsingSwift(usedTools) {
305+
return .swiftc
306+
} else {
307+
return .clang
308+
}
309+
}
310+
}
311+
307312
override public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set<FileTypeSpec>]) async {
313+
let resolvedLinkerDriver = Self.resolveLinkerDriver(cbc, usedTools: usedTools)
314+
let linkerDriverLookup: ((MacroDeclaration) -> MacroStringExpression?) = { macro in
315+
switch macro {
316+
case BuiltinMacros.LINKER_DRIVER:
317+
return cbc.scope.namespace.parseString(resolvedLinkerDriver.rawValue)
318+
default:
319+
return nil
320+
}
321+
}
322+
308323
// Validate that OTHER_LDFLAGS doesn't contain flags for constructs which we have dedicated settings for. This should be expanded over time.
309324
let dyldEnvDiagnosticBehavior: Diagnostic.Behavior = SWBFeatureFlag.useStrictLdEnvironmentBuildSetting.value ? .error : .warning
310-
let originalLdFlags = cbc.scope.evaluate(BuiltinMacros.OTHER_LDFLAGS)
325+
let originalLdFlags = cbc.scope.evaluate(BuiltinMacros.OTHER_LDFLAGS, lookup: linkerDriverLookup)
311326
enumerateLinkerCommandLine(arguments: originalLdFlags) { arg, value in
312327
switch arg {
313328
case "-dyld_env":
@@ -354,7 +369,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
354369
specialArgs.append(contentsOf: sparseSDKSearchPathArguments(cbc))
355370

356371
// Define the linker file list.
357-
let fileListPath = cbc.scope.evaluate(BuiltinMacros.__INPUT_FILE_LIST_PATH__)
372+
let fileListPath = cbc.scope.evaluate(BuiltinMacros.__INPUT_FILE_LIST_PATH__, lookup: linkerDriverLookup)
358373
if !fileListPath.isEmpty {
359374
let contents = OutputByteStream()
360375
for input in cbc.inputs {
@@ -385,7 +400,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
385400
}
386401

387402
// Add linker flags desired by the product type.
388-
let productTypeArgs = cbc.producer.productType?.computeAdditionalLinkerArgs(cbc.producer, scope: cbc.scope)
403+
let productTypeArgs = cbc.producer.productType?.computeAdditionalLinkerArgs(cbc.producer, scope: cbc.scope, lookup: linkerDriverLookup)
389404
specialArgs += productTypeArgs?.args ?? []
390405
inputPaths += productTypeArgs?.inputs ?? []
391406

@@ -425,7 +440,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
425440
inputPaths.append(contentsOf: inputs)
426441
}
427442

428-
let isLinkUsingSwift = isUsingSwift(usedTools)
443+
let isLinkUsingSwift = Self.isUsingSwift(usedTools)
429444
if !isLinkUsingSwift {
430445
// Check if we need to link with Swift's standard library
431446
// when linking a pure Objective-C/C++ target. This might be needed
@@ -483,6 +498,9 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
483498
let frameworkSearchPathsExpr = cbc.scope.namespace.parseStringList(frameworkSearchPaths)
484499

485500
func lookup(_ macro: MacroDeclaration) -> MacroExpression? {
501+
if let result = linkerDriverLookup(macro) {
502+
return result
503+
}
486504
switch macro {
487505
case BuiltinMacros.LD_RUNPATH_SEARCH_PATHS:
488506
return runpathSearchPathsExpr
@@ -589,7 +607,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
589607

590608
// Select the driver to use based on the input file types, replacing the value computed by commandLineFromTemplate().
591609
let usedCXX = usedTools.values.contains(where: { $0.contains(where: { $0.languageDialect?.isPlusPlus ?? false }) })
592-
commandLine[0] = await resolveExecutablePath(cbc, computeLinkerPath(cbc, usedCXX: usedCXX), delegate: delegate).str
610+
commandLine[0] = await resolveExecutablePath(cbc, computeLinkerPath(cbc, usedCXX: usedCXX, lookup: linkerDriverLookup), delegate: delegate).str
593611

594612
let entitlementsSection = cbc.scope.evaluate(BuiltinMacros.LD_ENTITLEMENTS_SECTION)
595613
if !entitlementsSection.isEmpty {
@@ -763,6 +781,15 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
763781
}
764782

765783
public func constructPreviewShimLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set<FileTypeSpec>], rpaths: [String], ldflags: [String]?) async {
784+
let resolvedLinkerDriver = Self.resolveLinkerDriver(cbc, usedTools: usedTools)
785+
let linkerDriverLookup: ((MacroDeclaration) -> MacroStringExpression?) = { macro in
786+
switch macro {
787+
case BuiltinMacros.LINKER_DRIVER:
788+
return cbc.scope.namespace.parseString(resolvedLinkerDriver.rawValue)
789+
default:
790+
return nil
791+
}
792+
}
766793
// Construct the "special args".
767794
var specialArgs = [String]()
768795
var inputPaths = cbc.inputs.map({ $0.absolutePath })
@@ -782,6 +809,9 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
782809
}
783810

784811
func lookup(_ macro: MacroDeclaration) -> MacroExpression? {
812+
if let result = linkerDriverLookup(macro) {
813+
return result
814+
}
785815
switch macro {
786816
case BuiltinMacros.LD_ENTRY_POINT where cbc.scope.previewStyle == .xojit:
787817
return cbc.scope.namespace.parseLiteralString("___debug_blank_executor_main")
@@ -835,7 +865,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
835865

836866
// Select the driver to use based on the input file types, replacing the value computed by commandLineFromTemplate().
837867
let usedCXX = usedTools.values.contains(where: { $0.contains(where: { $0.languageDialect?.isPlusPlus ?? false }) })
838-
commandLine[0] = await resolveExecutablePath(cbc, computeLinkerPath(cbc, usedCXX: usedCXX), delegate: delegate).str
868+
commandLine[0] = await resolveExecutablePath(cbc, computeLinkerPath(cbc, usedCXX: usedCXX, lookup: linkerDriverLookup), delegate: delegate).str
839869

840870
let entitlementsSection = cbc.scope.evaluate(BuiltinMacros.LD_ENTITLEMENTS_SECTION)
841871
if !entitlementsSection.isEmpty {
@@ -1105,31 +1135,45 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
11051135
]
11061136
}
11071137

1108-
private func computeLinkerPath(_ cbc: CommandBuildContext, usedCXX: Bool) -> Path {
1138+
public override func computeExecutablePath(_ cbc: CommandBuildContext) -> String {
1139+
// Placeholder fallback
1140+
return cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "clang")
1141+
}
1142+
1143+
public func computeLinkerPath(_ cbc: CommandBuildContext, usedCXX: Bool, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> Path {
11091144
if usedCXX {
1110-
let perArchValue = cbc.scope.evaluate(BuiltinMacros.PER_ARCH_LDPLUSPLUS)
1145+
let perArchValue = cbc.scope.evaluate(BuiltinMacros.PER_ARCH_LDPLUSPLUS, lookup: lookup)
11111146
if !perArchValue.isEmpty {
1112-
return Path(perArchValue)
1147+
return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: perArchValue))
11131148
}
11141149

1115-
let value = cbc.scope.evaluate(BuiltinMacros.LDPLUSPLUS)
1150+
let value = cbc.scope.evaluate(BuiltinMacros.LDPLUSPLUS, lookup: lookup)
11161151
if !value.isEmpty {
1117-
return Path(value)
1152+
return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: value))
11181153
}
1119-
1120-
return Path("clang++")
11211154
} else {
1122-
let perArchValue = cbc.scope.evaluate(BuiltinMacros.PER_ARCH_LD)
1155+
let perArchValue = cbc.scope.evaluate(BuiltinMacros.PER_ARCH_LD, lookup: lookup)
11231156
if !perArchValue.isEmpty {
1124-
return Path(perArchValue)
1157+
return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: perArchValue))
11251158
}
11261159

1127-
let value = cbc.scope.evaluate(BuiltinMacros.LD)
1160+
let value = cbc.scope.evaluate(BuiltinMacros.LD, lookup: lookup)
11281161
if !value.isEmpty {
1129-
return Path(value)
1162+
return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: value))
11301163
}
1164+
}
11311165

1132-
return Path(computeExecutablePath(cbc))
1166+
switch cbc.scope.evaluate(BuiltinMacros.LINKER_DRIVER, lookup: lookup) {
1167+
case .clang:
1168+
if usedCXX {
1169+
return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "clang++"))
1170+
} else {
1171+
return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "clang"))
1172+
}
1173+
case .swiftc:
1174+
return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "swiftc"))
1175+
case .auto:
1176+
preconditionFailure("LINKER_DRIVER was expected to be bound to a concrete value")
11331177
}
11341178
}
11351179

Tests/SWBCoreTests/CommandLineSpecTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,23 +1560,23 @@ import SWBMacro
15601560
}
15611561

15621562
// Check with just LD.
1563-
for (name, expected) in [("file.c", "SomeCLinker"), ("file.cpp", "clang++")] {
1563+
for (name, expected) in [("file.c", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeCLinker")), ("file.cpp", core.hostOperatingSystem.imageFormat.executableName(basename:"clang++"))] {
15641564
try await check(name: name, expectedLinker: expected, macros: [
15651565
BuiltinMacros.LD: "SomeCLinker"
15661566
// NOTE: One wonders whether this shouldn't change the C++ linker.
15671567
])
15681568
}
15691569

15701570
// Check with LD & LDPLUSPLUS.
1571-
for (name, expected) in [("file.c", "SomeCLinker"), ("file.cpp", "SomeC++Linker")] {
1571+
for (name, expected) in [("file.c", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeCLinker")), ("file.cpp", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeC++Linker"))] {
15721572
try await check(name: name, expectedLinker: expected, macros: [
15731573
BuiltinMacros.LD: "SomeCLinker",
15741574
BuiltinMacros.LDPLUSPLUS: "SomeC++Linker"
15751575
])
15761576
}
15771577

15781578
// Check with arch specific LD.
1579-
for (name, expected) in [("file.c", "SomeCLinker_x86_64"), ("file.cpp", "SomeC++Linker_x86_64")] {
1579+
for (name, expected) in [("file.c", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeCLinker_x86_64")), ("file.cpp", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeC++Linker_x86_64"))] {
15801580
try await check(name: name, expectedLinker: expected, macros: [
15811581
BuiltinMacros.CURRENT_ARCH: "x86_64",
15821582
try core.specRegistry.internalMacroNamespace.declareStringMacro("LD_x86_64"): "SomeCLinker_x86_64",

0 commit comments

Comments
 (0)