Skip to content

Commit bc72d82

Browse files
authored
Fix dup modmaps taking into account macro test targets. (#8524)
In my previous attempt to fix the duplicate modulemaps from C library dependencies of macros/plugins and destination targets, I didn't take into account test targets depending on macros, i.e. macro tests. In these cases the modulemap flags do need to pass through to the tests. This was discovered in swift-foundation which couldn't find the _SwiftSyntaxCShims module map. Fixed this my moving the traversing link dependencies a layer lower since we need access to the parent node during traversal and this is available in the successor methods. Cleaned up my previous attempt.
1 parent 4a36f55 commit bc72d82

File tree

6 files changed

+105
-73
lines changed

6 files changed

+105
-73
lines changed

Sources/Basics/Graph/GraphAlgorithms.swift

-47
Original file line numberDiff line numberDiff line change
@@ -131,50 +131,3 @@ public func depthFirstSearch<T: Hashable>(
131131
}
132132
}
133133
}
134-
135-
/// Implements a pre-order depth-first search that traverses the whole graph and
136-
/// doesn't distinguish between unique and duplicate nodes. The visitor can abort
137-
/// a path as needed to prune the tree.
138-
/// The method expects the graph to be acyclic but doesn't check that.
139-
///
140-
/// - Parameters:
141-
/// - nodes: The list of input nodes to sort.
142-
/// - successors: A closure for fetching the successors of a particular node.
143-
/// - onNext: A callback to indicate the node currently being processed
144-
/// including its parent (if any) and its depth. Returns whether to
145-
/// continue down the current path.
146-
///
147-
/// - Complexity: O(v + e) where (v, e) are the number of vertices and edges
148-
/// reachable from the input nodes via the relation.
149-
public enum DepthFirstContinue {
150-
case `continue`
151-
case abort
152-
}
153-
154-
public func depthFirstSearch<T: Hashable>(
155-
_ nodes: [T],
156-
successors: (T) throws -> [T],
157-
visitNext: (T, _ parent: T?) throws -> DepthFirstContinue
158-
) rethrows {
159-
var stack = OrderedSet<TraversalNode<T>>()
160-
161-
for node in nodes {
162-
precondition(stack.isEmpty)
163-
stack.append(TraversalNode(parent: nil, curr: node))
164-
165-
while !stack.isEmpty {
166-
let node = stack.removeLast()
167-
168-
if try visitNext(node.curr, node.parent) == .continue {
169-
for succ in try successors(node.curr) {
170-
stack.append(
171-
TraversalNode(
172-
parent: node.curr,
173-
curr: succ
174-
)
175-
)
176-
}
177-
}
178-
}
179-
}
180-
}

Sources/Build/BuildDescription/ModuleBuildDescription.swift

+1-13
Original file line numberDiff line numberDiff line change
@@ -187,30 +187,18 @@ extension ModuleBuildDescription {
187187
var dependencies: [Dependency] = []
188188
plan.traverseDependencies(of: self) { product, _, description in
189189
dependencies.append(.product(product, description))
190-
return .continue
191190
} onModule: { module, _, description in
192191
dependencies.append(.module(module, description))
193-
return .continue
194192
}
195193
return dependencies
196194
}
197195

198196
package func recursiveLinkDependencies(using plan: BuildPlan) -> [Dependency] {
199197
var dependencies: [Dependency] = []
200-
plan.traverseDependencies(of: self) { product, _, description in
201-
guard product.type != .macro && product.type != .plugin else {
202-
return .abort
203-
}
204-
198+
plan.traverseLinkDependencies(of: self) { product, _, description in
205199
dependencies.append(.product(product, description))
206-
return .continue
207200
} onModule: { module, _, description in
208-
guard module.type != .macro && module.type != .plugin else {
209-
return .abort
210-
}
211-
212201
dependencies.append(.module(module, description))
213-
return .continue
214202
}
215203
return dependencies
216204
}

Sources/Build/BuildPlan/BuildPlan+Swift.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ extension BuildPlan {
2424
func plan(swiftTarget: SwiftModuleBuildDescription) throws {
2525
// We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target
2626
// builds against
27-
for case .module(let dependency, let description) in swiftTarget.recursiveDependencies(using: self) {
27+
for case .module(let dependency, let description) in swiftTarget.recursiveLinkDependencies(using: self) {
2828
switch dependency.underlying {
2929
case let underlyingTarget as ClangModule where underlyingTarget.type == .library:
3030
guard case let .clang(target)? = description else {

Sources/Build/BuildPlan/BuildPlan.swift

+82-6
Original file line numberDiff line numberDiff line change
@@ -1175,8 +1175,8 @@ extension BuildPlan {
11751175

11761176
package func traverseDependencies(
11771177
of description: ModuleBuildDescription,
1178-
onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> DepthFirstContinue,
1179-
onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> DepthFirstContinue
1178+
onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> Void,
1179+
onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> Void
11801180
) {
11811181
var visited = Set<TraversalNode>()
11821182
func successors(
@@ -1217,16 +1217,92 @@ extension BuildPlan {
12171217
case .package:
12181218
[]
12191219
}
1220-
} visitNext: { module, _ in
1220+
} onNext: { module, _ in
12211221
switch module {
12221222
case .package:
1223-
return .continue
1223+
break
1224+
1225+
case .product(let product, let destination):
1226+
onProduct(product, destination, self.description(for: product, context: destination))
1227+
1228+
case .module(let module, let destination):
1229+
onModule(module, destination, self.description(for: module, context: destination))
1230+
}
1231+
}
1232+
}
1233+
1234+
// Only follow link time dependencies, i.e. skip dependencies on macros and plugins
1235+
// except for testTargets that depend on macros.
1236+
package func traverseLinkDependencies(
1237+
of description: ModuleBuildDescription,
1238+
onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> Void,
1239+
onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> Void
1240+
) {
1241+
var visited = Set<TraversalNode>()
1242+
func successors(
1243+
for product: ResolvedProduct,
1244+
destination: Destination
1245+
) -> [TraversalNode] {
1246+
product.modules.map { module in
1247+
TraversalNode(module: module, context: destination)
1248+
}.filter {
1249+
visited.insert($0).inserted
1250+
}
1251+
}
1252+
1253+
func successors(
1254+
for parentModule: ResolvedModule,
1255+
destination: Destination
1256+
) -> [TraversalNode] {
1257+
parentModule
1258+
.dependencies(satisfying: description.buildParameters.buildEnvironment)
1259+
.reduce(into: [TraversalNode]()) { partial, dependency in
1260+
switch dependency {
1261+
case .product(let product, _):
1262+
guard product.type != .plugin else {
1263+
return
1264+
}
1265+
1266+
guard product.type != .macro || parentModule.type == .test else {
1267+
return
1268+
}
1269+
1270+
partial.append(.init(product: product, context: destination))
1271+
case .module(let childModule, _):
1272+
guard childModule.type != .plugin else {
1273+
return
1274+
}
1275+
1276+
guard childModule.type != .macro || parentModule.type == .test else {
1277+
return
1278+
}
1279+
1280+
partial.append(.init(module: childModule, context: destination))
1281+
}
1282+
}.filter {
1283+
visited.insert($0).inserted
1284+
}
1285+
}
1286+
1287+
depthFirstSearch(successors(for: description.module, destination: description.destination)) {
1288+
switch $0 {
1289+
case .module(let module, let destination):
1290+
successors(for: module, destination: destination)
1291+
case .product(let product, let destination):
1292+
successors(for: product, destination: destination)
1293+
case .package:
1294+
[]
1295+
}
1296+
} onNext: { module, _ in
1297+
switch module {
1298+
case .package:
1299+
break
12241300

12251301
case .product(let product, let destination):
1226-
return onProduct(product, destination, self.description(for: product, context: destination))
1302+
onProduct(product, destination, self.description(for: product, context: destination))
12271303

12281304
case .module(let module, let destination):
1229-
return onModule(module, destination, self.description(for: module, context: destination))
1305+
onModule(module, destination, self.description(for: module, context: destination))
12301306
}
12311307
}
12321308
}

Tests/BuildTests/BuildPlanTests.swift

+21-4
Original file line numberDiff line numberDiff line change
@@ -6928,14 +6928,16 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
69286928
"/LeakTest/Sources/CLib/Clib.c",
69296929
"/LeakTest/Sources/MyMacro/MyMacro.swift",
69306930
"/LeakTest/Sources/MyPluginTool/MyPluginTool.swift",
6931-
"/LeakTest/Plugins/MyPlugin/MyPlugin.swift",
69326931
"/LeakTest/Sources/MyLib/MyLib.swift",
6932+
"/LeakTest/Plugins/MyPlugin/MyPlugin.swift",
6933+
"/LeakTest/Tests/MyMacroTests/MyMacroTests.swift",
6934+
"/LeakTest/Tests/MyMacro2Tests/MyMacro2Tests.swift",
69336935
"/LeakLib/Sources/CLib2/include/Clib.h",
69346936
"/LeakLib/Sources/CLib2/Clib.c",
69356937
"/LeakLib/Sources/MyMacro2/MyMacro.swift",
69366938
"/LeakLib/Sources/MyPluginTool2/MyPluginTool.swift",
6939+
"/LeakLib/Sources/MyLib2/MyLib.swift",
69376940
"/LeakLib/Plugins/MyPlugin2/MyPlugin.swift",
6938-
"/LeakLib/Sources/MyLib2/MyLib.swift"
69396941
])
69406942

69416943
let graph = try loadModulesGraph(fileSystem: fs, manifests: [
@@ -6944,6 +6946,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
69446946
path: "/LeakLib",
69456947
products: [
69466948
ProductDescription(name: "MyLib2", type: .library(.automatic), targets: ["MyLib2"]),
6949+
ProductDescription(name: "MyMacros2", type: .macro, targets: ["MyMacro2"])
69476950
],
69486951
targets: [
69496952
TargetDescription(name: "CLib2"),
@@ -6969,6 +6972,11 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
69696972
dependencies: ["CLib", "MyMacro", .product(name: "MyLib2", package: "LeakLib")],
69706973
pluginUsages: [.plugin(name: "MyPlugin", package: nil)]
69716974
),
6975+
TargetDescription(name: "MyMacroTests", dependencies: ["MyMacro"], type: .test),
6976+
TargetDescription(
6977+
name: "MyMacro2Tests",
6978+
dependencies: [.product(name: "MyMacros2", package: "LeakLib")],
6979+
type: .test),
69726980
]
69736981
)
69746982
], observabilityScope: observability.topScope)
@@ -6982,8 +6990,17 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
69826990
XCTAssertNoDiagnostics(observability.diagnostics)
69836991

69846992
let myLib = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyLib" })).swift()
6985-
print(myLib.additionalFlags)
6986-
XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool/include")}), "flags shouldn't contain tools items")
6993+
XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool")}), "flags shouldn't contain tools items")
6994+
6995+
// Make sure the tests do have the include path and the module map from the lib
6996+
let myMacroTests = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyMacroTests" })).swift()
6997+
let flags = myMacroTests.additionalFlags.joined(separator: " ")
6998+
XCTAssertMatch(flags, .regex("CLib[/\\\\]include"))
6999+
XCTAssertMatch(flags, .regex("CLib-tool.build[/\\\\]module.modulemap"))
7000+
let myMacro2Tests = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyMacro2Tests" })).swift()
7001+
let flags2 = myMacro2Tests.additionalFlags.joined(separator: " ")
7002+
XCTAssertMatch(flags2, .regex("CLib2[/\\\\]include"))
7003+
XCTAssertMatch(flags2, .regex("CLib2-tool.build[/\\\\]module.modulemap"))
69877004
}
69887005

69897006
func testDiagnosticsAreMentionedInOutputsFileMap() async throws {

Tests/BuildTests/BuildPlanTraversalTests.swift

-2
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,8 @@ final class BuildPlanTraversalTests: XCTestCase {
146146
XCTAssertEqual(product.name, "SwiftSyntax")
147147
XCTAssertEqual(destination, .host)
148148
XCTAssertNil(description)
149-
return .continue
150149
} onModule: { module, destination, description in
151150
moduleDependencies.append((module, destination, description))
152-
return .continue
153151
}
154152

155153
XCTAssertEqual(moduleDependencies.count, 2)

0 commit comments

Comments
 (0)