diff --git a/Fixtures/Miscellaneous/CheckTestLibraryEnvironmentVariable/Tests/CheckTestLibraryEnvironmentVariableTests/CheckTestLibraryEnvironmentVariableTests.swift b/Fixtures/Miscellaneous/CheckTestLibraryEnvironmentVariable/Tests/CheckTestLibraryEnvironmentVariableTests/CheckTestLibraryEnvironmentVariableTests.swift index 933c4a34311..66492767d31 100644 --- a/Fixtures/Miscellaneous/CheckTestLibraryEnvironmentVariable/Tests/CheckTestLibraryEnvironmentVariableTests/CheckTestLibraryEnvironmentVariableTests.swift +++ b/Fixtures/Miscellaneous/CheckTestLibraryEnvironmentVariable/Tests/CheckTestLibraryEnvironmentVariableTests/CheckTestLibraryEnvironmentVariableTests.swift @@ -1,8 +1,21 @@ import XCTest final class CheckTestLibraryEnvironmentVariableTests: XCTestCase { - func testEnvironmentVariable() throws { - let envvar = ProcessInfo.processInfo.environment["SWIFT_TESTING_ENABLED"] - XCTAssertEqual(envvar, "0") + func testEnvironmentVariables() throws { + #if !os(macOS) + try XCTSkipIf(true, "Test is macOS specific") + #endif + + let testingEnabled = ProcessInfo.processInfo.environment["SWIFT_TESTING_ENABLED"] + XCTAssertEqual(testingEnabled, "0") + + if ProcessInfo.processInfo.environment["CONTAINS_SWIFT_TESTING"] != nil { + let frameworkPath = try XCTUnwrap(ProcessInfo.processInfo.environment["DYLD_FRAMEWORK_PATH"]) + let libraryPath = try XCTUnwrap(ProcessInfo.processInfo.environment["DYLD_LIBRARY_PATH"]) + XCTAssertTrue( + frameworkPath.contains("testing") || libraryPath.contains("testing"), + "Expected 'testing' in '\(frameworkPath)' or '\(libraryPath)'" + ) + } } } diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index e3f2877c964..84206c5e4dd 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -209,12 +209,21 @@ enum TestingSupport { if let xctestLocation = toolchain.xctestPath { env.prependPath(key: .path, value: xctestLocation.pathString) } - if let swiftTestingLocation = toolchain.swiftTestingPathOnWindows { + if let swiftTestingLocation = toolchain.swiftTestingPath { env.prependPath(key: .path, value: swiftTestingLocation.pathString) } #endif return env #else + // Add path to swift-testing override if there is one + if let swiftTestingPath = toolchain.swiftTestingPath { + if swiftTestingPath.extension == "framework" { + env.appendPath(key: "DYLD_FRAMEWORK_PATH", value: swiftTestingPath.pathString) + } else { + env.appendPath(key: "DYLD_LIBRARY_PATH", value: swiftTestingPath.pathString) + } + } + // Add the sdk platform path if we have it. // Since XCTestHelper targets macOS, we need the macOS platform paths here. if let sdkPlatformPaths = try? SwiftSDK.sdkPlatformPaths(for: .macOS) { diff --git a/Sources/PackageModel/UserToolchain.swift b/Sources/PackageModel/UserToolchain.swift index 9af61b1f7f8..5a38f747171 100644 --- a/Sources/PackageModel/UserToolchain.swift +++ b/Sources/PackageModel/UserToolchain.swift @@ -404,53 +404,6 @@ public final class UserToolchain: Toolchain { } #endif - /// On MacOS toolchain can shadow SDK content. This method is intended - /// to locate and include swift-testing library from a toolchain before - /// sdk content which to sure that builds that use a custom toolchain - /// always get a custom swift-testing library as well. - static func deriveMacOSSpecificSwiftTestingFlags( - derivedSwiftCompiler: AbsolutePath, - fileSystem: any FileSystem - ) -> (swiftCFlags: [String], linkerFlags: [String]) { - // If this is CommandLineTools all we need to add is a frameworks path. - if let frameworksPath = try? AbsolutePath( - validating: "../../Library/Developer/Frameworks", - relativeTo: resolveSymlinks(derivedSwiftCompiler).parentDirectory - ), fileSystem.exists(frameworksPath.appending("Testing.framework")) { - return (swiftCFlags: [ - "-F", frameworksPath.pathString - ], linkerFlags: [ - "-rpath", frameworksPath.pathString - ]) - } - - guard let toolchainLibDir = try? toolchainLibDir( - swiftCompilerPath: derivedSwiftCompiler - ) else { - return (swiftCFlags: [], linkerFlags: []) - } - - let testingLibDir = toolchainLibDir.appending( - components: ["swift", "macosx", "testing"] - ) - - let testingPluginsDir = toolchainLibDir.appending( - components: ["swift", "host", "plugins", "testing"] - ) - - guard fileSystem.exists(testingLibDir), fileSystem.exists(testingPluginsDir) else { - return (swiftCFlags: [], linkerFlags: []) - } - - return (swiftCFlags: [ - "-I", testingLibDir.pathString, - "-L", testingLibDir.pathString, - "-plugin-path", testingPluginsDir.pathString - ], linkerFlags: [ - "-rpath", testingLibDir.pathString - ]) - } - internal static func deriveSwiftCFlags( triple: Triple, swiftSDK: SwiftSDK, @@ -673,14 +626,33 @@ public final class UserToolchain: Toolchain { var swiftCompilerFlags: [String] = [] var extraLinkerFlags: [String] = [] - if triple.isMacOSX { - let (swiftCFlags, linkerFlags) = Self.deriveMacOSSpecificSwiftTestingFlags( - derivedSwiftCompiler: swiftCompilers.compile, - fileSystem: fileSystem - ) + let swiftTestingPath: AbsolutePath? = try Self.deriveSwiftTestingPath( + derivedSwiftCompiler: swiftCompilers.compile, + swiftSDK: self.swiftSDK, + triple: triple, + environment: environment, + fileSystem: fileSystem + ) + + if triple.isMacOSX, let swiftTestingPath { + // swift-testing in CommandLineTools, needs extra frameworks search path + if swiftTestingPath.extension == "framework" { + swiftCompilerFlags += ["-F", swiftTestingPath.pathString] + } - swiftCompilerFlags += swiftCFlags - extraLinkerFlags += linkerFlags + // Otherwise we must have a custom toolchain, add overrides to find its swift-testing ahead of any in the + // SDK. We expect the library to be in `lib/swift/macosx/testing` and the plugin in + // `lib/swift/host/plugins/testing` + if let pluginsPath = try? AbsolutePath( + validating: "../../host/plugins/testing", + relativeTo: swiftTestingPath + ) { + swiftCompilerFlags += [ + "-I", swiftTestingPath.pathString, + "-L", swiftTestingPath.pathString, + "-plugin-path", pluginsPath.pathString, + ] + } } swiftCompilerFlags += try Self.deriveSwiftCFlags( @@ -774,19 +746,6 @@ public final class UserToolchain: Toolchain { ) } - let swiftTestingPath: AbsolutePath? - if case .custom(_, let useXcrun) = searchStrategy, !useXcrun { - swiftTestingPath = nil - } else { - swiftTestingPath = try Self.deriveSwiftTestingPath( - swiftSDK: self.swiftSDK, - triple: triple, - environment: environment, - fileSystem: fileSystem - ) - } - - self.configuration = .init( librarianPath: librarianPath, swiftCompilerPath: swiftCompilers.manifest, @@ -1006,58 +965,75 @@ public final class UserToolchain: Toolchain { .appending("bin") } } - return .none + return nil } + /// Find the swift-testing path if it is within a path that will need extra search paths. private static func deriveSwiftTestingPath( + derivedSwiftCompiler: AbsolutePath, swiftSDK: SwiftSDK, triple: Triple, environment: Environment, fileSystem: any FileSystem ) throws -> AbsolutePath? { - guard triple.isWindows() else { - return nil - } + if triple.isDarwin() { + // If this is CommandLineTools all we need to add is a frameworks path. + if let frameworksPath = try? AbsolutePath( + validating: "../../Library/Developer/Frameworks", + relativeTo: resolveSymlinks(derivedSwiftCompiler).parentDirectory + ), fileSystem.exists(frameworksPath.appending("Testing.framework")) { + return frameworksPath + } - guard let (platform, info) = getWindowsPlatformInfo( - swiftSDK: swiftSDK, - environment: environment, - fileSystem: fileSystem - ) else { - return nil - } + guard let toolchainLibDir = try? toolchainLibDir(swiftCompilerPath: derivedSwiftCompiler) else { + return nil + } - guard let swiftTestingVersion = info.defaults.swiftTestingVersion else { - return nil - } + let testingLibDir = toolchainLibDir.appending(components: ["swift", "macosx", "testing"]) + if fileSystem.exists(testingLibDir) { + return testingLibDir + } + } else if triple.isWindows() { + guard let (platform, info) = getWindowsPlatformInfo( + swiftSDK: swiftSDK, + environment: environment, + fileSystem: fileSystem + ) else { + return nil + } - let swiftTesting: AbsolutePath = - platform.appending("Developer") - .appending("Library") - .appending("Testing-\(swiftTestingVersion)") - - let binPath: AbsolutePath? = switch triple.arch { - case .x86_64: // amd64 x86_64 x86_64h - swiftTesting.appending("usr") - .appending("bin64") - case .x86: // i386 i486 i586 i686 i786 i886 i986 - swiftTesting.appending("usr") - .appending("bin32") - case .arm: // armv7 and many more - swiftTesting.appending("usr") - .appending("bin32a") - case .aarch64: // aarch6 arm64 - swiftTesting.appending("usr") - .appending("bin64a") - default: - nil - } + guard let swiftTestingVersion = info.defaults.swiftTestingVersion else { + return nil + } - guard let path = binPath, fileSystem.exists(path) else { - return nil + let swiftTesting: AbsolutePath = + platform.appending("Developer") + .appending("Library") + .appending("Testing-\(swiftTestingVersion)") + + let binPath: AbsolutePath? = switch triple.arch { + case .x86_64: // amd64 x86_64 x86_64h + swiftTesting.appending("usr") + .appending("bin64") + case .x86: // i386 i486 i586 i686 i786 i886 i986 + swiftTesting.appending("usr") + .appending("bin32") + case .arm: // armv7 and many more + swiftTesting.appending("usr") + .appending("bin32a") + case .aarch64: // aarch6 arm64 + swiftTesting.appending("usr") + .appending("bin64a") + default: + nil + } + + if let path = binPath, fileSystem.exists(path) { + return path + } } - return path + return nil } public var sdkRootPath: AbsolutePath? { @@ -1084,7 +1060,7 @@ public final class UserToolchain: Toolchain { configuration.xctestPath } - public var swiftTestingPathOnWindows: AbsolutePath? { + public var swiftTestingPath: AbsolutePath? { configuration.swiftTestingPath } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 47dc72b7fec..f0e6b47237f 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -4815,10 +4815,7 @@ final class BuildPlanTests: XCTestCase { "-sdk", "/fake/sdk", ] ) - XCTAssertEqual( - mockToolchain.extraFlags.linkerFlags, - ["-rpath", "/fake/path/lib/swift/macosx/testing"] - ) + XCTAssertNoMatch(mockToolchain.extraFlags.linkerFlags, ["-rpath"]) let observability = ObservabilitySystem.makeForTesting() let graph = try loadModulesGraph( @@ -4853,31 +4850,23 @@ final class BuildPlanTests: XCTestCase { let testProductLinkArgs = try result.buildProduct(for: "Lib").linkArguments() XCTAssertMatch(testProductLinkArgs, [ - .anySequence, "-I", "/fake/path/lib/swift/macosx/testing", "-L", "/fake/path/lib/swift/macosx/testing", - .anySequence, - "-Xlinker", "-rpath", - "-Xlinker", "/fake/path/lib/swift/macosx/testing", ]) let libModuleArgs = try result.moduleBuildDescription(for: "Lib").swift().compileArguments() XCTAssertMatch(libModuleArgs, [ - .anySequence, "-I", "/fake/path/lib/swift/macosx/testing", "-L", "/fake/path/lib/swift/macosx/testing", "-plugin-path", "/fake/path/lib/swift/host/plugins/testing", - .anySequence, ]) XCTAssertNoMatch(libModuleArgs, ["-Xlinker"]) let testModuleArgs = try result.moduleBuildDescription(for: "LibTest").swift().compileArguments() XCTAssertMatch(testModuleArgs, [ - .anySequence, "-I", "/fake/path/lib/swift/macosx/testing", "-L", "/fake/path/lib/swift/macosx/testing", "-plugin-path", "/fake/path/lib/swift/host/plugins/testing", - .anySequence, ]) XCTAssertNoMatch(testModuleArgs, ["-Xlinker"]) } diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index 04a4121d52a..7128a1306b3 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -559,14 +559,15 @@ final class TestCommandTests: CommandsTestCase { } #endif -#if os(macOS) - // "SWIFT_TESTING_ENABLED" is set only on macOS, skip the check on other platforms. func testLibraryEnvironmentVariable() async throws { try await fixture(name: "Miscellaneous/CheckTestLibraryEnvironmentVariable") { fixturePath in - await XCTAssertAsyncNoThrow(try await SwiftPM.Test.execute(packagePath: fixturePath)) + var extraEnv = Environment() + if try UserToolchain.default.swiftTestingPath != nil { + extraEnv["CONTAINS_SWIFT_TESTING"] = "1" + } + await XCTAssertAsyncNoThrow(try await SwiftPM.Test.execute(packagePath: fixturePath, env: extraEnv)) } } -#endif func testXCTestOnlyDoesNotLogAboutNoMatchingTests() async throws { try await fixture(name: "Miscellaneous/TestDiscovery/Simple") { fixturePath in