Skip to content

Commit 4a260ff

Browse files
committed
[Build] BuildPlan: Add a way to traverse module dependencies
This method is going to be very useful for planning various build descriptions because they currently rely on a modules graph + description lookup into the build plan which is not great because it means that modules graph has to know about module destinations.
1 parent 9b8813e commit 4a260ff

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

Sources/Build/BuildPlan/BuildPlan.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,20 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
706706
}
707707
return inputs
708708
}
709+
710+
public func description(
711+
for product: ResolvedProduct,
712+
context: BuildParameters.Destination
713+
) -> ProductBuildDescription? {
714+
return self.productMap[product.id]
715+
}
716+
717+
public func description(
718+
for module: ResolvedModule,
719+
context: BuildParameters.Destination
720+
) -> ModuleBuildDescription? {
721+
return self.targetMap[module.id]
722+
}
709723
}
710724

711725
extension BuildPlan {
@@ -1117,6 +1131,59 @@ extension BuildPlan {
11171131
}
11181132
}
11191133
}
1134+
1135+
package func traverseDependencies(
1136+
of description: ModuleBuildDescription,
1137+
onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> Void,
1138+
onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> Void
1139+
) {
1140+
func successors(
1141+
for product: ResolvedProduct,
1142+
destination: Destination
1143+
) -> [TraversalNode] {
1144+
product.modules.map { module in
1145+
TraversalNode(module: module, context: destination)
1146+
}
1147+
}
1148+
1149+
func successors(
1150+
for module: ResolvedModule,
1151+
destination: Destination
1152+
) -> [TraversalNode] {
1153+
module
1154+
.dependencies(satisfying: description.buildParameters.buildEnvironment)
1155+
.reduce(into: [TraversalNode]()) { partial, dependency in
1156+
switch dependency {
1157+
case .product(let product, _):
1158+
partial.append(.init(product: product, context: destination))
1159+
case .module(let module, _):
1160+
partial.append(.init(module: module, context: destination))
1161+
}
1162+
}
1163+
}
1164+
1165+
depthFirstSearch(successors(for: description.module, destination: description.destination)) {
1166+
switch $0 {
1167+
case .module(let module, let destination):
1168+
successors(for: module, destination: destination)
1169+
case .product(let product, let destination):
1170+
successors(for: product, destination: destination)
1171+
case .package:
1172+
[]
1173+
}
1174+
} onNext: { module, _, _ in
1175+
switch module {
1176+
case .package:
1177+
break
1178+
1179+
case .product(let product, let destination):
1180+
onProduct(product, destination, self.description(for: product, context: destination))
1181+
1182+
case .module(let module, let destination):
1183+
onModule(module, destination, self.description(for: module, context: destination))
1184+
}
1185+
}
1186+
}
11201187
}
11211188

11221189
extension Basics.Diagnostic {

Tests/BuildTests/BuildPlanTraversalTests.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,47 @@ final class BuildPlanTraversalTests: XCTestCase {
143143
XCTAssertEqual(self.getUniqueOccurrences(in: results, for: "SwiftSyntax", destination: .host), [2, 3, 4, 5, 6])
144144
XCTAssertEqual(self.getUniqueOccurrences(in: results, for: "HAL"), [1, 2, 3])
145145
}
146+
147+
func testRecursiveDependencyTraversal() async throws {
148+
let destinationTriple = Triple.arm64Linux
149+
let toolsTriple = Triple.x86_64MacOS
150+
151+
let (graph, fs, scope) = try macrosPackageGraph()
152+
let plan = try await BuildPlan(
153+
destinationBuildParameters: mockBuildParameters(
154+
destination: .target,
155+
triple: destinationTriple
156+
),
157+
toolsBuildParameters: mockBuildParameters(
158+
destination: .host,
159+
triple: toolsTriple
160+
),
161+
graph: graph,
162+
fileSystem: fs,
163+
observabilityScope: scope
164+
)
165+
166+
let mmioModule = try XCTUnwrap(plan.description(for: graph.module(for: "MMIO")!, context: .target))
167+
168+
var moduleDependencies: [(ResolvedModule, Dest, Build.ModuleBuildDescription?)] = []
169+
plan.traverseDependencies(of: mmioModule) { product, destination, description in
170+
XCTAssertEqual(product.name, "SwiftSyntax")
171+
XCTAssertEqual(destination, .host)
172+
XCTAssertNil(description)
173+
} onModule: { module, destination, description in
174+
moduleDependencies.append((module, destination, description))
175+
}
176+
177+
XCTAssertEqual(moduleDependencies.count, 2)
178+
179+
// The ordering is guaranteed by the traversal
180+
181+
XCTAssertEqual(moduleDependencies[0].0.name, "MMIOMacros")
182+
XCTAssertEqual(moduleDependencies[1].0.name, "SwiftSyntax")
183+
184+
for index in 0 ..< moduleDependencies.count {
185+
XCTAssertEqual(moduleDependencies[index].1, .host)
186+
XCTAssertNotNil(moduleDependencies[index].2)
187+
}
188+
}
146189
}

0 commit comments

Comments
 (0)