Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not add rpath to swift-testing for CLT or custom toolchains #8295

Merged
merged 1 commit into from
Feb 18, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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)'"
)
}
}
}
11 changes: 10 additions & 1 deletion Sources/Commands/Utilities/TestingSupport.swift
Original file line number Diff line number Diff line change
@@ -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) {
188 changes: 82 additions & 106 deletions Sources/PackageModel/UserToolchain.swift
Original file line number Diff line number Diff line change
@@ -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
}

13 changes: 1 addition & 12 deletions Tests/BuildTests/BuildPlanTests.swift
Original file line number Diff line number Diff line change
@@ -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"])
}
9 changes: 5 additions & 4 deletions Tests/CommandsTests/TestCommandTests.swift
Original file line number Diff line number Diff line change
@@ -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