Skip to content

Commit f6e00e6

Browse files
authored
[6.2] Include search path for Swift Testing's macro plugin from toolchain if present (#8706)
Cherry-pick #8670 to 6.2
1 parent 5e4b608 commit f6e00e6

File tree

2 files changed

+158
-11
lines changed

2 files changed

+158
-11
lines changed

Sources/PackageModel/UserToolchain.swift

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -636,26 +636,28 @@ public final class UserToolchain: Toolchain {
636636
)
637637

638638
if triple.isMacOSX, let swiftTestingPath {
639-
// swift-testing in CommandLineTools, needs extra frameworks search path
639+
// Swift Testing is a framework (e.g. from CommandLineTools) so use -F.
640640
if swiftTestingPath.extension == "framework" {
641641
swiftCompilerFlags += ["-F", swiftTestingPath.pathString]
642-
}
643642

644-
// Otherwise we must have a custom toolchain, add overrides to find its swift-testing ahead of any in the
645-
// SDK. We expect the library to be in `lib/swift/macosx/testing` and the plugin in
646-
// `lib/swift/host/plugins/testing`
647-
if let pluginsPath = try? AbsolutePath(
648-
validating: "../../host/plugins/testing",
649-
relativeTo: swiftTestingPath
650-
) {
643+
// Otherwise Swift Testing is assumed to be a swiftmodule + library, so use -I and -L.
644+
} else {
651645
swiftCompilerFlags += [
652646
"-I", swiftTestingPath.pathString,
653647
"-L", swiftTestingPath.pathString,
654-
"-plugin-path", pluginsPath.pathString,
655648
]
656649
}
657650
}
658651

652+
// Specify the plugin path for Swift Testing's macro plugin if such a
653+
// path exists in this toolchain.
654+
if let swiftTestingPluginPath = Self.deriveSwiftTestingPluginPath(
655+
derivedSwiftCompiler: swiftCompilers.compile,
656+
fileSystem: fileSystem
657+
) {
658+
swiftCompilerFlags += ["-plugin-path", swiftTestingPluginPath.pathString]
659+
}
660+
659661
swiftCompilerFlags += try Self.deriveSwiftCFlags(
660662
triple: triple,
661663
swiftSDK: swiftSDK,
@@ -1037,6 +1039,35 @@ public final class UserToolchain: Toolchain {
10371039
return nil
10381040
}
10391041

1042+
/// Derive the plugin path needed to locate the Swift Testing macro plugin,
1043+
/// if such a path exists in the toolchain of the specified compiler.
1044+
///
1045+
/// - Parameters:
1046+
/// - derivedSwiftCompiler: The derived path of the Swift compiler to use
1047+
/// when deriving the Swift Testing plugin path.
1048+
/// - fileSystem: The file system instance to use when validating the path
1049+
/// to return.
1050+
///
1051+
/// - Returns: A path to the directory containing Swift Testing's macro
1052+
/// plugin, or `nil` if the path does not exist or cannot be determined.
1053+
///
1054+
/// The path returned is a directory containing a library, suitable for
1055+
/// passing to a client compiler via the `-plugin-path` flag.
1056+
private static func deriveSwiftTestingPluginPath(
1057+
derivedSwiftCompiler: Basics.AbsolutePath,
1058+
fileSystem: any FileSystem
1059+
) -> AbsolutePath? {
1060+
guard let toolchainLibDir = try? toolchainLibDir(swiftCompilerPath: derivedSwiftCompiler) else {
1061+
return nil
1062+
}
1063+
1064+
if let pluginsPath = try? AbsolutePath(validating: "swift/host/plugins/testing", relativeTo: toolchainLibDir), fileSystem.exists(pluginsPath) {
1065+
return pluginsPath
1066+
}
1067+
1068+
return nil
1069+
}
1070+
10401071
public var sdkRootPath: AbsolutePath? {
10411072
configuration.sdkRootPath
10421073
}

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4997,6 +4997,122 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
49974997
])
49984998
}
49994999

5000+
func testSwiftTestingFlagsOnMacOSWithoutCustomToolchain() async throws {
5001+
#if !os(macOS)
5002+
// This is testing swift-testing in a toolchain which is macOS only feature.
5003+
try XCTSkipIf(true, "test is only supported on macOS")
5004+
#endif
5005+
5006+
let fs = InMemoryFileSystem(
5007+
emptyFiles:
5008+
"/fake/path/lib/swift/host/plugins/testing/libTestingMacros.dylib",
5009+
"/Pkg/Sources/Lib/main.swift",
5010+
"/Pkg/Tests/LibTest/test.swift"
5011+
)
5012+
try fs.createMockToolchain()
5013+
5014+
let userSwiftSDK = SwiftSDK(
5015+
hostTriple: .x86_64MacOS,
5016+
targetTriple: .x86_64MacOS,
5017+
toolset: .init(
5018+
knownTools: [
5019+
.cCompiler: .init(extraCLIOptions: []),
5020+
.swiftCompiler: .init(extraCLIOptions: []),
5021+
],
5022+
rootPaths: ["/fake/path/to"]
5023+
),
5024+
pathsConfiguration: .init(
5025+
sdkRootPath: "/fake/sdk",
5026+
swiftResourcesPath: "/fake/lib/swift",
5027+
swiftStaticResourcesPath: "/fake/lib/swift_static"
5028+
)
5029+
)
5030+
5031+
let env = Environment.mockEnvironment
5032+
let mockToolchain = try UserToolchain(
5033+
swiftSDK: userSwiftSDK,
5034+
environment: env,
5035+
searchStrategy: .custom(
5036+
searchPaths: getEnvSearchPaths(
5037+
pathString: env[.path],
5038+
currentWorkingDirectory: fs.currentWorkingDirectory
5039+
),
5040+
useXcrun: true
5041+
),
5042+
fileSystem: fs
5043+
)
5044+
5045+
XCTAssertEqual(
5046+
mockToolchain.extraFlags.swiftCompilerFlags,
5047+
[
5048+
"-plugin-path", "/fake/path/lib/swift/host/plugins/testing",
5049+
"-sdk", "/fake/sdk",
5050+
]
5051+
)
5052+
XCTAssertNoMatch(mockToolchain.extraFlags.linkerFlags, ["-rpath"])
5053+
XCTAssertNoMatch(mockToolchain.extraFlags.swiftCompilerFlags, [
5054+
"-I", "/fake/path/lib/swift/macosx/testing",
5055+
"-L", "/fake/path/lib/swift/macosx/testing",
5056+
])
5057+
5058+
let observability = ObservabilitySystem.makeForTesting()
5059+
let graph = try loadModulesGraph(
5060+
fileSystem: fs,
5061+
manifests: [
5062+
Manifest.createRootManifest(
5063+
displayName: "Pkg",
5064+
path: "/Pkg",
5065+
targets: [
5066+
TargetDescription(name: "Lib", dependencies: []),
5067+
TargetDescription(
5068+
name: "LibTest",
5069+
dependencies: ["Lib"],
5070+
type: .test
5071+
),
5072+
]
5073+
),
5074+
],
5075+
observabilityScope: observability.topScope
5076+
)
5077+
XCTAssertNoDiagnostics(observability.diagnostics)
5078+
5079+
let result = try await BuildPlanResult(plan: mockBuildPlan(
5080+
toolchain: mockToolchain,
5081+
graph: graph,
5082+
commonFlags: .init(),
5083+
fileSystem: fs,
5084+
observabilityScope: observability.topScope
5085+
))
5086+
result.checkProductsCount(2)
5087+
result.checkTargetsCount(3)
5088+
5089+
let testProductLinkArgs = try result.buildProduct(for: "Lib").linkArguments()
5090+
XCTAssertNoMatch(testProductLinkArgs, [
5091+
"-I", "/fake/path/lib/swift/macosx/testing",
5092+
"-L", "/fake/path/lib/swift/macosx/testing",
5093+
])
5094+
5095+
let libModuleArgs = try result.moduleBuildDescription(for: "Lib").swift().compileArguments()
5096+
XCTAssertMatch(libModuleArgs, [
5097+
"-plugin-path", "/fake/path/lib/swift/host/plugins/testing",
5098+
])
5099+
XCTAssertNoMatch(libModuleArgs, ["-Xlinker"])
5100+
XCTAssertNoMatch(libModuleArgs, [
5101+
"-I", "/fake/path/lib/swift/macosx/testing",
5102+
"-L", "/fake/path/lib/swift/macosx/testing",
5103+
])
5104+
5105+
let testModuleArgs = try result.moduleBuildDescription(for: "LibTest").swift().compileArguments()
5106+
XCTAssertMatch(testModuleArgs, [
5107+
"-plugin-path", "/fake/path/lib/swift/host/plugins/testing",
5108+
])
5109+
XCTAssertNoMatch(testModuleArgs, ["-Xlinker"])
5110+
XCTAssertNoMatch(testModuleArgs, [
5111+
"-I", "/fake/path/lib/swift/macosx/testing",
5112+
"-L", "/fake/path/lib/swift/macosx/testing",
5113+
])
5114+
}
5115+
50005116
func testSwiftTestingFlagsOnMacOSWithCustomToolchain() async throws {
50015117
#if !os(macOS)
50025118
// This is testing swift-testing in a toolchain which is macOS only feature.
@@ -5006,7 +5122,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
50065122
let fs = InMemoryFileSystem(
50075123
emptyFiles:
50085124
"/fake/path/lib/swift/macosx/testing/Testing.swiftmodule",
5009-
"/fake/path/lib/swift/host/plugins/testing/libTesting.dylib",
5125+
"/fake/path/lib/swift/host/plugins/testing/libTestingMacros.dylib",
50105126
"/Pkg/Sources/Lib/main.swift",
50115127
"/Pkg/Tests/LibTest/test.swift"
50125128
)

0 commit comments

Comments
 (0)