From 7f8505d472dc31493dae3313ad0df722322da2c1 Mon Sep 17 00:00:00 2001 From: Dave Inglis Date: Mon, 3 Nov 2025 10:34:12 -0500 Subject: [PATCH] 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). --- .../SpecImplementations/LinkerSpec.swift | 9 +- .../SWBCore/SpecImplementations/Specs.swift | 6 +- .../Tools/LinkerTools.swift | 89 ++++++++----------- .../SWBGenericUnixPlatform/Specs/Unix.xcspec | 10 +++ .../SourcesTaskProducer.swift | 40 ++++++--- .../RunDestinationTestSupport.swift | 7 +- .../Specs/StandardFileTypes.xcspec | 4 +- Sources/SWBWindowsPlatform/Plugin.swift | 2 +- .../SWBWindowsPlatform/Specs/Windows.xcspec | 23 +++++ .../BuildOperationTests.swift | 56 +++++++----- .../CustomTaskBuildOperationTests.swift | 2 +- Tests/SWBBuildSystemTests/LinkerTests.swift | 3 +- Tests/SWBCoreTests/CommandLineSpecTests.swift | 9 +- .../UnitTestTaskConstructionTests.swift | 13 +-- 14 files changed, 173 insertions(+), 100 deletions(-) diff --git a/Sources/SWBCore/SpecImplementations/LinkerSpec.swift b/Sources/SWBCore/SpecImplementations/LinkerSpec.swift index 6149d4c4..a8d7df3f 100644 --- a/Sources/SWBCore/SpecImplementations/LinkerSpec.swift +++ b/Sources/SWBCore/SpecImplementations/LinkerSpec.swift @@ -89,11 +89,12 @@ open class LinkerSpec : CommandLineToolSpec, @unchecked Sendable { /// The path to the privacy file, if one exists. public let privacyFile: Path? - 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) { + public let libPrefix: String? + + 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) { self.kind = kind self.path = path self.mode = mode - self.useSearchPaths = useSearchPaths self.swiftModulePaths = swiftModulePaths self.swiftModuleAdditionalLinkerArgResponseFilePaths = swiftModuleAdditionalLinkerArgResponseFilePaths self.explicitDependencies = explicitDependencies @@ -101,6 +102,10 @@ open class LinkerSpec : CommandLineToolSpec, @unchecked Sendable { self.dsymPath = dsymPath self.xcframeworkSourcePath = xcframeworkSourcePath self.privacyFile = privacyFile + self.libPrefix = prefix + // Only use search paths when no prefix is required or when the prefix matches + let hasValidPrefix = libPrefix.map { path.basename.hasPrefix($0) } ?? true + self.useSearchPaths = hasValidPrefix && useSearchPaths } } diff --git a/Sources/SWBCore/SpecImplementations/Specs.swift b/Sources/SWBCore/SpecImplementations/Specs.swift index cb8cf844..8223ce77 100644 --- a/Sources/SWBCore/SpecImplementations/Specs.swift +++ b/Sources/SWBCore/SpecImplementations/Specs.swift @@ -301,6 +301,9 @@ public class FileTypeSpec : Spec, SpecType, @unchecked Sendable { /// Returns `true` if the `isWrapperFolder` value is set in the XCSpec for the file spec. public let isWrapper: Bool + /// Returns any common prefix this file may have (currently used when specifying searched libraries to linker) + public let prefix: String? + required init(_ parser: SpecParser, _ basedOnSpec: Spec?) { let basedOnFileTypeSpec = basedOnSpec as? FileTypeSpec ?? nil @@ -318,8 +321,8 @@ public class FileTypeSpec : Spec, SpecType, @unchecked Sendable { self.isEmbeddableInProduct = parser.parseBool("IsEmbeddable") ?? false self.validateOnCopy = parser.parseBool("ValidateOnCopy") ?? false self.codeSignOnCopy = parser.parseBool("CodeSignOnCopy") ?? false - self.isWrapper = parser.parseBool("IsWrapperFolder") ?? false + self.prefix = parser.parseString("Prefix") // Parse and ignore keys we have no use for. // @@ -358,7 +361,6 @@ public class FileTypeSpec : Spec, SpecType, @unchecked Sendable { parser.parseStringList("MIMETypes") parser.parseString("Permissions") parser.parseString("PlistStructureDefinition") - parser.parseStringList("Prefix") parser.parseBool("RemoveHeadersOnCopy") parser.parseBool("RequiresHardTabs") parser.parseString("UTI") diff --git a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift index 414ec486..9cfc2d8c 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift @@ -1289,41 +1289,12 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec private static func computeLibraryArgs(_ libraries: [LibrarySpecifier], scope: MacroEvaluationScope) -> (args: [String], inputs: [Path]) { // Construct the library arguments. return libraries.compactMap { specifier -> (args: [String], inputs: [Path]) in - let basename = specifier.path.basename - - // 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. switch specifier.kind { - case .static: - if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".a") { - return (specifier.searchPathFlagsForLd(basename.withoutPrefix("lib").withoutSuffix(".a")), []) - } - return (specifier.absolutePathFlagsForLd(), [specifier.path]) - case .dynamic: - let suffix = ".\(scope.evaluate(BuiltinMacros.DYNAMIC_LIBRARY_EXTENSION))" - if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(suffix) { - return (specifier.searchPathFlagsForLd(basename.withoutPrefix("lib").withoutSuffix(suffix)), []) - } - return (specifier.absolutePathFlagsForLd(), [specifier.path]) - case .textBased: - if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".tbd") { - // .merge and .reexport are not supported for text-based libraries. - return (specifier.searchPathFlagsForLd(basename.withoutPrefix("lib").withoutSuffix(".tbd")), []) - } - return (specifier.absolutePathFlagsForLd(), [specifier.path]) - case .framework: - let frameworkName = Path(basename).withoutSuffix + case .static, .dynamic, .textBased, .framework: if specifier.useSearchPaths { - return (specifier.searchPathFlagsForLd(frameworkName), []) + return (specifier.searchPathFlagsForLd(), []) } - let absPathArgs = specifier.absolutePathFlagsForLd() - let returnPath: Path - if let pathArg = absPathArgs.last, Path(pathArg).basename == frameworkName { - returnPath = Path(pathArg) - } - else { - returnPath = specifier.path - } - return (absPathArgs, [returnPath]) + return (specifier.absolutePathFlagsForLd(), [specifier.path]) case .object: // Object files are added to linker inputs in the sources task producer. return ([], []) @@ -1559,35 +1530,47 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec /// Extensions to `LinkerSpec.LibrarySpecifier` specific to the dynamic linker. fileprivate extension LinkerSpec.LibrarySpecifier { - func searchPathFlagsForLd(_ name: String) -> [String] { + + func searchPathFlagsForLd() -> [String] { + precondition(useSearchPaths) + // Extract basename once to avoid redundant operations + let basename = path.basename + let basenameWithoutSuffix = Path(basename).withoutSuffix + // Strip the prefix if one exists and is present in the basename + let strippedName: String + if let prefix = libPrefix, basename.hasPrefix(prefix) { + strippedName = basenameWithoutSuffix.withoutPrefix(prefix) + } else { + strippedName = basenameWithoutSuffix + } switch (kind, mode) { case (.dynamic, .normal): - return ["-l" + name] + return ["-l" + strippedName] case (.dynamic, .reexport): - return ["-Xlinker", "-reexport-l" + name] + return ["-Xlinker", "-reexport-l" + strippedName] case (.dynamic, .merge): - return ["-Xlinker", "-merge-l" + name] + return ["-Xlinker", "-merge-l" + strippedName] case (.dynamic, .reexport_merge): - return ["-Xlinker", "-no_merge-l" + name] + return ["-Xlinker", "-no_merge-l" + strippedName] case (.dynamic, .weak): - return ["-weak-l" + name] + return ["-weak-l" + strippedName] case (.static, .weak), (.textBased, .weak): - return ["-weak-l" + name] + return ["-weak-l" + strippedName] case (.static, _), (.textBased, _): // Other modes are not supported for these kinds. - return ["-l" + name] + return ["-l" + strippedName] case (.framework, .normal): - return ["-framework", name] + return ["-framework", strippedName] case (.framework, .reexport): - return ["-Xlinker", "-reexport_framework", "-Xlinker", name] + return ["-Xlinker", "-reexport_framework", "-Xlinker", strippedName] case (.framework, .merge): - return ["-Xlinker", "-merge_framework", "-Xlinker", name] + return ["-Xlinker", "-merge_framework", "-Xlinker", strippedName] case (.framework, .reexport_merge): - return ["-Xlinker", "-no_merge_framework", "-Xlinker", name] + return ["-Xlinker", "-no_merge_framework", "-Xlinker", strippedName] case (.framework, .weak): - return ["-weak_framework", name] + return ["-weak_framework", strippedName] case (.object, _): // Object files are added to linker inputs in the sources task producer. return [] @@ -1729,15 +1712,17 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u delegate.warning("Product \(cbc.output.basename) cannot weak-link \(specifier.kind) \(basename)") } - if specifier.useSearchPaths, basename.hasPrefix("lib"), basename.hasSuffix(".a") { + if specifier.useSearchPaths { // Locate using search paths: Add a -l option and *don't* add the path to the library as an input to the task. - return ["-l" + basename.withoutPrefix("lib").withoutSuffix(".a")] - } - else { - // Locate using an absolute path: Add the path as an option and as an input to the task. - inputPaths.append(specifier.path) - return [specifier.path.str] + let basename = specifier.path.basename + let expectedPrefix = specifier.libPrefix ?? "lib" + if basename.hasPrefix(expectedPrefix) { + return ["-l" + Path(basename).withoutSuffix.withoutPrefix(expectedPrefix)] + } } + // Locate using an absolute path: Add the path as an option and as an input to the task. + inputPaths.append(specifier.path) + return [specifier.path.str] case .object: // Object files are added to linker inputs in the sources task producer and so end up in the link-file-list. diff --git a/Sources/SWBGenericUnixPlatform/Specs/Unix.xcspec b/Sources/SWBGenericUnixPlatform/Specs/Unix.xcspec index 446d925b..0922b489 100644 --- a/Sources/SWBGenericUnixPlatform/Specs/Unix.xcspec +++ b/Sources/SWBGenericUnixPlatform/Specs/Unix.xcspec @@ -91,4 +91,14 @@ IconNamePrefix = "TargetPlugin"; DefaultTargetName = "Object File"; }, + { + Domain = generic-unix; + Type = FileType; + Identifier = compiled.mach-o.dylib; + BasedOn = compiled.mach-o; + Prefix = lib; + Extensions = (so); + IsLibrary = YES; + IsDynamicLibrary = YES; + } ) diff --git a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift index c8e6296f..a16e4b75 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift @@ -364,6 +364,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F } // Link using search paths unless the reference is in a different project, in which case we use a full path (to support legacy build locations, primarily). + // On Windows, always use search paths to ensure import libraries (.lib) are found correctly instead of DLLs. let useSearchPaths: Bool switch buildFile.buildableItem { case .reference: @@ -371,7 +372,12 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F case .targetProduct(guid: let guid): if let referenceTarget = self.context.workspaceContext.workspace.target(for: guid) { let referenceProject = self.context.workspaceContext.workspace.project(for: referenceTarget) - useSearchPaths = referenceProject === self.context.project + // Always use search paths on Windows to ensure .lib files are found instead of .dll files + if context.sdkVariant?.llvmTargetTripleSys == "windows" { + useSearchPaths = true + } else { + useSearchPaths = referenceProject === self.context.project + } } else { useSearchPaths = true } @@ -507,6 +513,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F useSearchPaths: useSearchPaths, swiftModulePaths: swiftModulePaths, swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths, + prefix: fileType.prefix, privacyFile: privacyFile ) } else if fileType.conformsTo(context.lookupFileType(identifier: "compiled.mach-o.dylib")!) { @@ -517,6 +524,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F useSearchPaths: useSearchPaths, swiftModulePaths: [:], swiftModuleAdditionalLinkerArgResponseFilePaths: [:], + prefix: fileType.prefix, privacyFile: privacyFile ) } else if fileType.conformsTo(context.lookupFileType(identifier: "sourcecode.text-based-dylib-definition")!) { @@ -527,17 +535,18 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F useSearchPaths: useSearchPaths, swiftModulePaths: [:], swiftModuleAdditionalLinkerArgResponseFilePaths: [:], + prefix: fileType.prefix, privacyFile: privacyFile ) } else if fileType.conformsTo(context.lookupFileType(identifier: "wrapper.framework")!) { - func kindFromSettings(_ settings: Settings) -> LinkerSpec.LibrarySpecifier.Kind? { + func kindFromSettings(_ settings: Settings) -> (kind: LinkerSpec.LibrarySpecifier.Kind, prefix: String?)? { switch settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE) { case "staticlib": - return .static + return (.static, context.lookupFileType(identifier: "archive.ar")?.prefix) case "mh_dylib": - return .dynamic + return (.dynamic, context.lookupFileType(identifier: "compiled.mach-o.dylib")?.prefix) case "mh_object": - return .object + return (.object, nil) default: return nil } @@ -547,9 +556,11 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F let path: Path let dsymPath: Path? let topLevelItemPath: Path? + let prefix: String? if let settingsForRef, let presumedKind = kindFromSettings(settingsForRef), !useSearchPaths { // 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. - kind = presumedKind + kind = presumedKind.kind + prefix = presumedKind.prefix path = settingsForRef.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(settingsForRef.globalScope.evaluate(BuiltinMacros.EXECUTABLE_PATH)).normalize() topLevelItemPath = absolutePath if shouldGenerateDSYM(settingsForRef.globalScope) { @@ -563,6 +574,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F path = absolutePath topLevelItemPath = nil dsymPath = nil + prefix = nil } return LinkerSpec.LibrarySpecifier( @@ -572,6 +584,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F useSearchPaths: useSearchPaths, swiftModulePaths: [:], swiftModuleAdditionalLinkerArgResponseFilePaths: [:], + prefix: prefix, topLevelItemPath: topLevelItemPath, dsymPath: dsymPath, privacyFile: privacyFile @@ -581,7 +594,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F kind: .object, path: absolutePath, mode: buildFile.shouldLinkWeakly ? .weak : .normal, - useSearchPaths: useSearchPaths, + useSearchPaths: false, swiftModulePaths: swiftModulePaths, swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths, privacyFile: privacyFile @@ -621,10 +634,16 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F } let libraryKind: LinkerSpec.LibrarySpecifier.Kind + let prefix: String? switch library.libraryType { - case .framework: libraryKind = .framework; break - case .dynamicLibrary: libraryKind = .dynamic; break - case .staticLibrary: libraryKind = .static; break + case .framework: libraryKind = .framework; prefix = nil + case .dynamicLibrary: + libraryKind = .dynamic; + prefix = context.lookupFileType(identifier: "compiled.mach-o.dylib")?.prefix + case .staticLibrary: + libraryKind = .static + prefix = context.lookupFileType(identifier: "archive.ar")?.prefix + break case let .unknown(fileExtension): // An error of type this type should have already been manifested. assertionFailure("unknown xcframework type: \(fileExtension)") @@ -651,6 +670,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F useSearchPaths: useSearchPaths, swiftModulePaths: [:], swiftModuleAdditionalLinkerArgResponseFilePaths: [:], + prefix: prefix, explicitDependencies: outputFilePaths, xcframeworkSourcePath: xcframeworkPath, privacyFile: nil diff --git a/Sources/SWBTestSupport/RunDestinationTestSupport.swift b/Sources/SWBTestSupport/RunDestinationTestSupport.swift index fa29f90c..c54777b6 100644 --- a/Sources/SWBTestSupport/RunDestinationTestSupport.swift +++ b/Sources/SWBTestSupport/RunDestinationTestSupport.swift @@ -319,7 +319,7 @@ extension RunDestinationInfo { /// 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. /// /// - note: On macOS, the OS provided Swift runtime is used, so `DYLD_LIBRARY_PATH` is never set for Mach-O destinations. - package func hostRuntimeEnvironment(_ core: Core, initialEnvironment: Environment = Environment()) -> Environment { + package func hostRuntimeEnvironment(_ core: Core, initialEnvironment: Environment = Environment()) throws -> Environment { var environment = initialEnvironment guard let toolchain = core.toolchainRegistry.defaultToolchain else { return environment @@ -328,7 +328,10 @@ extension RunDestinationInfo { case .elf: environment.prependPath(key: "LD_LIBRARY_PATH", value: toolchain.path.join("usr/lib/swift/\(platform)").str) case .pe: - environment.prependPath(key: .path, value: core.developerPath.path.join("Runtimes").join(toolchain.version.description).join("usr/bin").str) + let currentPath = Environment.current[.path] ?? "" + let runtimePath = core.developerPath.path.join("Runtimes").join(toolchain.version.description).join("usr/bin").str + let newPath = currentPath.isEmpty ? "\(runtimePath)" : "\(runtimePath);\(currentPath)" + environment[.path] = newPath case .macho: // Fall back to the OS provided Swift runtime break diff --git a/Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec b/Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec index 6afa5833..13695671 100644 --- a/Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec @@ -903,6 +903,7 @@ Class = PBXMachOFileType; BasedOn = compiled.mach-o; Extensions = (dylib); + Prefix = lib; IsLibrary = YES; IsDynamicLibrary = YES; CodeSignOnCopy = YES; @@ -939,6 +940,7 @@ Identifier = sourcecode.text-based-dylib-definition; BasedOn = sourcecode; Extensions = (tbd); + Prefix = lib; IsLibrary = YES; IsDynamicLibrary = YES; CodeSignOnCopy = YES; @@ -1471,7 +1473,7 @@ Identifier = archive.ar; BasedOn = archive; Extensions = (a); - Prefix = (lib); + Prefix = lib; IsLibrary = YES; IsStaticLibrary = YES; ContainsNativeCode = YES; diff --git a/Sources/SWBWindowsPlatform/Plugin.swift b/Sources/SWBWindowsPlatform/Plugin.swift index b3c095ef..c4e07e67 100644 --- a/Sources/SWBWindowsPlatform/Plugin.swift +++ b/Sources/SWBWindowsPlatform/Plugin.swift @@ -170,7 +170,7 @@ struct WindowsSDKRegistryExtension: SDKRegistryExtension { "GENERATE_INTERMEDIATE_TEXT_BASED_STUBS": "NO", "LIBRARY_SEARCH_PATHS": "$(inherited) $(SDKROOT)/usr/lib/swift/windows/$(CURRENT_ARCH)", - "TEST_LIBRARY_SEARCH_PATHS": .plString("\(testingLibraryPath.strWithPosixSlashes)/Testing-$(SWIFT_TESTING_VERSION)/usr/lib/swift/windows/$(CURRENT_ARCH) \(testingLibraryPath.strWithPosixSlashes)/XCTest-$(XCTEST_VERSION)/usr/lib/swift/windows/$(CURRENT_ARCH)"), + "TEST_LIBRARY_SEARCH_PATHS": .plString("\(testingLibraryPath.strWithPosixSlashes)/Testing-$(SWIFT_TESTING_VERSION)/usr/lib/swift/windows/ \(testingLibraryPath.strWithPosixSlashes)/Testing-$(SWIFT_TESTING_VERSION)/usr/lib/swift/windows/$(CURRENT_ARCH) \(testingLibraryPath.strWithPosixSlashes)/XCTest-$(XCTEST_VERSION)/usr/lib/swift/windows/$(CURRENT_ARCH) \(testingLibraryPath.strWithPosixSlashes)/XCTest-$(XCTEST_VERSION)/usr/lib/swift/windows"), "OTHER_SWIFT_FLAGS": "$(inherited) -libc $(DEFAULT_USE_RUNTIME)", "DEFAULT_USE_RUNTIME": "MD", diff --git a/Sources/SWBWindowsPlatform/Specs/Windows.xcspec b/Sources/SWBWindowsPlatform/Specs/Windows.xcspec index 3ed0fccb..c182b273 100644 --- a/Sources/SWBWindowsPlatform/Specs/Windows.xcspec +++ b/Sources/SWBWindowsPlatform/Specs/Windows.xcspec @@ -48,6 +48,7 @@ Identifier = com.apple.product-type.bundle.unit-test; BasedOn = com.apple.product-type.library.dynamic; DefaultBuildProperties = { + ENABLE_TESTING_SEARCH_PATHS = YES; // Index store data is required to discover XCTest tests COMPILER_INDEX_STORE_ENABLE = YES; SWIFT_INDEX_STORE_ENABLE = YES; @@ -87,6 +88,7 @@ BasedOn = default:com.apple.product-type.library.dynamic; HasInfoPlist = NO; DefaultBuildProperties = { + EXECUTABLE_PREFIX = ""; PUBLIC_HEADERS_FOLDER_PATH = ""; PRIVATE_HEADERS_FOLDER_PATH = ""; }; @@ -111,4 +113,25 @@ Identifier = org.swift.product-type.common.object; BasedOn = com.apple.product-type.library.static; }, + + { + Domain = windows; + Type = FileType; + Identifier = archive.ar; + BasedOn = archive; + Extensions = (lib); + IsLibrary = YES; + IsStaticLibrary = YES; + ContainsNativeCode = YES; + }, + + { + Domain = windows; + Type = FileType; + Identifier = compiled.mach-o.dylib; + BasedOn = compiled.mach-o; + Extensions = (dll); + IsLibrary = YES; + IsDynamicLibrary = YES; + } ) diff --git a/Tests/SWBBuildSystemTests/BuildOperationTests.swift b/Tests/SWBBuildSystemTests/BuildOperationTests.swift index 25f8c854..9b83dc32 100644 --- a/Tests/SWBBuildSystemTests/BuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/BuildOperationTests.swift @@ -94,7 +94,8 @@ fileprivate struct BuildOperationTests: CoreBasedTests { ], buildPhases: [ TestSourcesBuildPhase(["dynamic library.swift"]), - ] + ], + productReferenceName: "$(EXECUTABLE_NAME)", ), TestStandardTarget( "staticlib", @@ -108,7 +109,8 @@ fileprivate struct BuildOperationTests: CoreBasedTests { ], buildPhases: [ TestSourcesBuildPhase(["static library.swift"]), - ] + ], + productReferenceName: "$(EXECUTABLE_NAME)", ), ]) let core = try await getCore() @@ -279,7 +281,8 @@ fileprivate struct BuildOperationTests: CoreBasedTests { ], buildPhases: [ TestSourcesBuildPhase(["dynamic.swift"]), - ] + ], + productReferenceName: "$(EXECUTABLE_NAME)", ), TestStandardTarget( "staticlib", @@ -293,7 +296,8 @@ fileprivate struct BuildOperationTests: CoreBasedTests { ], buildPhases: [ TestSourcesBuildPhase(["static.swift"]), - ] + ], + productReferenceName: "$(EXECUTABLE_NAME)", ), ]) let core = try await getCore() @@ -422,7 +426,8 @@ fileprivate struct BuildOperationTests: CoreBasedTests { ], buildPhases: [ TestSourcesBuildPhase(["dynamic.swift"]), - ] + ], + productReferenceName: "$(EXECUTABLE_NAME)", ), TestStandardTarget( "staticlib", @@ -436,7 +441,8 @@ fileprivate struct BuildOperationTests: CoreBasedTests { ], buildPhases: [ TestSourcesBuildPhase(["static.swift"]), - ] + ], + productReferenceName: "$(EXECUTABLE_NAME)", ), ]) let core = try await getCore() @@ -546,7 +552,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { } } - @Test(.requireSDKs(.host), .skipHostOS(.macOS), .skipHostOS(.windows, "cannot find testing library")) + @Test(.requireSDKs(.host), .skipHostOS(.macOS)) func unitTestWithGeneratedEntryPoint() async throws { try await withTemporaryDirectory(removeTreeOnDeinit: false) { (tmpDir: Path) in let testProject = try await TestProject( @@ -582,7 +588,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { buildPhases: [ TestSourcesBuildPhase(), TestFrameworksBuildPhase([ - "MyTests.so" + TestBuildFile(.target("MyTests")) ]) ], dependencies: ["MyTests"] @@ -593,7 +599,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { buildConfigurations: [ TestBuildConfiguration("Debug", buildSettings: [ "LD_RUNPATH_SEARCH_PATHS": "$(RPATH_ORIGIN)", - "LD_DYLIB_INSTALL_NAME": "MyTests.so" + "LD_DYLIB_INSTALL_NAME": "$(EXECUTABLE_NAME)" ]) ], buildPhases: [ @@ -604,7 +610,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { ], dependencies: [ "library" ], - productReferenceName: "MyTests.so" + productReferenceName: "$(EXECUTABLE_NAME)" ), TestStandardTarget( "library", @@ -612,16 +618,16 @@ fileprivate struct BuildOperationTests: CoreBasedTests { buildConfigurations: [ TestBuildConfiguration("Debug", buildSettings: [ "LD_RUNPATH_SEARCH_PATHS": "$(RPATH_ORIGIN)", - "LD_DYLIB_INSTALL_NAME": "liblibrary.so", - // FIXME: Find a way to make these default "EXECUTABLE_PREFIX": "lib", "EXECUTABLE_PREFIX[sdk=windows*]": "", + "LD_DYLIB_INSTALL_NAME": "$(EXECUTABLE_NAME)", ]) ], buildPhases: [ TestSourcesBuildPhase(["library.swift"]), ], + productReferenceName: "$(EXECUTABLE_NAME)", ) ]) let core = try await getCore() @@ -656,21 +662,23 @@ fileprivate struct BuildOperationTests: CoreBasedTests { try await tester.checkBuild(runDestination: destination, persistent: true) { results in results.checkNoErrors() - let environment = destination.hostRuntimeEnvironment(core) + let environment = try destination.hostRuntimeEnvironment(core) do { let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: projectDir.join("build").join("Debug\(destination.builtProductsDirSuffix)").join(core.hostOperatingSystem.imageFormat.executableName(basename: "UnitTestRunner")).str), arguments: [], environment: environment) + #expect(executionResult.exitStatus == .exit(0)) #expect(String(decoding: executionResult.stdout, as: UTF8.self).contains("Executed 1 test")) } do { let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: projectDir.join("build").join("Debug\(destination.builtProductsDirSuffix)").join(core.hostOperatingSystem.imageFormat.executableName(basename: "UnitTestRunner")).str), arguments: ["--testing-library", "swift-testing"], environment: environment) + #expect(executionResult.exitStatus == .exit(0)) #expect(String(decoding: executionResult.stderr, as: UTF8.self).contains("Test run with 1 test ")) } } } } - @Test(.requireSDKs(.host), .skipHostOS(.macOS), .skipHostOS(.windows, "cannot find testing library")) + @Test(.requireSDKs(.host), .skipHostOS(.macOS)) func unitTestWithGeneratedEntryPoint_testabilityDisabled() async throws { try await withTemporaryDirectory(removeTreeOnDeinit: false) { (tmpDir: Path) in let testProject = try await TestProject( @@ -708,7 +716,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { buildPhases: [ TestSourcesBuildPhase(), TestFrameworksBuildPhase([ - "MyTests.so" + TestBuildFile(.target("MyTests")) ]) ], dependencies: ["MyTests"] @@ -719,7 +727,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { buildConfigurations: [ TestBuildConfiguration("Debug", buildSettings: [ "LD_RUNPATH_SEARCH_PATHS": "$(RPATH_ORIGIN)", - "LD_DYLIB_INSTALL_NAME": "MyTests.so" + "LD_DYLIB_INSTALL_NAME": "$(EXECUTABLE_NAME)" ]) ], buildPhases: [ @@ -730,7 +738,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { ], dependencies: [ "library" ], - productReferenceName: "MyTests.so" + productReferenceName: "$(EXECUTABLE_NAME)" ), TestStandardTarget( "library", @@ -738,16 +746,16 @@ fileprivate struct BuildOperationTests: CoreBasedTests { buildConfigurations: [ TestBuildConfiguration("Debug", buildSettings: [ "LD_RUNPATH_SEARCH_PATHS": "$(RPATH_ORIGIN)", - "LD_DYLIB_INSTALL_NAME": "liblibrary.so", - // FIXME: Find a way to make these default "EXECUTABLE_PREFIX": "lib", "EXECUTABLE_PREFIX[sdk=windows*]": "", + "LD_DYLIB_INSTALL_NAME": "$(EXECUTABLE_NAME)", ]) ], buildPhases: [ TestSourcesBuildPhase(["library.swift"]), ], + productReferenceName: "$(EXECUTABLE_NAME)", ) ]) let core = try await getCore() @@ -783,15 +791,21 @@ fileprivate struct BuildOperationTests: CoreBasedTests { results.checkWarning(.prefix("Skipping XCTest discovery for 'MyTests' because it was not built for testing")) results.checkNoErrors() - let environment = destination.hostRuntimeEnvironment(core) + let environment = try destination.hostRuntimeEnvironment(core) do { let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: projectDir.join("build").join("Debug\(destination.builtProductsDirSuffix)").join(core.hostOperatingSystem.imageFormat.executableName(basename: "UnitTestRunner")).str), arguments: [], environment: environment) + #expect(executionResult.exitStatus == .exit(0)) #expect(String(decoding: executionResult.stdout, as: UTF8.self).contains("Executed 0 tests")) } do { let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: projectDir.join("build").join("Debug\(destination.builtProductsDirSuffix)").join(core.hostOperatingSystem.imageFormat.executableName(basename: "UnitTestRunner")).str), arguments: ["--testing-library", "swift-testing"], environment: environment) - #expect(String(decoding: executionResult.stderr, as: UTF8.self).contains("Test run with 1 test ")) + withKnownIssue("On windows the test output indicates no tests ran, needs investigation") { + #expect(executionResult.exitStatus == .exit(0)) + #expect(String(decoding: executionResult.stderr, as: UTF8.self).contains("Test run with 1 test ")) + } when: { + core.hostOperatingSystem == .windows + } } } } diff --git a/Tests/SWBBuildSystemTests/CustomTaskBuildOperationTests.swift b/Tests/SWBBuildSystemTests/CustomTaskBuildOperationTests.swift index 736b48d2..e92f796c 100644 --- a/Tests/SWBBuildSystemTests/CustomTaskBuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/CustomTaskBuildOperationTests.swift @@ -29,7 +29,7 @@ fileprivate struct CustomTaskBuildOperationTests: CoreBasedTests { try await withTemporaryDirectory { tmpDir in let destination: RunDestinationInfo = .host let core = try await getCore() - let environment = destination.hostRuntimeEnvironment(core) + let environment = try destination.hostRuntimeEnvironment(core) let testProject = TestProject( "aProject", diff --git a/Tests/SWBBuildSystemTests/LinkerTests.swift b/Tests/SWBBuildSystemTests/LinkerTests.swift index 8991b51e..ed6fa8cd 100644 --- a/Tests/SWBBuildSystemTests/LinkerTests.swift +++ b/Tests/SWBBuildSystemTests/LinkerTests.swift @@ -223,7 +223,8 @@ fileprivate struct LinkerTests: CoreBasedTests { TestSourcesBuildPhase([ "library.swift" ]) - ] + ], + productReferenceName: "$(EXECUTABLE_NAME)" ), ]) let tester = try await BuildOperationTester(getCore(), testProject, simulated: false) diff --git a/Tests/SWBCoreTests/CommandLineSpecTests.swift b/Tests/SWBCoreTests/CommandLineSpecTests.swift index fa1c49e1..1a137783 100644 --- a/Tests/SWBCoreTests/CommandLineSpecTests.swift +++ b/Tests/SWBCoreTests/CommandLineSpecTests.swift @@ -1346,23 +1346,30 @@ import SWBMacro for useSearchPaths in [true, false] { let searchPathString = useSearchPaths ? "search" : "abs" for mode in LinkerSpec.LibrarySpecifier.Mode.allCases { + let prefix: String? let suffix = "_\(mode)_\(searchPathString)" let filePath: Path switch kind { case .static: filePath = Path.root.join("usr/lib/libfoo\(suffix).a") + prefix = producer.lookupFileType(identifier: "archive.ar")?.prefix case .dynamic: filePath = Path.root.join("usr/lib/libbar\(suffix).dylib") + prefix = producer.lookupFileType(identifier: "compiled.mach-o.dylib")?.prefix case .textBased: filePath = Path.root.join("usr/lib/libbaz\(suffix).tbd") + prefix = producer.lookupFileType(identifier: "sourcecode.text-based-dylib-definition")?.prefix case .framework: filePath = Path.root.join("tmp/Foo\(suffix).framework") + prefix = nil case .object: + prefix = nil continue case .objectLibrary: + prefix = nil continue } - result.append(LinkerSpec.LibrarySpecifier(kind: kind, path: filePath, mode: mode, useSearchPaths: useSearchPaths, swiftModulePaths: [:], swiftModuleAdditionalLinkerArgResponseFilePaths: [:])) + result.append(LinkerSpec.LibrarySpecifier(kind: kind, path: filePath, mode: mode, useSearchPaths: useSearchPaths, swiftModulePaths: [:], swiftModuleAdditionalLinkerArgResponseFilePaths: [:], prefix: prefix)) } } return result diff --git a/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift index f818fbef..71ec7d72 100644 --- a/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift @@ -328,9 +328,7 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests { "UnitTestRunner", type: .swiftpmTestRunner, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [:]) + TestBuildConfiguration("Debug") ], buildPhases: [ TestSourcesBuildPhase(), @@ -346,7 +344,11 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests { buildConfigurations: [ TestBuildConfiguration( "Debug", - buildSettings: [:]) + buildSettings: [ + // FIXME: Find a way to make these default + "EXECUTABLE_PREFIX": "lib", + "EXECUTABLE_PREFIX[sdk=windows*]": "", + ]) ], buildPhases: [ TestSourcesBuildPhase([ @@ -355,7 +357,6 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests { ]) ], dependencies: [], - productReferenceName: "$(EXCTABLE_NAME)" ), ]) let core = try await getCore() @@ -384,7 +385,7 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests { ]) task.checkInputs([ .pathPattern(.suffix("UnitTestTarget.LinkFileList")), - .pathPattern(.or(.suffix("UnitTestTarget.so"), .suffix("UnitTestTarget.dll"))), + .pathPattern(.or(.suffix("/libUnitTestTarget.so"), .suffix("\\UnitTestTarget.dll"))), .namePattern(.any), .namePattern(.any), ])