Skip to content

Commit 3e9f319

Browse files
authored
_InternalTestSupport should not depend on Build (#8224)
Looks like something I caused a while ago in d457fa4 which was later further entrenched in `PluginInvocationTests`, but we should keep the "data model" and "build" parts of SwiftPM separate, so this PR cleans that up again.
1 parent 0c6acf3 commit 3e9f319

File tree

8 files changed

+248
-211
lines changed

8 files changed

+248
-211
lines changed

Package.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -682,12 +682,23 @@ let package = Package(
682682

683683
// MARK: Additional Test Dependencies
684684

685+
.target(
686+
/** SwiftPM internal build test suite support library */
687+
name: "_InternalBuildTestSupport",
688+
dependencies: [
689+
"Build",
690+
"_InternalTestSupport"
691+
],
692+
swiftSettings: [
693+
.unsafeFlags(["-static"]),
694+
]
695+
),
696+
685697
.target(
686698
/** SwiftPM internal test suite support library */
687699
name: "_InternalTestSupport",
688700
dependencies: [
689701
"Basics",
690-
"Build",
691702
"PackageFingerprint",
692703
"PackageGraph",
693704
"PackageLoading",
@@ -748,7 +759,7 @@ let package = Package(
748759
),
749760
.testTarget(
750761
name: "BuildTests",
751-
dependencies: ["Build", "PackageModel", "Commands", "_InternalTestSupport"]
762+
dependencies: ["Build", "PackageModel", "Commands", "_InternalTestSupport", "_InternalBuildTestSupport"]
752763
),
753764
.testTarget(
754765
name: "LLBuildManifestTests",
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Basics
14+
15+
@_spi(SwiftPMInternal)
16+
import Build
17+
18+
import _InternalTestSupport
19+
import struct PackageGraph.ModulesGraph
20+
import struct PackageGraph.ResolvedModule
21+
import struct PackageGraph.ResolvedProduct
22+
import PackageModel
23+
import SPMBuildCore
24+
import TSCUtility
25+
import XCTest
26+
27+
public func mockBuildPlan(
28+
buildPath: AbsolutePath? = nil,
29+
environment: BuildEnvironment,
30+
toolchain: PackageModel.Toolchain = MockToolchain(),
31+
graph: ModulesGraph,
32+
commonFlags: PackageModel.BuildFlags = .init(),
33+
indexStoreMode: BuildParameters.IndexStoreMode = .off,
34+
omitFramePointers: Bool? = nil,
35+
driverParameters: BuildParameters.Driver = .init(),
36+
linkingParameters: BuildParameters.Linking = .init(),
37+
targetSanitizers: EnabledSanitizers = .init(),
38+
fileSystem fs: any FileSystem,
39+
observabilityScope: ObservabilityScope
40+
) async throws -> Build.BuildPlan {
41+
try await mockBuildPlan(
42+
buildPath: buildPath,
43+
config: environment.configuration ?? .debug,
44+
platform: environment.platform,
45+
toolchain: toolchain,
46+
graph: graph,
47+
commonFlags: commonFlags,
48+
indexStoreMode: indexStoreMode,
49+
omitFramePointers: omitFramePointers,
50+
driverParameters: driverParameters,
51+
linkingParameters: linkingParameters,
52+
targetSanitizers: targetSanitizers,
53+
fileSystem: fs,
54+
observabilityScope: observabilityScope
55+
)
56+
}
57+
58+
public func mockBuildPlan(
59+
buildPath: AbsolutePath? = nil,
60+
config: BuildConfiguration = .debug,
61+
triple: Basics.Triple? = nil,
62+
platform: PackageModel.Platform? = nil,
63+
toolchain: PackageModel.Toolchain = MockToolchain(),
64+
graph: ModulesGraph,
65+
commonFlags: PackageModel.BuildFlags = .init(),
66+
indexStoreMode: BuildParameters.IndexStoreMode = .off,
67+
omitFramePointers: Bool? = nil,
68+
driverParameters: BuildParameters.Driver = .init(),
69+
linkingParameters: BuildParameters.Linking = .init(),
70+
targetSanitizers: EnabledSanitizers = .init(),
71+
fileSystem fs: any FileSystem,
72+
observabilityScope: ObservabilityScope
73+
) async throws -> Build.BuildPlan {
74+
let inferredTriple: Basics.Triple
75+
if let platform {
76+
precondition(triple == nil)
77+
78+
inferredTriple = switch platform {
79+
case .macOS:
80+
Triple.x86_64MacOS
81+
case .linux:
82+
Triple.arm64Linux
83+
case .android:
84+
Triple.arm64Android
85+
case .windows:
86+
Triple.windows
87+
default:
88+
fatalError("unsupported platform in tests")
89+
}
90+
} else {
91+
inferredTriple = triple ?? hostTriple
92+
}
93+
94+
let commonDebuggingParameters = BuildParameters.Debugging(
95+
triple: inferredTriple,
96+
shouldEnableDebuggingEntitlement: config == .debug,
97+
omitFramePointers: omitFramePointers
98+
)
99+
100+
var destinationParameters = mockBuildParameters(
101+
destination: .target,
102+
buildPath: buildPath,
103+
config: config,
104+
toolchain: toolchain,
105+
flags: commonFlags,
106+
triple: inferredTriple,
107+
indexStoreMode: indexStoreMode
108+
)
109+
destinationParameters.debuggingParameters = commonDebuggingParameters
110+
destinationParameters.driverParameters = driverParameters
111+
destinationParameters.linkingParameters = linkingParameters
112+
destinationParameters.sanitizers = targetSanitizers
113+
114+
var hostParameters = mockBuildParameters(
115+
destination: .host,
116+
buildPath: buildPath,
117+
config: config,
118+
toolchain: toolchain,
119+
flags: commonFlags,
120+
triple: inferredTriple,
121+
indexStoreMode: indexStoreMode
122+
)
123+
hostParameters.debuggingParameters = commonDebuggingParameters
124+
hostParameters.driverParameters = driverParameters
125+
hostParameters.linkingParameters = linkingParameters
126+
127+
return try await BuildPlan(
128+
destinationBuildParameters: destinationParameters,
129+
toolsBuildParameters: hostParameters,
130+
graph: graph,
131+
fileSystem: fs,
132+
observabilityScope: observabilityScope
133+
)
134+
}
135+
136+
package func mockPluginTools(
137+
plugins: IdentifiableSet<ResolvedModule>,
138+
fileSystem: any FileSystem,
139+
buildParameters: BuildParameters,
140+
hostTriple: Basics.Triple
141+
) async throws -> [ResolvedModule.ID: [String: PluginTool]] {
142+
var accessibleToolsPerPlugin: [ResolvedModule.ID: [String: PluginTool]] = [:]
143+
for plugin in plugins where accessibleToolsPerPlugin[plugin.id] == nil {
144+
let accessibleTools = try await plugin.preparePluginTools(
145+
fileSystem: fileSystem,
146+
environment: buildParameters.buildEnvironment,
147+
for: hostTriple
148+
) { name, path in
149+
buildParameters.buildPath.appending(path)
150+
}
151+
152+
accessibleToolsPerPlugin[plugin.id] = accessibleTools
153+
}
154+
155+
return accessibleToolsPerPlugin
156+
}
157+
158+
enum BuildError: Swift.Error {
159+
case error(String)
160+
}
161+
162+
public struct BuildPlanResult {
163+
public let plan: Build.BuildPlan
164+
165+
public var productMap: IdentifiableSet<Build.ProductBuildDescription> {
166+
self.plan.productMap
167+
}
168+
169+
public var targetMap: IdentifiableSet<Build.ModuleBuildDescription> {
170+
self.plan.targetMap
171+
}
172+
173+
public init(plan: Build.BuildPlan) throws {
174+
self.plan = plan
175+
}
176+
177+
public func checkTargetsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) {
178+
XCTAssertEqual(self.targetMap.count, count, file: file, line: line)
179+
}
180+
181+
public func checkProductsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) {
182+
XCTAssertEqual(self.productMap.count, count, file: file, line: line)
183+
}
184+
185+
public func moduleBuildDescription(for name: String) throws -> Build.ModuleBuildDescription {
186+
let matches = self.targetMap.filter({ $0.module.name == name })
187+
guard matches.count == 1 else {
188+
if matches.isEmpty {
189+
throw BuildError.error("Target \(name) not found.")
190+
} else {
191+
throw BuildError.error("More than one target \(name) found.")
192+
}
193+
}
194+
return matches.first!
195+
}
196+
197+
public func buildProduct(for name: String) throws -> Build.ProductBuildDescription {
198+
let matches = self.productMap.filter({ $0.product.name == name })
199+
guard matches.count == 1 else {
200+
if matches.isEmpty {
201+
// <rdar://problem/30162871> Display the thrown error on macOS
202+
throw BuildError.error("Product \(name) not found.")
203+
} else {
204+
throw BuildError.error("More than one target \(name) found.")
205+
}
206+
}
207+
return matches.first!
208+
}
209+
}
210+
211+
extension Build.ModuleBuildDescription {
212+
public func swift() throws -> SwiftModuleBuildDescription {
213+
switch self {
214+
case .swift(let description):
215+
return description
216+
default:
217+
throw BuildError.error("Unexpected \(self) type found")
218+
}
219+
}
220+
221+
public func clang() throws -> ClangModuleBuildDescription {
222+
switch self {
223+
case .clang(let description):
224+
return description
225+
default:
226+
throw BuildError.error("Unexpected \(self) type")
227+
}
228+
}
229+
}

0 commit comments

Comments
 (0)