Skip to content

Commit 9f29426

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 bf620f3 commit 9f29426

File tree

13 files changed

+181
-95
lines changed

13 files changed

+181
-95
lines changed

Sources/SWBCore/SpecImplementations/LinkerSpec.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,23 @@ 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], prefix: String? = nil, 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
96-
self.useSearchPaths = useSearchPaths
9798
self.swiftModulePaths = swiftModulePaths
9899
self.swiftModuleAdditionalLinkerArgResponseFilePaths = swiftModuleAdditionalLinkerArgResponseFilePaths
99100
self.explicitDependencies = explicitDependencies
100101
self.topLevelItemPath = topLevelItemPath
101102
self.dsymPath = dsymPath
102103
self.xcframeworkSourcePath = xcframeworkSourcePath
103104
self.privacyFile = privacyFile
105+
self.libPrefix = prefix
106+
// Only use search paths when no prefix is required or when the prefix matches
107+
let hasValidPrefix = libPrefix.map { path.basename.hasPrefix($0) } ?? true
108+
self.useSearchPaths = hasValidPrefix && useSearchPaths
104109
}
105110
}
106111

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 prefix: 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.prefix = parser.parseString("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: 37 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,41 +1289,12 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
12891289
private static func computeLibraryArgs(_ libraries: [LibrarySpecifier], scope: MacroEvaluationScope) -> (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), [])
1295+
return (specifier.searchPathFlagsForLd(), [])
13171296
}
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
1325-
}
1326-
return (absPathArgs, [returnPath])
1297+
return (specifier.absolutePathFlagsForLd(), [specifier.path])
13271298
case .object:
13281299
// Object files are added to linker inputs in the sources task producer.
13291300
return ([], [])
@@ -1559,35 +1530,47 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
15591530

15601531
/// Extensions to `LinkerSpec.LibrarySpecifier` specific to the dynamic linker.
15611532
fileprivate extension LinkerSpec.LibrarySpecifier {
1562-
func searchPathFlagsForLd(_ name: String) -> [String] {
1533+
1534+
func searchPathFlagsForLd() -> [String] {
1535+
precondition(useSearchPaths)
1536+
// Extract basename once to avoid redundant operations
1537+
let basename = path.basename
1538+
let basenameWithoutSuffix = Path(basename).withoutSuffix
1539+
// Strip the prefix if one exists and is present in the basename
1540+
let strippedName: String
1541+
if let prefix = libPrefix, basename.hasPrefix(prefix) {
1542+
strippedName = basenameWithoutSuffix.withoutPrefix(prefix)
1543+
} else {
1544+
strippedName = basenameWithoutSuffix
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 []
@@ -1729,15 +1712,17 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u
17291712
delegate.warning("Product \(cbc.output.basename) cannot weak-link \(specifier.kind) \(basename)")
17301713
}
17311714

1732-
if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".a") {
1715+
if specifier.useSearchPaths {
17331716
// Locate using search paths: Add a -l option and *don't* add the path to the library as an input to the task.
1734-
return ["-l" + basename.withoutPrefix("lib").withoutSuffix(".a")]
1735-
}
1736-
else {
1737-
// Locate using an absolute path: Add the path as an option and as an input to the task.
1738-
inputPaths.append(specifier.path)
1739-
return [specifier.path.str]
1717+
let basename = specifier.path.basename
1718+
let expectedPrefix = specifier.libPrefix ?? "lib"
1719+
if basename.hasPrefix(expectedPrefix) {
1720+
return ["-l" + Path(basename).withoutSuffix.withoutPrefix(expectedPrefix)]
1721+
}
17401722
}
1723+
// Locate using an absolute path: Add the path as an option and as an input to the task.
1724+
inputPaths.append(specifier.path)
1725+
return [specifier.path.str]
17411726

17421727
case .object:
17431728
// 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: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -507,16 +507,25 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
507507
useSearchPaths: useSearchPaths,
508508
swiftModulePaths: swiftModulePaths,
509509
swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths,
510+
prefix: fileType.prefix,
510511
privacyFile: privacyFile
511512
)
512513
} else if fileType.conformsTo(context.lookupFileType(identifier: "compiled.mach-o.dylib")!) {
514+
let adjustedAbsolutePath: Path
515+
// On Windows, ensure import libraries (.lib) are used instead of DLLs.
516+
if context.sdkVariant?.llvmTargetTripleSys == "windows" && absolutePath.fileSuffix.lowercased() == ".dll" {
517+
adjustedAbsolutePath = Path(absolutePath.withoutSuffix + ".lib")
518+
} else {
519+
adjustedAbsolutePath = absolutePath
520+
}
513521
return LinkerSpec.LibrarySpecifier(
514522
kind: .dynamic,
515-
path: absolutePath,
523+
path: adjustedAbsolutePath,
516524
mode: linkageModeForDylib(),
517525
useSearchPaths: useSearchPaths,
518526
swiftModulePaths: [:],
519527
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
528+
prefix: fileType.prefix,
520529
privacyFile: privacyFile
521530
)
522531
} else if fileType.conformsTo(context.lookupFileType(identifier: "sourcecode.text-based-dylib-definition")!) {
@@ -527,17 +536,18 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
527536
useSearchPaths: useSearchPaths,
528537
swiftModulePaths: [:],
529538
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
539+
prefix: fileType.prefix,
530540
privacyFile: privacyFile
531541
)
532542
} else if fileType.conformsTo(context.lookupFileType(identifier: "wrapper.framework")!) {
533-
func kindFromSettings(_ settings: Settings) -> LinkerSpec.LibrarySpecifier.Kind? {
543+
func kindFromSettings(_ settings: Settings) -> (kind: LinkerSpec.LibrarySpecifier.Kind, prefix: String?)? {
534544
switch settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE) {
535545
case "staticlib":
536-
return .static
546+
return (.static, context.lookupFileType(identifier: "archive.ar")?.prefix)
537547
case "mh_dylib":
538-
return .dynamic
548+
return (.dynamic, context.lookupFileType(identifier: "compiled.mach-o.dylib")?.prefix)
539549
case "mh_object":
540-
return .object
550+
return (.object, nil)
541551
default:
542552
return nil
543553
}
@@ -547,9 +557,11 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
547557
let path: Path
548558
let dsymPath: Path?
549559
let topLevelItemPath: Path?
560+
let prefix: String?
550561
if let settingsForRef, let presumedKind = kindFromSettings(settingsForRef), !useSearchPaths {
551562
// 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
563+
kind = presumedKind.kind
564+
prefix = presumedKind.prefix
553565
path = settingsForRef.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(settingsForRef.globalScope.evaluate(BuiltinMacros.EXECUTABLE_PATH)).normalize()
554566
topLevelItemPath = absolutePath
555567
if shouldGenerateDSYM(settingsForRef.globalScope) {
@@ -563,6 +575,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
563575
path = absolutePath
564576
topLevelItemPath = nil
565577
dsymPath = nil
578+
prefix = nil
566579
}
567580

568581
return LinkerSpec.LibrarySpecifier(
@@ -572,6 +585,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
572585
useSearchPaths: useSearchPaths,
573586
swiftModulePaths: [:],
574587
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
588+
prefix: prefix,
575589
topLevelItemPath: topLevelItemPath,
576590
dsymPath: dsymPath,
577591
privacyFile: privacyFile
@@ -581,7 +595,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
581595
kind: .object,
582596
path: absolutePath,
583597
mode: buildFile.shouldLinkWeakly ? .weak : .normal,
584-
useSearchPaths: useSearchPaths,
598+
useSearchPaths: false,
585599
swiftModulePaths: swiftModulePaths,
586600
swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths,
587601
privacyFile: privacyFile
@@ -621,10 +635,16 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
621635
}
622636

623637
let libraryKind: LinkerSpec.LibrarySpecifier.Kind
638+
let prefix: String?
624639
switch library.libraryType {
625-
case .framework: libraryKind = .framework; break
626-
case .dynamicLibrary: libraryKind = .dynamic; break
627-
case .staticLibrary: libraryKind = .static; break
640+
case .framework: libraryKind = .framework; prefix = nil
641+
case .dynamicLibrary:
642+
libraryKind = .dynamic;
643+
prefix = context.lookupFileType(identifier: "compiled.mach-o.dylib")?.prefix
644+
case .staticLibrary:
645+
libraryKind = .static
646+
prefix = context.lookupFileType(identifier: "archive.ar")?.prefix
647+
break
628648
case let .unknown(fileExtension):
629649
// An error of type this type should have already been manifested.
630650
assertionFailure("unknown xcframework type: \(fileExtension)")
@@ -651,6 +671,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
651671
useSearchPaths: useSearchPaths,
652672
swiftModulePaths: [:],
653673
swiftModuleAdditionalLinkerArgResponseFilePaths: [:],
674+
prefix: prefix,
654675
explicitDependencies: outputFilePaths,
655676
xcframeworkSourcePath: xcframeworkPath,
656677
privacyFile: nil

Sources/SWBTestSupport/RunDestinationTestSupport.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,21 @@ extension RunDestinationInfo {
328328
case .elf:
329329
environment.prependPath(key: "LD_LIBRARY_PATH", value: toolchain.path.join("usr/lib/swift/\(platform)").str)
330330
case .pe:
331+
if let path = core.platformRegistry.lookup(name: platform)?.platform?.path.join("Developer/Library") {
332+
func matchesArch(_ path: Path) -> Bool {
333+
switch Architecture.hostStringValue {
334+
case "x86_64":
335+
return path.basename == "bin64"
336+
case "aarch64":
337+
return path.basename == "bin64a"
338+
default:
339+
return false
340+
}
341+
}
342+
for dir in try! localFS.traverse(path, { $0 }).sorted() where localFS.isDirectory(dir) && matchesArch(dir) {
343+
environment.prependPath(key: .path, value: dir.str)
344+
}
345+
}
331346
environment.prependPath(key: .path, value: core.developerPath.path.join("Runtimes").join(toolchain.version.description).join("usr/bin").str)
332347
case .macho:
333348
// Fall back to the OS provided Swift runtime

Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,7 @@
903903
Class = PBXMachOFileType;
904904
BasedOn = compiled.mach-o;
905905
Extensions = (dylib);
906+
Prefix = lib;
906907
IsLibrary = YES;
907908
IsDynamicLibrary = YES;
908909
CodeSignOnCopy = YES;
@@ -939,6 +940,7 @@
939940
Identifier = sourcecode.text-based-dylib-definition;
940941
BasedOn = sourcecode;
941942
Extensions = (tbd);
943+
Prefix = lib;
942944
IsLibrary = YES;
943945
IsDynamicLibrary = YES;
944946
CodeSignOnCopy = YES;
@@ -1471,7 +1473,7 @@
14711473
Identifier = archive.ar;
14721474
BasedOn = archive;
14731475
Extensions = (a);
1474-
Prefix = (lib);
1476+
Prefix = lib;
14751477
IsLibrary = YES;
14761478
IsStaticLibrary = YES;
14771479
ContainsNativeCode = YES;

0 commit comments

Comments
 (0)