Skip to content

Commit 2909a82

Browse files
authored
Migrate action query from text to protobuf parsing (#40)
* replacing plain text parsing to proto parsing for action query results - append command string with "--output proto" - added CompilerArgError for target not found when processing compiler arguments - added back skip `swiftc` and `worker` arguments * updated tests for action query - added aquery.pb and aquery_objc.pb as mocking data - updated expected aquery by append "--output proto" * rename the command for action query * fix the bug - the clang compiler call is included in the arguments list * use opaque type for Data and String return type * removed the error and using simple strings, improve performance with setting index only * fix test * change log to private * use generic instead of opaque type, then call static func convert
1 parent c3f31de commit 2909a82

File tree

12 files changed

+200
-367
lines changed

12 files changed

+200
-367
lines changed

Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ let package = Package(
5151
name: "SourceKitBazelBSPTests",
5252
dependencies: ["SourceKitBazelBSP"],
5353
resources: [
54+
.copy("Resources/aquery.pb"),
55+
.copy("Resources/aquery_objc.pb"),
5456
.copy("Resources/streamdeps.pb"),
5557
],
5658
),

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetQuerier.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ final class BazelTargetQuerier {
123123

124124
// We run this one on the main output base since it's not related to the actual indexing bits
125125
let cmd = "query \"kind('\(kindsFilter)', \(depsQuery))\" --output streamed_proto"
126-
let output = try commandRunner.run(config.bazelWrapper + " " + cmd, cwd: rootUri)
126+
let output: Data = try commandRunner.run(config.bazelWrapper + " " + cmd, cwd: rootUri)
127127

128128
logger.debug("Finished querying, building result Protobuf")
129129

Sources/SourceKitBazelBSP/RequestHandlers/InitializeHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ final class InitializeHandler {
9292
logger.debug("outputBase: \(outputBase)")
9393

9494
// Now, get the full output path based on the above output base.
95-
let outputPath = try commandRunner.bazelIndexAction(
95+
let outputPath: String = try commandRunner.bazelIndexAction(
9696
baseConfig: baseConfig,
9797
outputBase: outputBase,
9898
cmd: "info output_path",

Sources/SourceKitBazelBSP/RequestHandlers/PrepareHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ final class PrepareHandler {
7979
logger.info("Will build \(labelsToBuild.joined(separator: ", "))")
8080

8181
// Build the provided targets, on our special output base and taking into account special index flags.
82-
_ = try commandRunner.bazelIndexAction(
82+
let _: String = try commandRunner.bazelIndexAction(
8383
initializedConfig: initializedConfig,
8484
cmd: "build \(labelsToBuild.joined(separator: " "))"
8585
)

Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/BazelTargetAquerier.swift

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// specific language governing permissions and limitations
1818
// under the License.
1919

20+
import BazelProtobufBindings
2021
import Foundation
2122

2223
private let logger = makeFileLevelBSPLogger()
@@ -33,12 +34,10 @@ enum BazelTargetAquerierError: Error, LocalizedError {
3334

3435
/// Small abstraction to handle and cache the results of bazel _action queries_.
3536
/// FIXME: This is separate from BazelTargetQuerier because of the different output types, but we can unify these.
36-
///
37-
/// FIXME: Currently uses text outputs, should use proto instead so that we can organize and test this properly.
3837
final class BazelTargetAquerier {
3938

4039
private let commandRunner: CommandRunner
41-
private var queryCache = [String: String]()
40+
private var queryCache = [String: Analysis_ActionGraphContainer]()
4241

4342
init(commandRunner: CommandRunner = ShellCommandRunner()) {
4443
self.commandRunner = commandRunner
@@ -50,15 +49,15 @@ final class BazelTargetAquerier {
5049
config: InitializedServerConfig,
5150
mnemonics: Set<String>,
5251
additionalFlags: [String]
53-
) throws -> String {
52+
) throws -> Analysis_ActionGraphContainer {
5453
guard !mnemonics.isEmpty else {
5554
throw BazelTargetAquerierError.noMnemonics
5655
}
5756

5857
let mnemonicsFilter = mnemonics.sorted().joined(separator: "|")
5958
let depsQuery = BazelTargetQuerier.queryDepsString(forTargets: [target])
6059

61-
let otherFlags = additionalFlags.joined(separator: " ")
60+
let otherFlags = additionalFlags.joined(separator: " ") + " --output proto"
6261
let cmd = "aquery \"mnemonic('\(mnemonicsFilter)', filter(\(filteringFor), \(depsQuery)))\" \(otherFlags)"
6362
logger.info("Processing aquery request for \(target), filtering for \(filteringFor)")
6463

@@ -68,11 +67,15 @@ final class BazelTargetAquerier {
6867
}
6968

7069
// Run the aquery on the special index output base since that's where we will build at.
71-
let output = try commandRunner.bazelIndexAction(initializedConfig: config, cmd: cmd)
70+
let output: Data = try commandRunner.bazelIndexAction(initializedConfig: config, cmd: cmd)
7271

73-
queryCache[cmd] = output
72+
let parsedOutput = try BazelProtobufBindings.parseActionGraph(data: output)
7473

75-
return output
74+
logger.debug("actionGraphContainer count \(parsedOutput.actions.count, privacy: .private)")
75+
76+
queryCache[cmd] = parsedOutput
77+
78+
return parsedOutput
7679
}
7780

7881
func clearCache() {

Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/CompilerArgumentsProcessor.swift

Lines changed: 17 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -17,114 +17,48 @@
1717
// specific language governing permissions and limitations
1818
// under the License.
1919

20+
import BazelProtobufBindings
2021
import Foundation
2122
import LanguageServerProtocol
2223

2324
private let logger = makeFileLevelBSPLogger()
2425

2526
enum CompilerArgumentsProcessor {
2627
// Parses and processes the compilation step for a given target from a larger aquery output.
27-
// The parsing step is only necessary because BazelTargetAquerier operates on text. Should become unnecessary once we move to proto.
2828
static func extractAndProcessCompilerArgs(
29-
fromAquery aqueryOutput: String,
29+
fromAquery aqueryOutput: Analysis_ActionGraphContainer,
3030
bazelTarget: String,
3131
contentToQuery: String,
3232
language: Language,
3333
sdkRoot: String,
3434
initializedConfig: InitializedServerConfig
3535
) -> [String]? {
36-
var lines: [String] = aqueryOutput.components(separatedBy: "\n")
37-
var idx = -1
38-
for (i, line) in lines.enumerated() {
39-
var actionPrefix = "action 'Compiling "
40-
if language == .swift {
41-
actionPrefix += "Swift module \(bazelTarget)'"
42-
} else {
43-
actionPrefix += "\(contentToQuery)'"
44-
}
45-
if line.hasPrefix(actionPrefix) {
46-
if language == .swift {
47-
if lines[i + 1] != " Mnemonic: SwiftCompile" { continue }
48-
} else {
49-
if lines[i + 1] != " Mnemonic: ObjcCompile" { continue }
50-
if lines[i + 2] != " Target: \(bazelTarget)" { continue }
51-
}
52-
idx = i
53-
break
54-
}
55-
}
56-
if idx == -1 {
57-
logger.error("No module entry found in the aquery for \(contentToQuery)")
58-
return nil
59-
}
60-
// now, get the first index of the line that starts with "Command Line: ("
61-
lines = Array(lines.dropFirst(idx + 1))
62-
idx = -1
63-
for (i, line) in lines.enumerated() {
64-
if line.starts(with: " Command Line: (") {
65-
idx = i
66-
// Also skip the swiftc line for swift targets
67-
if lines[idx + 1].contains("swiftc") { idx += 1 }
68-
break
69-
}
70-
}
71-
if idx == -1 {
72-
logger.error("No command line entry found")
36+
guard let target = aqueryOutput.targets.first(where: { $0.label == bazelTarget }) else {
37+
logger.debug("Target: \(bazelTarget, privacy: .private) not found.")
7338
return nil
7439
}
75-
logger.info("Found command line entry at \(idx)")
76-
77-
// now, find where the arguments end
78-
lines = Array(lines.dropFirst(idx + 1))
79-
80-
idx = -1
81-
for (i, line) in lines.enumerated() {
82-
if line.starts(with: "#") {
83-
idx = i
84-
break
85-
}
86-
}
87-
if idx == -1 {
88-
logger.error("Couldn't find where the args end")
40+
guard let action = aqueryOutput.actions.first(where: { $0.targetID == target.id }) else {
41+
logger.debug("Action for \(bazelTarget, privacy: .private) not found.")
8942
return nil
9043
}
9144

92-
logger.info("Found where the args end at \(idx)")
93-
lines = Array(lines.dropLast(lines.count - idx - 1))
94-
95-
// the spaced lines are the compiler arguments
96-
lines = lines.filter { $0.starts(with: " ") }
97-
98-
lines = lines.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
99-
lines = lines.map { $0.hasSuffix(" \\") ? String($0.dropLast(2)) : $0 }
100-
// remove the trailing ) from the last line
101-
lines[lines.count - 1] = String(lines[lines.count - 1].dropLast())
102-
103-
// some args are wrapped in single quotes for some reason
104-
for i in 0..<lines.count {
105-
if lines[i].hasPrefix("'"), lines[i].hasSuffix("'") {
106-
lines[i] = String(lines[i].dropFirst().dropLast())
107-
}
108-
}
45+
let rawArguments = action.arguments
10946

11047
let processedArgs = _processCompilerArguments(
111-
rawArguments: lines,
48+
rawArguments: rawArguments,
11249
contentToQuery: contentToQuery,
11350
language: language,
11451
sdkRoot: sdkRoot,
11552
initializedConfig: initializedConfig
11653
)
11754

118-
lines = processedArgs
119-
12055
logger.info("Finished processing compiler arguments")
12156
logger.logFullObjectInMultipleLogMessages(
12257
level: .debug,
12358
header: "Parsed compiler arguments",
124-
lines.joined(separator: "\n"),
59+
processedArgs.joined(separator: "\n"),
12560
)
126-
127-
return lines
61+
return processedArgs
12862
}
12963

13064
/// Processes compiler arguments for the LSP by removing unnecessary arguments and replacing placeholders.
@@ -153,6 +87,13 @@ enum CompilerArgumentsProcessor {
15387
var index = 0
15488
let count = rawArguments.count
15589

90+
// drop clang and swiftc compiler call
91+
switch language {
92+
case .swift: index = 2
93+
case .objective_c: index = 1
94+
default: break
95+
}
96+
15697
while index < count {
15798
let arg = rawArguments[index]
15899

Sources/SourceKitBazelBSP/SharedUtils/Shell/CommandRunner.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,10 @@
2020
import Foundation
2121

2222
public protocol CommandRunner {
23-
func run(_ cmd: String, cwd: String?) throws -> Data
23+
func run<T: DataConvertible>(_ cmd: String, cwd: String?) throws -> T
2424
}
2525

2626
extension CommandRunner {
27-
func run(_ cmd: String, cwd: String?) throws -> String {
28-
let data: Data = try run(cmd, cwd: cwd)
29-
let str = String(data: data, encoding: .utf8) ?? ""
30-
return str.trimmingCharacters(in: .whitespacesAndNewlines)
31-
}
32-
3327
func run(_ cmd: String) throws -> String {
3428
try run(cmd, cwd: nil)
3529
}
@@ -38,12 +32,12 @@ extension CommandRunner {
3832
// MARK: Bazel-related helpers
3933

4034
extension CommandRunner {
41-
func bazel(baseConfig: BaseServerConfig, rootUri: String, cmd: String) throws -> String {
35+
func bazel<T: DataConvertible>(baseConfig: BaseServerConfig, rootUri: String, cmd: String) throws -> T {
4236
try run(baseConfig.bazelWrapper + " " + cmd, cwd: rootUri)
4337
}
4438

4539
/// A regular bazel command, but at this BSP's special output base and taking into account the special index flags.
46-
func bazelIndexAction(initializedConfig: InitializedServerConfig, cmd: String) throws -> String {
40+
func bazelIndexAction<T: DataConvertible>(initializedConfig: InitializedServerConfig, cmd: String) throws -> T {
4741
return try bazelIndexAction(
4842
baseConfig: initializedConfig.baseConfig,
4943
outputBase: initializedConfig.outputBase,
@@ -53,12 +47,12 @@ extension CommandRunner {
5347
}
5448

5549
/// A regular bazel command, but at this BSP's special output base and taking into account the special index flags.
56-
func bazelIndexAction(
50+
func bazelIndexAction<T: DataConvertible>(
5751
baseConfig: BaseServerConfig,
5852
outputBase: String,
5953
cmd: String,
6054
rootUri: String,
61-
) throws -> String {
55+
) throws -> T {
6256
let indexFlags = baseConfig.indexFlags
6357
let additionalFlags: String
6458
if indexFlags.isEmpty {
@@ -70,3 +64,20 @@ extension CommandRunner {
7064
return try bazel(baseConfig: baseConfig, rootUri: rootUri, cmd: cmd)
7165
}
7266
}
67+
68+
public protocol DataConvertible {
69+
static func convert(from data: Data) -> Self
70+
}
71+
72+
extension String: DataConvertible {
73+
public static func convert(from data: Data) -> Self {
74+
let str = String(data: data, encoding: .utf8) ?? ""
75+
return str.trimmingCharacters(in: .whitespacesAndNewlines)
76+
}
77+
}
78+
79+
extension Data: DataConvertible {
80+
public static func convert(from data: Data) -> Self {
81+
return data
82+
}
83+
}

Sources/SourceKitBazelBSP/SharedUtils/Shell/ShellCommandRunner.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ enum ShellCommandRunnerError: LocalizedError {
3232
}
3333

3434
struct ShellCommandRunner: CommandRunner {
35-
func run(_ cmd: String, cwd: String?) throws -> Data {
35+
func run<T: DataConvertible>(_ cmd: String, cwd: String?) throws -> T {
3636
let task = Process()
3737
let stdout = Pipe()
3838
let stderr = Pipe()
@@ -64,6 +64,6 @@ struct ShellCommandRunner: CommandRunner {
6464
throw ShellCommandRunnerError.failed(cmd, stderrString)
6565
}
6666

67-
return data
67+
return T.convert(from: data)
6868
}
6969
}

0 commit comments

Comments
 (0)