Skip to content

Commit 0c47332

Browse files
authored
Include search path for Swift Testing's macro plugin from toolchain if present (#8670)
This modifies the logic for determining compiler flags for locating Swift Testing content such that it will always include a search path for Swift Testing's macro plugin at its newer, testing-specific location in the toolchain, if that directory exists. ### Motivation: Swift Testing installs a macro plugin library named `TestingMacros` into the official Swift toolchains for several platforms. By convention, most Swift macro plugins are installed into the toolchain's `usr/lib/swift/host/plugins` directory. However, testing libraries are somewhat special in the sense that they should generally only be available for use in "non-product" targets, i.e. targets which are not intended for distribution and only intended for use in qualifying the main product targets which _will_ be distributed. For that reason, in Darwin toolchains, Swift Testing's plugin is installed into a different location than for other platforms: `usr/lib/swift/host/plugins/testing` (note the final `testing/` directory) — see the [CMake rules](https://github.com/swiftlang/swift-testing/blob/72afbb418542654781a6b7853479c7e70a862b6f/Sources/TestingMacros/CMakeLists.txt#L67-L75) where that is controlled. Over time, we'd like to move the macro plugin on other platforms to that location for consistency, because the current _inconsistent_ install path has caused friction for users if they attempt to form search paths to the plugin on their own — see swiftlang/swift-testing#1039. So this PR paves the way for Swift Testing to begin to relocate its plugin by ensuring that _if_ this distinct plugin directory exists in a toolchain, it will be preferred. ### Modifications: - Modify logic in `UserToolchain.swift` to begin passing `-plugin-path` whenever the relevant, testing-specific plugin path exists in a toolchain. - Add a new test which validates this, by simulating the scenario of _not_ having a custom toolchain and validating the expected flags are passed. (This most closely matches the current scenario today of using the toolchain included in Xcode, and in that toolchain the plugin is already installed into the `testing/`-suffixed path.) rdar://151319768
1 parent 2e98598 commit 0c47332

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
@@ -4998,6 +4998,122 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
49984998
])
49994999
}
50005000

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

0 commit comments

Comments
 (0)