Skip to content

Commit 46159ec

Browse files
authored
Optimize the dependency graph calculation (#51)
* Optimize the dependency graph calculation * Handle the example watch app correctly * Lint
1 parent feae34f commit 46159ec

File tree

4 files changed

+85
-22
lines changed

4 files changed

+85
-22
lines changed

Example/HelloWorld/BUILD

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,13 @@ ios_application(
155155
watchos_application(
156156
name = "HelloWorldWatchApp",
157157
bundle_id = "com.example.HelloWorld.Watch",
158-
extensions = [":HelloWorld-WatchExtension"],
158+
extensions = [":HelloWorldWatchExtension"],
159159
infoplists = ["Resources/WatchApp-Info.plist"],
160160
minimum_os_version = WATCHOS_MINIMUM_OS_VERSION,
161161
)
162162

163163
watchos_extension(
164-
name = "HelloWorldWatchExt",
164+
name = "HelloWorldWatchExtension",
165165
bundle_id = "com.example.HelloWorld.Watch.extension",
166166
infoplists = ["Resources/WatchExt-Info.plist"],
167167
minimum_os_version = WATCHOS_MINIMUM_OS_VERSION,

Package.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ let package = Package(
3131
name: "ArgumentParser",
3232
package: "swift-argument-parser"
3333
),
34+
],
35+
exclude: [
36+
"BUILD",
3437
]
3538
),
3639
.target(
@@ -46,6 +49,9 @@ let package = Package(
4649
package: "sourcekit-lsp"
4750
),
4851
],
52+
exclude: [
53+
"BUILD",
54+
]
4955
),
5056
.testTarget(
5157
name: "SourceKitBazelBSPTests",
@@ -62,6 +68,7 @@ let package = Package(
6268
.product(name: "SwiftProtobuf", package: "swift-protobuf")
6369
],
6470
exclude: [
71+
"BUILD",
6572
"README.md",
6673
"protos/analysis_v2.proto",
6774
"protos/build.proto",

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetQuerier.swift

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ final class BazelTargetQuerier {
4646

4747
private var topLevelRuleCache = [String: [(String, TopLevelRuleType)]]()
4848
private var queryCache = [String: [BlazeQuery_Target]]()
49-
private var dependencyLabelsCache = [String: [String]]()
49+
private var dependencyGraphCache = [String: [String: [String]]]()
5050

5151
static func queryDepsString(forTargets targets: [String]) -> String {
5252
var query = ""
@@ -137,38 +137,59 @@ final class BazelTargetQuerier {
137137
return targets
138138
}
139139

140-
func queryDependencyLabels(
141-
ofTarget target: String,
140+
func queryDependencyGraph(
141+
ofTargets targets: [String],
142142
forConfig config: BaseServerConfig,
143143
rootUri: String,
144144
kinds: Set<String>
145-
) throws -> [String] {
145+
) throws -> [String: [String]] {
146146
guard !kinds.isEmpty else {
147147
throw BazelTargetQuerierError.noKinds
148148
}
149149

150150
let kindsFilter = kinds.sorted().joined(separator: "|")
151151

152-
let cacheKey = "\(kindsFilter)+\(target)"
152+
var depsQuery = Self.queryDepsString(forTargets: targets)
153+
for target in targets {
154+
// Include the top-level target itself so that we can later traverse the graph correctly.
155+
depsQuery += " union \(target)"
156+
}
157+
158+
let cacheKey = "\(kindsFilter)+\(depsQuery)"
153159

154-
logger.info("Processing dependency labels request for \(cacheKey)")
160+
logger.info("Processing dependency graph request for \(cacheKey)")
155161

156-
if let cached = dependencyLabelsCache[cacheKey] {
162+
if let cached = dependencyGraphCache[cacheKey] {
157163
logger.debug("Returning cached results")
158164
return cached
159165
}
160166

161-
let cmd = "query \"kind('\(kindsFilter)', deps(\(target)))\" --output label"
167+
let cmd = "query \"kind('\(kindsFilter)', \(depsQuery))\" --output graph"
162168
let output: String = try commandRunner.run(config.bazelWrapper + " " + cmd, cwd: rootUri)
163-
let result = output.components(separatedBy: "\n")
169+
let rawGraph = output.components(separatedBy: "\n").filter {
170+
$0.hasPrefix(" \"")
171+
}
172+
173+
var graph = [String: [String]]()
174+
for line in rawGraph {
175+
let parts = line.components(separatedBy: "\"")
176+
// Example line:
177+
// "//path/to/target" -> "//path/to/target2\n//path/to/target3"
178+
guard parts.count == 5 else {
179+
continue
180+
}
181+
let source = parts[1]
182+
let targets = parts[3].components(separatedBy: "\n")
183+
graph[source, default: []].append(contentsOf: targets)
184+
}
164185

165-
dependencyLabelsCache[cacheKey] = result
166-
return result
186+
dependencyGraphCache[cacheKey] = graph
187+
return graph
167188
}
168189

169190
func clearCache() {
170191
topLevelRuleCache = [:]
171192
queryCache = [:]
172-
dependencyLabelsCache = [:]
193+
dependencyGraphCache = [:]
173194
}
174195
}

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,15 @@ final class BazelTargetStoreImpl: BazelTargetStore {
138138
forConfig: initializedConfig.baseConfig,
139139
rootUri: initializedConfig.rootUri,
140140
)
141+
let topLevelTargets = topLevelTargetData.map { $0.0 }
141142

142143
logger.debug("Queried top-level target data: \(topLevelTargetData)")
143144

145+
// Parse the target dependencies for the top-level targets.
146+
// This doesn't include information about which top-level app a target belongs to,
147+
// but we'll fill that in below.
144148
let targets: [BlazeQuery_Target] = try bazelTargetQuerier.queryTargetDependencies(
145-
forTargets: topLevelTargetData.map { $0.0 },
149+
forTargets: topLevelTargets,
146150
forConfig: initializedConfig.baseConfig,
147151
rootUri: initializedConfig.rootUri,
148152
kinds: Self.supportedKinds
@@ -174,13 +178,24 @@ final class BazelTargetStoreImpl: BazelTargetStore {
174178

175179
// We need to now map which targets belong to which top-level apps,
176180
// to further support the target / platform combo differentiation mentioned above.
177-
for (topLevelTarget, _) in topLevelTargetData {
178-
let deps = try bazelTargetQuerier.queryDependencyLabels(
179-
ofTarget: topLevelTarget,
180-
forConfig: initializedConfig.baseConfig,
181-
rootUri: initializedConfig.rootUri,
182-
kinds: Self.supportedKinds
183-
)
181+
182+
// We need to include the top-level data here as well to be able to traverse the graph correctly.
183+
let depGraphKinds = Self.supportedKinds.union(TopLevelRuleType.allCases.map { $0.rawValue })
184+
185+
let depGraph = try bazelTargetQuerier.queryDependencyGraph(
186+
ofTargets: topLevelTargets,
187+
forConfig: initializedConfig.baseConfig,
188+
rootUri: initializedConfig.rootUri,
189+
kinds: depGraphKinds
190+
)
191+
192+
// We should ignore any app -> app nodes as we don't want to follow things such
193+
// as the watchos_application field in ios_application rules. Otherwise some targets
194+
// will be misclassified.
195+
let targetsToIgnore = Set(topLevelTargets)
196+
197+
for topLevelTarget in topLevelTargets {
198+
let deps = traverseGraph(from: topLevelTarget, in: depGraph, ignoring: targetsToIgnore)
184199
for dep in deps {
185200
guard availableBazelLabels.contains(dep) else {
186201
// Ignore any labels that we also ignored above
@@ -193,6 +208,26 @@ final class BazelTargetStoreImpl: BazelTargetStore {
193208
return targetData.map { $0.0 }
194209
}
195210

211+
private func traverseGraph(from target: String, in graph: [String: [String]], ignoring: Set<String>) -> Set<String>
212+
{
213+
var visited = Set<String>()
214+
var result = Set<String>()
215+
var queue: [String] = graph[target, default: []]
216+
while let curr = queue.popLast() {
217+
guard !ignoring.contains(curr) else {
218+
continue
219+
}
220+
result.insert(curr)
221+
for dep in graph[curr, default: []] {
222+
if !visited.contains(dep) {
223+
visited.insert(dep)
224+
queue.append(dep)
225+
}
226+
}
227+
}
228+
return result
229+
}
230+
196231
func clearCache() {
197232
bspURIsToBazelLabelsMap = [:]
198233
bspURIsToSrcsMap = [:]

0 commit comments

Comments
 (0)