@@ -13,6 +13,10 @@ import kotlinx.coroutines.runBlocking
13
13
import org.koin.core.component.KoinComponent
14
14
import org.koin.core.component.inject
15
15
16
+ private val versionComparator = compareBy<Triple <Int , Int , Int >> { it.first }
17
+ .thenBy { it.second }
18
+ .thenBy { it.third }
19
+
16
20
class BazelQueryService (
17
21
private val workingDirectory : Path ,
18
22
private val bazelPath : Path ,
@@ -23,6 +27,44 @@ class BazelQueryService(
23
27
private val noBazelrc : Boolean ,
24
28
) : KoinComponent {
25
29
private val logger: Logger by inject()
30
+ private val version: Triple <Int , Int , Int > by lazy {
31
+ runBlocking { determineBazelVersion() }
32
+ }
33
+
34
+ @OptIn(ExperimentalCoroutinesApi ::class )
35
+ private suspend fun determineBazelVersion (): Triple <Int , Int , Int > {
36
+ val cmd = arrayOf(bazelPath.toString(), " --version" )
37
+ logger.i { " Executing Bazel version command: ${cmd.joinToString()} " }
38
+ val result = process(
39
+ * cmd,
40
+ stdout = Redirect .CAPTURE ,
41
+ workingDirectory = workingDirectory.toFile(),
42
+ stderr = Redirect .PRINT ,
43
+ destroyForcibly = true ,
44
+ )
45
+
46
+ if (result.resultCode != 0 ) {
47
+ throw RuntimeException (" Bazel version command failed, exit code ${result.resultCode} " )
48
+ }
49
+
50
+ if (result.output.size != 1 || ! result.output.first().startsWith(" bazel " )) {
51
+ throw RuntimeException (" Bazel version command returned unexpected output: ${result.output} " )
52
+ }
53
+ // Trim off any prerelease suffixes.
54
+ val versionString = result.output.first().removePrefix(" bazel " ).trim().split(' -' )[0 ]
55
+ val version = versionString.split(' .' ).map { it.toInt() }.toTypedArray()
56
+ return Triple (version[0 ], version[1 ], version[2 ])
57
+ }
58
+
59
+ // Use streamed_proto output for cquery if available. This is more efficient than the proto output.
60
+ // https://github.com/bazelbuild/bazel/commit/607d0f7335f95aa0ee236ba3c18ce2a232370cdb
61
+ private val canUseStreamedProtoWithCquery
62
+ get() = versionComparator.compare(version, Triple (7 , 0 , 0 )) >= 0
63
+
64
+ // Use an output file for (c)query if supported. This avoids excessively large stdout, which is sent out on the BES.
65
+ // https://github.com/bazelbuild/bazel/commit/514e9052f2c603c53126fbd9436bdd3ad3a1b0c7
66
+ private val canUseOutputFile
67
+ get() = versionComparator.compare(version, Triple (8 , 2 , 0 )) >= 0
26
68
27
69
suspend fun query (query : String , useCquery : Boolean = false): List <BazelTarget > {
28
70
// Unfortunately, there is still no direct way to tell if a target is compatible or not with the
@@ -44,10 +86,21 @@ class BazelQueryService(
44
86
val targets =
45
87
outputFile.inputStream().buffered().use { proto ->
46
88
if (useCquery) {
47
- val cqueryResult = AnalysisProtosV2 .CqueryResult .parseFrom(proto)
48
- cqueryResult.resultsList
49
- .mapNotNull { toBazelTarget(it.target) }
50
- .filter { it.name in compatibleTargetSet }
89
+ if (canUseStreamedProtoWithCquery) {
90
+ mutableListOf<AnalysisProtosV2 .CqueryResult >()
91
+ .apply {
92
+ while (true ) {
93
+ val result = AnalysisProtosV2 .CqueryResult .parseDelimitedFrom(proto) ? : break
94
+ // EOF
95
+ add(result)
96
+ }
97
+ }
98
+ .flatMap { it.resultsList }
99
+ } else {
100
+ AnalysisProtosV2 .CqueryResult .parseFrom(proto).resultsList
101
+ }
102
+ .mapNotNull { toBazelTarget(it.target) }
103
+ .filter { it.name in compatibleTargetSet }
51
104
} else {
52
105
mutableListOf<Build .Target >()
53
106
.apply {
@@ -98,9 +151,9 @@ class BazelQueryService(
98
151
if (outputCompatibleTargets) {
99
152
add(" starlark" )
100
153
add(" --starlark:file" )
101
- val cqueryOutputFile = Files .createTempFile(null , " .cquery" ).toFile()
102
- cqueryOutputFile .deleteOnExit()
103
- cqueryOutputFile .writeText(
154
+ val cqueryStarlarkFile = Files .createTempFile(null , " .cquery" ).toFile()
155
+ cqueryStarlarkFile .deleteOnExit()
156
+ cqueryStarlarkFile .writeText(
104
157
"""
105
158
def format(target):
106
159
if providers(target) == None:
@@ -112,13 +165,11 @@ class BazelQueryService(
112
165
return str(target.label)
113
166
return ""
114
167
"""
115
- .trimIndent())
116
- add(cqueryOutputFile.toString())
168
+ .trimIndent()
169
+ )
170
+ add(cqueryStarlarkFile.toString())
117
171
} else {
118
- // Unfortunately, cquery does not support streamed_proto yet.
119
- // See https://github.com/bazelbuild/bazel/issues/17743. This poses an issue for large
120
- // monorepos.
121
- add(" proto" )
172
+ add(if (canUseStreamedProtoWithCquery) " streamed_proto" else " proto" )
122
173
}
123
174
} else {
124
175
add(" streamed_proto" )
@@ -137,19 +188,21 @@ class BazelQueryService(
137
188
}
138
189
add(" --query_file" )
139
190
add(queryFile.toString())
191
+ if (canUseOutputFile) {
192
+ add(" --output_file" )
193
+ add(outputFile.toString())
194
+ }
140
195
}
141
196
142
197
logger.i { " Executing Query: $query " }
143
198
logger.i { " Command: ${cmd.toTypedArray().joinToString()} " }
144
- val result = runBlocking {
145
- process(
199
+ val result = process(
146
200
* cmd.toTypedArray(),
147
- stdout = Redirect .ToFile (outputFile),
201
+ stdout = if (canUseOutputFile) Redirect . SILENT else Redirect .ToFile (outputFile),
148
202
workingDirectory = workingDirectory.toFile(),
149
203
stderr = Redirect .PRINT ,
150
204
destroyForcibly = true ,
151
205
)
152
- }
153
206
154
207
if (result.resultCode != 0 )
155
208
throw RuntimeException (" Bazel query failed, exit code ${result.resultCode} " )
0 commit comments