Skip to content

Commit ad5407f

Browse files
authored
Fix more sources of non-determinism (#118)
1 parent 831803b commit ad5407f

File tree

8 files changed

+53
-22
lines changed

8 files changed

+53
-22
lines changed

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelQueryParser.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ enum BazelQueryParser {
5959
/// and converts them into BSP-compatible build targets with associated source files.
6060
///
6161
/// - Parameters:
62-
/// - targets: Array of `BlazeQuery_Target` protobuf objects from Bazel query output
62+
/// - targets: Array of `BlazeQuery_Target` protobuf objects from Bazel query output (type: rule)
63+
/// - allSrcs: Array of `BlazeQuery_Target` protobuf objects from Bazel query output (type: source_file)
6364
/// - rootUri: Absolute path to the project root directory
6465
/// - toolchainPath: Absolute path to the development toolchain
6566
///
@@ -68,6 +69,7 @@ enum BazelQueryParser {
6869
/// - `[URI]`: Array of source file URIs associated with the target
6970
static func parseTargetsWithProto(
7071
from targets: [BlazeQuery_Target],
72+
allSrcs: [BlazeQuery_Target],
7173
rootUri: String,
7274
toolchainPath: String,
7375
) throws -> [(BuildTarget, [URI])] {
@@ -78,7 +80,7 @@ enum BazelQueryParser {
7880
// parse this info.
7981

8082
var result: [(BuildTarget, [URI])] = []
81-
let srcMap = buildSourceFilesMap(targets)
83+
let srcMap = buildSourceFilesMap(allSrcs)
8284

8385
for target in targets {
8486
guard target.type == .rule else {
@@ -108,7 +110,7 @@ enum BazelQueryParser {
108110
let _deps: [BuildTargetIdentifier] = try attr.stringListValue.map {
109111
let id = try $0.toTargetId(rootUri: rootUri)
110112
return .init(uri: id)
111-
}
113+
}.sorted(by: { $0.uri.stringValue < $1.uri.stringValue })
112114
deps = _deps
113115
}
114116
if attr.name == "srcs" {
@@ -122,7 +124,7 @@ enum BazelQueryParser {
122124
return nil
123125
}
124126
return try URI(string: path)
125-
}
127+
}.sorted(by: { $0.stringValue < $1.stringValue })
126128
srcs = _srcs
127129
}
128130
}
@@ -162,10 +164,10 @@ enum BazelQueryParser {
162164
/// The `srcs` attribute is a list of source_file labels instead of URI, thus we need
163165
/// a hashmap to reduce the time complexity.
164166
private static func buildSourceFilesMap(
165-
_ targets: [BlazeQuery_Target]
167+
_ allSrcs: [BlazeQuery_Target]
166168
) -> [String: String] {
167169
var srcMap: [String: String] = [:]
168-
for target in targets {
170+
for target in allSrcs {
169171
// making sure the target is a source_file type
170172
guard target.type == .sourceFile else {
171173
continue

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetDiscoverer.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public enum BazelTargetDiscoverer {
6565
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
6666
.filter { !$0.isEmpty }
6767
.map { $0.components(separatedBy: " (")[0] }
68+
.sorted()
6869

6970
if discoveredTargets.isEmpty {
7071
throw BazelTargetDiscovererError.noTargetsDiscovered

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetQuerier.swift

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ final class BazelTargetQuerier {
3939

4040
private let commandRunner: CommandRunner
4141

42-
private var queryCache = [String: [BlazeQuery_Target]]()
42+
private var queryCache = [String: ([BlazeQuery_Target], [BlazeQuery_Target])]()
4343
private var dependencyGraphCache = [String: [String: [String]]]()
4444

4545
static func queryDepsString(forTargets targets: [String]) -> String {
@@ -61,7 +61,7 @@ final class BazelTargetQuerier {
6161
func queryTargets(
6262
config: InitializedServerConfig,
6363
dependencyKinds: Set<String>,
64-
) throws -> [BlazeQuery_Target] {
64+
) throws -> (rules: [BlazeQuery_Target], srcs: [BlazeQuery_Target]) {
6565
if dependencyKinds.isEmpty {
6666
throw BazelTargetQuerierError.noKinds
6767
}
@@ -102,14 +102,31 @@ final class BazelTargetQuerier {
102102
rootUri: config.rootUri
103103
)
104104

105-
let result = try BazelProtobufBindings.parseCqueryResult(data: output)
105+
let queryResult = try BazelProtobufBindings.parseCqueryResult(data: output)
106106

107-
let targets = result.results.map { $0.target }
107+
let targets = queryResult.results.map { $0.target }
108108

109109
logger.debug("Parsed \(targets.count, privacy: .public) targets for cache key: \(cacheKey, privacy: .public)")
110-
queryCache[cacheKey] = targets
111110

112-
return targets
111+
var rules = [BlazeQuery_Target]()
112+
var srcs = [BlazeQuery_Target]()
113+
for target in targets {
114+
if target.type == .rule {
115+
rules.append(target)
116+
} else if target.type == .sourceFile {
117+
srcs.append(target)
118+
} else {
119+
logger.error("Parsed unexpected target type: \(target.type.rawValue)")
120+
}
121+
}
122+
123+
// Sort for determinism
124+
rules = rules.sorted(by: { $0.rule.name < $1.rule.name })
125+
srcs = srcs.sorted(by: { $0.sourceFile.name < $1.sourceFile.name })
126+
127+
let result = (rules, srcs)
128+
queryCache[cacheKey] = result
129+
return result
113130
}
114131

115132
func queryDependencyGraph(
@@ -168,6 +185,9 @@ final class BazelTargetQuerier {
168185
graph[source, default: []].append(contentsOf: targets)
169186
}
170187

188+
// Sort for determinism
189+
graph = graph.mapValues { $0.sorted() }
190+
171191
dependencyGraphCache[cacheKey] = graph
172192
return graph
173193
}

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ final class BazelTargetStoreImpl: BazelTargetStore {
214214
// Query all the targets we are interested in one invocation:
215215
// - Top-level targets (e.g. `ios_application`, `ios_unit_test`, etc.)
216216
// - Dependencies of the top-level targets (e.g. `swift_library`, `objc_library`, etc.)
217-
let allTargets =
217+
// - Source files connected to these targets
218+
let (allTargets, allSrcs) =
218219
try bazelTargetQuerier
219220
.queryTargets(
220221
config: initializedConfig,
@@ -254,6 +255,7 @@ final class BazelTargetStoreImpl: BazelTargetStore {
254255

255256
let targetData = try BazelQueryParser.parseTargetsWithProto(
256257
from: dependencyTargets,
258+
allSrcs: allSrcs,
257259
rootUri: initializedConfig.rootUri,
258260
toolchainPath: initializedConfig.devToolchainPath,
259261
)

Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ struct AqueryResult: Hashable {
5151
let configurations: [UInt32: Analysis_Configuration] = results.configuration.reduce(into: [:]) {
5252
result,
5353
configuration in
54+
if result.keys.contains(configuration.id) {
55+
logger.error(
56+
"Duplicate configuration found when aquerying (\(configuration.id))! This is unexpected. Will ignore the duplicate."
57+
)
58+
}
5459
result[configuration.id] = configuration
5560
}
5661
self.targets = targets

Tests/SourceKitBazelBSPTests/BazelTargetDiscovererTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct BazelTargetDiscovererTests {
3838
commandRunner: commandRunner,
3939
)
4040

41-
#expect(targets == ["//Example/HelloWorld:HelloWorld", "//Example/AnotherApp:AnotherApp"])
41+
#expect(targets == ["//Example/AnotherApp:AnotherApp", "//Example/HelloWorld:HelloWorld"])
4242
#expect(commandRunner.commands.count == 1)
4343
#expect(commandRunner.commands[0].command == "fakeBazel cquery 'kind(\"ios_application\", ...)' --output label")
4444
}
@@ -81,7 +81,7 @@ struct BazelTargetDiscovererTests {
8181
commandRunner: commandRunner,
8282
)
8383

84-
#expect(targets == ["//Example/HelloWorld:HelloWorld", "//Example/AnotherApp:AnotherApp"])
84+
#expect(targets == ["//Example/AnotherApp:AnotherApp", "//Example/HelloWorld:HelloWorld"])
8585
#expect(commandRunner.commands.count == 1)
8686
#expect(
8787
commandRunner.commands[0].command
@@ -105,7 +105,7 @@ struct BazelTargetDiscovererTests {
105105
commandRunner: commandRunner,
106106
)
107107

108-
#expect(targets == ["//Example/HelloWorld:HelloWorld", "//Example/AnotherApp:AnotherApp"])
108+
#expect(targets == ["//Example/AnotherApp:AnotherApp", "//Example/HelloWorld:HelloWorld"])
109109
#expect(commandRunner.commands.count == 1)
110110
#expect(
111111
commandRunner.commands[0].command

Tests/SourceKitBazelBSPTests/BazelTargetParserTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ struct BazelTargetParserTests {
6363
)
6464

6565
let result = try BazelQueryParser.parseTargetsWithProto(
66-
from: targets,
66+
from: targets.rules,
67+
allSrcs: targets.srcs,
6768
rootUri: rootUri,
6869
toolchainPath: toolchainPath,
6970
)

Tests/SourceKitBazelBSPTests/BazelTargetQuerierTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ struct BazelTargetQuerierTests {
6666
#expect(ranCommands.count == 1)
6767
#expect(ranCommands[0].command == expectedCommand)
6868
#expect(ranCommands[0].cwd == mockRootUri)
69-
#expect(!result.isEmpty)
69+
#expect(result.rules.count > 0)
70+
#expect(result.srcs.count > 0)
7071
}
7172

7273
@Test
@@ -110,7 +111,8 @@ struct BazelTargetQuerierTests {
110111
#expect(ranCommands.count == 1)
111112
#expect(ranCommands[0].command == expectedCommand)
112113
#expect(ranCommands[0].cwd == mockRootUri)
113-
#expect(!result.isEmpty)
114+
#expect(result.rules.count > 0)
115+
#expect(result.srcs.count > 0)
114116
}
115117

116118
@Test
@@ -217,9 +219,7 @@ struct BazelTargetQuerierTests {
217219
dependencyKinds: dependencyKinds
218220
)
219221

220-
let rules = result.filter { target in
221-
target.type == .rule
222-
}
222+
let rules = result.rules
223223

224224
let ranCommands = runner.commands
225225

0 commit comments

Comments
 (0)