Skip to content

Commit eb28138

Browse files
authored
Merge pull request #890 from daveinglis/building_tests_fixes_windows
Change how libraries are specified to the linker when using searched libs
2 parents 959cf4b + 90ab51e commit eb28138

File tree

14 files changed

+185
-99
lines changed

14 files changed

+185
-99
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
@@ -1312,41 +1312,12 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
13121312
private static func computeLibraryArgs(_ libraries: [LibrarySpecifier], scope: MacroEvaluationScope) -> (args: [String], inputs: [Path]) {
13131313
// Construct the library arguments.
13141314
return libraries.compactMap { specifier -> (args: [String], inputs: [Path]) in
1315-
let basename = specifier.path.basename
1316-
1317-
// 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.
13181315
switch specifier.kind {
1319-
case .static:
1320-
if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".a") {
1321-
return (specifier.searchPathFlagsForLd(basename.withoutPrefix("lib").withoutSuffix(".a")), [])
1322-
}
1323-
return (specifier.absolutePathFlagsForLd(), [specifier.path])
1324-
case .dynamic:
1325-
let suffix = ".\(scope.evaluate(BuiltinMacros.DYNAMIC_LIBRARY_EXTENSION))"
1326-
if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(suffix) {
1327-
return (specifier.searchPathFlagsForLd(basename.withoutPrefix("lib").withoutSuffix(suffix)), [])
1328-
}
1329-
return (specifier.absolutePathFlagsForLd(), [specifier.path])
1330-
case .textBased:
1331-
if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".tbd") {
1332-
// .merge and .reexport are not supported for text-based libraries.
1333-
return (specifier.searchPathFlagsForLd(basename.withoutPrefix("lib").withoutSuffix(".tbd")), [])
1334-
}
1335-
return (specifier.absolutePathFlagsForLd(), [specifier.path])
1336-
case .framework:
1337-
let frameworkName = Path(basename).withoutSuffix
1316+
case .static, .dynamic, .textBased, .framework:
13381317
if specifier.useSearchPaths {
1339-
return (specifier.searchPathFlagsForLd(frameworkName), [])
1318+
return (specifier.searchPathFlagsForLd(), [])
13401319
}
1341-
let absPathArgs = specifier.absolutePathFlagsForLd()
1342-
let returnPath: Path
1343-
if let pathArg = absPathArgs.last, Path(pathArg).basename == frameworkName {
1344-
returnPath = Path(pathArg)
1345-
}
1346-
else {
1347-
returnPath = specifier.path
1348-
}
1349-
return (absPathArgs, [returnPath])
1320+
return (specifier.absolutePathFlagsForLd(), [specifier.path])
13501321
case .object:
13511322
// Object files are added to linker inputs in the sources task producer.
13521323
return ([], [])
@@ -1582,35 +1553,47 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
15821553

15831554
/// Extensions to `LinkerSpec.LibrarySpecifier` specific to the dynamic linker.
15841555
fileprivate extension LinkerSpec.LibrarySpecifier {
1585-
func searchPathFlagsForLd(_ name: String) -> [String] {
1556+
1557+
func searchPathFlagsForLd() -> [String] {
1558+
precondition(useSearchPaths)
1559+
// Extract basename once to avoid redundant operations
1560+
let basename = path.basename
1561+
let basenameWithoutSuffix = Path(basename).withoutSuffix
1562+
// Strip the prefix if one exists and is present in the basename
1563+
let strippedName: String
1564+
if let prefix = libPrefix, basename.hasPrefix(prefix) {
1565+
strippedName = basenameWithoutSuffix.withoutPrefix(prefix)
1566+
} else {
1567+
strippedName = basenameWithoutSuffix
1568+
}
15861569
switch (kind, mode) {
15871570
case (.dynamic, .normal):
1588-
return ["-l" + name]
1571+
return ["-l" + strippedName]
15891572
case (.dynamic, .reexport):
1590-
return ["-Xlinker", "-reexport-l" + name]
1573+
return ["-Xlinker", "-reexport-l" + strippedName]
15911574
case (.dynamic, .merge):
1592-
return ["-Xlinker", "-merge-l" + name]
1575+
return ["-Xlinker", "-merge-l" + strippedName]
15931576
case (.dynamic, .reexport_merge):
1594-
return ["-Xlinker", "-no_merge-l" + name]
1577+
return ["-Xlinker", "-no_merge-l" + strippedName]
15951578
case (.dynamic, .weak):
1596-
return ["-weak-l" + name]
1579+
return ["-weak-l" + strippedName]
15971580
case (.static, .weak),
15981581
(.textBased, .weak):
1599-
return ["-weak-l" + name]
1582+
return ["-weak-l" + strippedName]
16001583
case (.static, _),
16011584
(.textBased, _):
16021585
// Other modes are not supported for these kinds.
1603-
return ["-l" + name]
1586+
return ["-l" + strippedName]
16041587
case (.framework, .normal):
1605-
return ["-framework", name]
1588+
return ["-framework", strippedName]
16061589
case (.framework, .reexport):
1607-
return ["-Xlinker", "-reexport_framework", "-Xlinker", name]
1590+
return ["-Xlinker", "-reexport_framework", "-Xlinker", strippedName]
16081591
case (.framework, .merge):
1609-
return ["-Xlinker", "-merge_framework", "-Xlinker", name]
1592+
return ["-Xlinker", "-merge_framework", "-Xlinker", strippedName]
16101593
case (.framework, .reexport_merge):
1611-
return ["-Xlinker", "-no_merge_framework", "-Xlinker", name]
1594+
return ["-Xlinker", "-no_merge_framework", "-Xlinker", strippedName]
16121595
case (.framework, .weak):
1613-
return ["-weak_framework", name]
1596+
return ["-weak_framework", strippedName]
16141597
case (.object, _):
16151598
// Object files are added to linker inputs in the sources task producer.
16161599
return []
@@ -1752,15 +1735,17 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u
17521735
delegate.warning("Product \(cbc.output.basename) cannot weak-link \(specifier.kind) \(basename)")
17531736
}
17541737

1755-
if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".a") {
1738+
if specifier.useSearchPaths {
17561739
// Locate using search paths: Add a -l option and *don't* add the path to the library as an input to the task.
1757-
return ["-l" + basename.withoutPrefix("lib").withoutSuffix(".a")]
1758-
}
1759-
else {
1760-
// Locate using an absolute path: Add the path as an option and as an input to the task.
1761-
inputPaths.append(specifier.path)
1762-
return [specifier.path.str]
1740+
let basename = specifier.path.basename
1741+
let expectedPrefix = specifier.libPrefix ?? "lib"
1742+
if basename.hasPrefix(expectedPrefix) {
1743+
return ["-l" + Path(basename).withoutSuffix.withoutPrefix(expectedPrefix)]
1744+
}
17631745
}
1746+
// Locate using an absolute path: Add the path as an option and as an input to the task.
1747+
inputPaths.append(specifier.path)
1748+
return [specifier.path.str]
17641749

17651750
case .object:
17661751
// 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: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ extension RunDestinationInfo {
319319
/// 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.
320320
///
321321
/// - note: On macOS, the OS provided Swift runtime is used, so `DYLD_LIBRARY_PATH` is never set for Mach-O destinations.
322-
package func hostRuntimeEnvironment(_ core: Core, initialEnvironment: Environment = Environment()) -> Environment {
322+
package func hostRuntimeEnvironment(_ core: Core, initialEnvironment: Environment = Environment()) throws -> Environment {
323323
var environment = initialEnvironment
324324
guard let toolchain = core.toolchainRegistry.defaultToolchain else {
325325
return environment
@@ -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

0 commit comments

Comments
 (0)