Skip to content

Commit d788510

Browse files
committed
Change how libraries are specified to the linker when using searched libs
- remove the platform specifics from computeLibraryArgs (we cannot assume that all libraries have a lib prefix and what there suffix is.) So we now use the FileType prefix and remove any suffix when using searchPathFlagsForLD, moving this into the LinkerSpec.LibrarySpecifier extension, this allows for proper searching of libraries, and linking of dynamic libraries (especially on Windows).
1 parent 3a9a8f6 commit d788510

28 files changed

+209
-235
lines changed

Sources/SWBCore/SpecImplementations/LinkerSpec.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ open class LinkerSpec : CommandLineToolSpec, @unchecked Sendable {
8989
/// The path to the privacy file, if one exists.
9090
public let privacyFile: Path?
9191

92-
public init(kind: Kind, path: Path, mode: Mode, useSearchPaths: Bool, swiftModulePaths: [String: Path], swiftModuleAdditionalLinkerArgResponseFilePaths: [String: Path], explicitDependencies: [Path] = [], topLevelItemPath: Path? = nil, dsymPath: Path? = nil, xcframeworkSourcePath: Path? = nil, privacyFile: Path? = nil) {
92+
public let libPrefix: String?
93+
94+
public init(kind: Kind, path: Path, mode: Mode, useSearchPaths: Bool, swiftModulePaths: [String: Path], swiftModuleAdditionalLinkerArgResponseFilePaths: [String: Path], prefixes: [String] = [], explicitDependencies: [Path] = [], topLevelItemPath: Path? = nil, dsymPath: Path? = nil, xcframeworkSourcePath: Path? = nil, privacyFile: Path? = nil) {
9395
self.kind = kind
9496
self.path = path
9597
self.mode = mode
@@ -101,6 +103,7 @@ open class LinkerSpec : CommandLineToolSpec, @unchecked Sendable {
101103
self.dsymPath = dsymPath
102104
self.xcframeworkSourcePath = xcframeworkSourcePath
103105
self.privacyFile = privacyFile
106+
self.libPrefix = prefixes.first
104107
}
105108
}
106109

Sources/SWBCore/SpecImplementations/Specs.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,9 @@ public class FileTypeSpec : Spec, SpecType, @unchecked Sendable {
301301
/// Returns `true` if the `isWrapperFolder` value is set in the XCSpec for the file spec.
302302
public let isWrapper: Bool
303303

304+
/// Returns any common prefix this file may have (currently used when specifying searched libraries to linker)
305+
public let prefixes: [String]
306+
304307
required init(_ parser: SpecParser, _ basedOnSpec: Spec?) {
305308
let basedOnFileTypeSpec = basedOnSpec as? FileTypeSpec ?? nil
306309

@@ -318,8 +321,8 @@ public class FileTypeSpec : Spec, SpecType, @unchecked Sendable {
318321
self.isEmbeddableInProduct = parser.parseBool("IsEmbeddable") ?? false
319322
self.validateOnCopy = parser.parseBool("ValidateOnCopy") ?? false
320323
self.codeSignOnCopy = parser.parseBool("CodeSignOnCopy") ?? false
321-
322324
self.isWrapper = parser.parseBool("IsWrapperFolder") ?? false
325+
self.prefixes = parser.parseStringList("Prefix") ?? []
323326

324327
// Parse and ignore keys we have no use for.
325328
//
@@ -358,7 +361,6 @@ public class FileTypeSpec : Spec, SpecType, @unchecked Sendable {
358361
parser.parseStringList("MIMETypes")
359362
parser.parseString("Permissions")
360363
parser.parseString("PlistStructureDefinition")
361-
parser.parseStringList("Prefix")
362364
parser.parseBool("RemoveHeadersOnCopy")
363365
parser.parseBool("RequiresHardTabs")
364366
parser.parseString("UTI")

Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift

Lines changed: 40 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
466466
}
467467

468468
// Add the library arguments.
469-
let libraryArgs = LdLinkerSpec.computeLibraryArgs(libraries, scope: cbc.scope)
469+
let libraryArgs = LdLinkerSpec.computeLibraryArgs(libraries, scope: cbc.scope, delegate: delegate)
470470
specialArgs += libraryArgs.args
471471
if SWBFeatureFlag.enableLinkerInputsFromLibrarySpecifiers.value {
472472
inputPaths += libraryArgs.inputs
@@ -888,7 +888,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
888888
var inputPaths = cbc.inputs.map({ $0.absolutePath })
889889

890890
// Add the library arguments.
891-
let libraryArgs = LdLinkerSpec.computeLibraryArgs(libraries, scope: cbc.scope)
891+
let libraryArgs = LdLinkerSpec.computeLibraryArgs(libraries, scope: cbc.scope, delegate: delegate)
892892
specialArgs += libraryArgs.args
893893
if SWBFeatureFlag.enableLinkerInputsFromLibrarySpecifiers.value {
894894
inputPaths += libraryArgs.inputs
@@ -1286,44 +1286,18 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
12861286
/// Compute the list of command line arguments and inputs to pass to the linker, given a list of library specifiers.
12871287
///
12881288
/// Note that `inputs` will only contain values for libraries which are being directly linked by absolute path rather than by using search paths.
1289-
private static func computeLibraryArgs(_ libraries: [LibrarySpecifier], scope: MacroEvaluationScope) -> (args: [String], inputs: [Path]) {
1289+
private static func computeLibraryArgs(_ libraries: [LibrarySpecifier], scope: MacroEvaluationScope, delegate: any TaskGenerationDelegate) -> (args: [String], inputs: [Path]) {
12901290
// Construct the library arguments.
12911291
return libraries.compactMap { specifier -> (args: [String], inputs: [Path]) in
1292-
let basename = specifier.path.basename
1293-
1294-
// FIXME: This isn't a good system, we need to redesign how we talk to the linker w.r.t. search paths and our notion of paths.
12951292
switch specifier.kind {
1296-
case .static:
1297-
if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".a") {
1298-
return (specifier.searchPathFlagsForLd(basename.withoutPrefix("lib").withoutSuffix(".a")), [])
1299-
}
1300-
return (specifier.absolutePathFlagsForLd(), [specifier.path])
1301-
case .dynamic:
1302-
let suffix = ".\(scope.evaluate(BuiltinMacros.DYNAMIC_LIBRARY_EXTENSION))"
1303-
if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(suffix) {
1304-
return (specifier.searchPathFlagsForLd(basename.withoutPrefix("lib").withoutSuffix(suffix)), [])
1305-
}
1306-
return (specifier.absolutePathFlagsForLd(), [specifier.path])
1307-
case .textBased:
1308-
if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".tbd") {
1309-
// .merge and .reexport are not supported for text-based libraries.
1310-
return (specifier.searchPathFlagsForLd(basename.withoutPrefix("lib").withoutSuffix(".tbd")), [])
1311-
}
1312-
return (specifier.absolutePathFlagsForLd(), [specifier.path])
1313-
case .framework:
1314-
let frameworkName = Path(basename).withoutSuffix
1293+
case .static, .dynamic, .textBased, .framework:
13151294
if specifier.useSearchPaths {
1316-
return (specifier.searchPathFlagsForLd(frameworkName), [])
1317-
}
1318-
let absPathArgs = specifier.absolutePathFlagsForLd()
1319-
let returnPath: Path
1320-
if let pathArg = absPathArgs.last, Path(pathArg).basename == frameworkName {
1321-
returnPath = Path(pathArg)
1322-
}
1323-
else {
1324-
returnPath = specifier.path
1295+
let args = specifier.searchPathFlagsForLd()
1296+
if !args.isEmpty { // no search args, fallback to absolute path to library
1297+
return (args, [])
1298+
}
13251299
}
1326-
return (absPathArgs, [returnPath])
1300+
return (specifier.absolutePathFlagsForLd(), [specifier.path])
13271301
case .object:
13281302
// Object files are added to linker inputs in the sources task producer.
13291303
return ([], [])
@@ -1559,35 +1533,44 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
15591533

15601534
/// Extensions to `LinkerSpec.LibrarySpecifier` specific to the dynamic linker.
15611535
fileprivate extension LinkerSpec.LibrarySpecifier {
1562-
func searchPathFlagsForLd(_ name: String) -> [String] {
1536+
func searchPathFlagsForLd() -> [String] {
1537+
let strippedName: String
1538+
if let prefix = libPrefix, path.basename.hasPrefix(prefix) {
1539+
strippedName = Path(path.basename).withoutSuffix.withoutPrefix(prefix)
1540+
} else {
1541+
if libPrefix != nil { // we need a prefix for linking with search paths
1542+
return [] // this will fallback to using absolute paths
1543+
}
1544+
strippedName = Path(path.basename).withoutSuffix
1545+
}
15631546
switch (kind, mode) {
15641547
case (.dynamic, .normal):
1565-
return ["-l" + name]
1548+
return ["-l" + strippedName]
15661549
case (.dynamic, .reexport):
1567-
return ["-Xlinker", "-reexport-l" + name]
1550+
return ["-Xlinker", "-reexport-l" + strippedName]
15681551
case (.dynamic, .merge):
1569-
return ["-Xlinker", "-merge-l" + name]
1552+
return ["-Xlinker", "-merge-l" + strippedName]
15701553
case (.dynamic, .reexport_merge):
1571-
return ["-Xlinker", "-no_merge-l" + name]
1554+
return ["-Xlinker", "-no_merge-l" + strippedName]
15721555
case (.dynamic, .weak):
1573-
return ["-weak-l" + name]
1556+
return ["-weak-l" + strippedName]
15741557
case (.static, .weak),
15751558
(.textBased, .weak):
1576-
return ["-weak-l" + name]
1559+
return ["-weak-l" + strippedName]
15771560
case (.static, _),
15781561
(.textBased, _):
15791562
// Other modes are not supported for these kinds.
1580-
return ["-l" + name]
1563+
return ["-l" + strippedName]
15811564
case (.framework, .normal):
1582-
return ["-framework", name]
1565+
return ["-framework", strippedName]
15831566
case (.framework, .reexport):
1584-
return ["-Xlinker", "-reexport_framework", "-Xlinker", name]
1567+
return ["-Xlinker", "-reexport_framework", "-Xlinker", strippedName]
15851568
case (.framework, .merge):
1586-
return ["-Xlinker", "-merge_framework", "-Xlinker", name]
1569+
return ["-Xlinker", "-merge_framework", "-Xlinker", strippedName]
15871570
case (.framework, .reexport_merge):
1588-
return ["-Xlinker", "-no_merge_framework", "-Xlinker", name]
1571+
return ["-Xlinker", "-no_merge_framework", "-Xlinker", strippedName]
15891572
case (.framework, .weak):
1590-
return ["-weak_framework", name]
1573+
return ["-weak_framework", strippedName]
15911574
case (.object, _):
15921575
// Object files are added to linker inputs in the sources task producer.
15931576
return []
@@ -1724,15 +1707,17 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u
17241707
delegate.warning("Product \(cbc.output.basename) cannot weak-link \(specifier.kind) \(basename)")
17251708
}
17261709

1727-
if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".a") {
1710+
if specifier.useSearchPaths {
17281711
// Locate using search paths: Add a -l option and *don't* add the path to the library as an input to the task.
1729-
return ["-l" + basename.withoutPrefix("lib").withoutSuffix(".a")]
1730-
}
1731-
else {
1732-
// Locate using an absolute path: Add the path as an option and as an input to the task.
1733-
inputPaths.append(specifier.path)
1734-
return [specifier.path.str]
1712+
let basename = specifier.path.basename
1713+
let expectedPrefix = specifier.libPrefix ?? "lib"
1714+
if basename.hasPrefix(expectedPrefix) {
1715+
return ["-l" + Path(basename).withoutSuffix.withoutPrefix(expectedPrefix)]
1716+
}
17351717
}
1718+
// Locate using an absolute path: Add the path as an option and as an input to the task.
1719+
inputPaths.append(specifier.path)
1720+
return [specifier.path.str]
17361721

17371722
case .object:
17381723
// Object files are added to linker inputs in the sources task producer and so end up in the link-file-list.

Sources/SWBGenericUnixPlatform/Specs/Unix.xcspec

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,14 @@
9191
IconNamePrefix = "TargetPlugin";
9292
DefaultTargetName = "Object File";
9393
},
94+
{
95+
Domain = generic-unix;
96+
Type = FileType;
97+
Identifier = compiled.mach-o.dylib;
98+
BasedOn = compiled.mach-o;
99+
Prefix = (lib);
100+
Extensions = (so);
101+
IsLibrary = YES;
102+
IsDynamicLibrary = YES;
103+
}
94104
)

Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
507507
useSearchPaths: useSearchPaths,
508508
swiftModulePaths: swiftModulePaths,
509509
swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths,
510+
prefixes: fileType.prefixes,
510511
privacyFile: privacyFile
511512
)
512513
} else if fileType.conformsTo(context.lookupFileType(identifier: "compiled.mach-o.dylib")!) {
@@ -517,6 +518,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
517518
useSearchPaths: useSearchPaths,
518519
swiftModulePaths: [:],
519520
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
521+
prefixes: fileType.prefixes,
520522
privacyFile: privacyFile
521523
)
522524
} else if fileType.conformsTo(context.lookupFileType(identifier: "sourcecode.text-based-dylib-definition")!) {
@@ -527,17 +529,18 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
527529
useSearchPaths: useSearchPaths,
528530
swiftModulePaths: [:],
529531
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
532+
prefixes: fileType.prefixes,
530533
privacyFile: privacyFile
531534
)
532535
} else if fileType.conformsTo(context.lookupFileType(identifier: "wrapper.framework")!) {
533-
func kindFromSettings(_ settings: Settings) -> LinkerSpec.LibrarySpecifier.Kind? {
536+
func kindFromSettings(_ settings: Settings) -> (kind: LinkerSpec.LibrarySpecifier.Kind, prefixes: [String])? {
534537
switch settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE) {
535538
case "staticlib":
536-
return .static
539+
return (.static, context.lookupFileType(identifier: "archive.ar")?.prefixes ?? [])
537540
case "mh_dylib":
538-
return .dynamic
541+
return (.dynamic, context.lookupFileType(identifier: "compiled.mach-o.dylib")?.prefixes ?? [])
539542
case "mh_object":
540-
return .object
543+
return (.object, [])
541544
default:
542545
return nil
543546
}
@@ -547,9 +550,11 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
547550
let path: Path
548551
let dsymPath: Path?
549552
let topLevelItemPath: Path?
553+
let prefixes: [String]
550554
if let settingsForRef, let presumedKind = kindFromSettings(settingsForRef), !useSearchPaths {
551555
// If we have a Settings from a cross-project reference, use the _actual_ library path. This prevents downstream code from reconstituting the framework path by joining the framework path with the basename of the framework, which won't be correct for deep frameworks which also need the Versions/A path component.
552-
kind = presumedKind
556+
kind = presumedKind.kind
557+
prefixes = presumedKind.prefixes
553558
path = settingsForRef.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(settingsForRef.globalScope.evaluate(BuiltinMacros.EXECUTABLE_PATH)).normalize()
554559
topLevelItemPath = absolutePath
555560
if shouldGenerateDSYM(settingsForRef.globalScope) {
@@ -563,6 +568,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
563568
path = absolutePath
564569
topLevelItemPath = nil
565570
dsymPath = nil
571+
prefixes = []
566572
}
567573

568574
return LinkerSpec.LibrarySpecifier(
@@ -572,6 +578,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
572578
useSearchPaths: useSearchPaths,
573579
swiftModulePaths: [:],
574580
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
581+
prefixes: prefixes,
575582
topLevelItemPath: topLevelItemPath,
576583
dsymPath: dsymPath,
577584
privacyFile: privacyFile
@@ -581,7 +588,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
581588
kind: .object,
582589
path: absolutePath,
583590
mode: buildFile.shouldLinkWeakly ? .weak : .normal,
584-
useSearchPaths: useSearchPaths,
591+
useSearchPaths: false,
585592
swiftModulePaths: swiftModulePaths,
586593
swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths,
587594
privacyFile: privacyFile
@@ -621,10 +628,16 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
621628
}
622629

623630
let libraryKind: LinkerSpec.LibrarySpecifier.Kind
631+
let prefixes: [String]
624632
switch library.libraryType {
625-
case .framework: libraryKind = .framework; break
626-
case .dynamicLibrary: libraryKind = .dynamic; break
627-
case .staticLibrary: libraryKind = .static; break
633+
case .framework: libraryKind = .framework; prefixes = []
634+
case .dynamicLibrary:
635+
libraryKind = .dynamic;
636+
prefixes = context.lookupFileType(identifier: "compiled.mach-o.dylib")?.prefixes ?? []
637+
case .staticLibrary:
638+
libraryKind = .static
639+
prefixes = context.lookupFileType(identifier: "archive.ar")?.prefixes ?? []
640+
break
628641
case let .unknown(fileExtension):
629642
// An error of type this type should have already been manifested.
630643
assertionFailure("unknown xcframework type: \(fileExtension)")
@@ -651,6 +664,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
651664
useSearchPaths: useSearchPaths,
652665
swiftModulePaths: [:],
653666
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
667+
prefixes: prefixes,
654668
explicitDependencies: outputFilePaths,
655669
xcframeworkSourcePath: xcframeworkPath,
656670
privacyFile: nil

Sources/SWBTestSupport/RunDestinationTestSupport.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ extension RunDestinationInfo {
320320
/// An `Environment` object with `PATH` or `LD_LIBRARY_PATH` set appropriately pointing into the toolchain to be able to run a built Swift binary in tests.
321321
///
322322
/// - note: On macOS, the OS provided Swift runtime is used, so `DYLD_LIBRARY_PATH` is never set for Mach-O destinations.
323-
package func hostRuntimeEnvironment(_ core: Core, initialEnvironment: Environment = Environment()) -> Environment {
323+
package func hostRuntimeEnvironment(_ core: Core, initialEnvironment: Environment = Environment()) throws -> Environment {
324324
var environment = initialEnvironment
325325
guard let toolchain = core.toolchainRegistry.defaultToolchain else {
326326
return environment
@@ -329,7 +329,10 @@ extension RunDestinationInfo {
329329
case .elf:
330330
environment.prependPath(key: "LD_LIBRARY_PATH", value: toolchain.path.join("usr/lib/swift/\(platform)").str)
331331
case .pe:
332-
environment.prependPath(key: .path, value: core.developerPath.path.join("Runtimes").join(toolchain.version.description).join("usr/bin").str)
332+
let currentPath = Environment.current[.path] ?? ""
333+
let runtimePath = core.developerPath.path.join("Runtimes").join(toolchain.version.description).join("usr/bin").str
334+
let newPath = currentPath.isEmpty ? "\(runtimePath)" : "\(runtimePath);\(currentPath)"
335+
environment[.path] = newPath
333336
case .macho:
334337
// Fall back to the OS provided Swift runtime
335338
break

0 commit comments

Comments
 (0)