@@ -13,6 +13,10 @@ import kotlinx.coroutines.runBlocking
1313import org.koin.core.component.KoinComponent
1414import org.koin.core.component.inject
1515
16+ private val versionComparator = compareBy<Triple <Int , Int , Int >> { it.first }
17+ .thenBy { it.second }
18+ .thenBy { it.third }
19+
1620class BazelQueryService (
1721 private val workingDirectory : Path ,
1822 private val bazelPath : Path ,
@@ -23,6 +27,44 @@ class BazelQueryService(
2327 private val noBazelrc : Boolean ,
2428) : KoinComponent {
2529 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
2668
2769 suspend fun query (query : String , useCquery : Boolean = false): List <BazelTarget > {
2870 // Unfortunately, there is still no direct way to tell if a target is compatible or not with the
@@ -44,10 +86,21 @@ class BazelQueryService(
4486 val targets =
4587 outputFile.inputStream().buffered().use { proto ->
4688 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 }
51104 } else {
52105 mutableListOf<Build .Target >()
53106 .apply {
@@ -98,9 +151,9 @@ class BazelQueryService(
98151 if (outputCompatibleTargets) {
99152 add(" starlark" )
100153 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(
104157 """
105158 def format(target):
106159 if providers(target) == None:
@@ -112,13 +165,11 @@ class BazelQueryService(
112165 return str(target.label)
113166 return ""
114167 """
115- .trimIndent())
116- add(cqueryOutputFile.toString())
168+ .trimIndent()
169+ )
170+ add(cqueryStarlarkFile.toString())
117171 } 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" )
122173 }
123174 } else {
124175 add(" streamed_proto" )
@@ -137,19 +188,21 @@ class BazelQueryService(
137188 }
138189 add(" --query_file" )
139190 add(queryFile.toString())
191+ if (canUseOutputFile) {
192+ add(" --output_file" )
193+ add(outputFile.toString())
194+ }
140195 }
141196
142197 logger.i { " Executing Query: $query " }
143198 logger.i { " Command: ${cmd.toTypedArray().joinToString()} " }
144- val result = runBlocking {
145- process(
199+ val result = process(
146200 * cmd.toTypedArray(),
147- stdout = Redirect .ToFile (outputFile),
201+ stdout = if (canUseOutputFile) Redirect . SILENT else Redirect .ToFile (outputFile),
148202 workingDirectory = workingDirectory.toFile(),
149203 stderr = Redirect .PRINT ,
150204 destroyForcibly = true ,
151205 )
152- }
153206
154207 if (result.resultCode != 0 )
155208 throw RuntimeException (" Bazel query failed, exit code ${result.resultCode} " )
0 commit comments