Skip to content

Commit ea553ca

Browse files
Add reproducer for #301 (#313)
* Add reproducer for #301 Starts a reproducer for this issue * Attempt to fix * bump version
1 parent 2bb4662 commit ea553ca

File tree

14 files changed

+711
-5
lines changed

14 files changed

+711
-5
lines changed

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module(
22
name = "bazel-diff",
3-
version = "15.0.5",
3+
version = "16.0.0",
44
compatibility_level = 0,
55
)
66

MODULE.bazel.lock

Lines changed: 236 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@ workspace.
187187
Additional space separated Bazel command options used
188188
when invoking `bazel cquery`. This flag is has no
189189
effect if `--useCquery`is false.
190+
--cqueryExpression=<cqueryExpression>
191+
Custom cquery expression to use instead of the default
192+
'deps(//...:all-targets)'. This allows you to exclude
193+
problematic targets (e.g., analysis_test targets that
194+
are designed to fail). Example: 'deps(//:target1) +
195+
deps(//:target2)'. This flag has no effect if
196+
`--useCquery` is false.
190197
--fineGrainedHashExternalRepos=<fineGrainedHashExternalRepos>
191198
Comma separate list of external repos in which
192199
fine-grained hashes are computed for the targets.
@@ -239,6 +246,37 @@ workspace.
239246
of Bazel. You may want to fallback to use normal query mode in that case.
240247
See <https://github.com/bazelbuild/bazel/issues/17743> for more details.
241248

249+
#### Handling Failing Analysis Targets with `--cqueryExpression`
250+
251+
When using `--useCquery`, Bazel's `cquery` command analyzes all targets (executes their implementation functions). This can cause issues with targets that are intentionally designed to fail during analysis, such as:
252+
253+
- `analysis_test` targets from the Bazel `rules_testing` library
254+
- Other validation targets that verify build failures
255+
256+
With regular `bazel query`, these targets don't cause problems because `query` doesn't execute implementation functions. However, `cquery` will fail when it encounters these targets.
257+
258+
**Solution**: Use the `--cqueryExpression` flag to specify a custom query expression that excludes the problematic targets:
259+
260+
```bash
261+
bazel-diff generate-hashes \
262+
--useCquery \
263+
--cqueryExpression "deps(//:target1) + deps(//:target2)" \
264+
output.json
265+
```
266+
267+
**Important**: When crafting custom cquery expressions:
268+
269+
-**Don't use**: `deps(//...:all-targets) except //:failing_target`
270+
- This still analyzes the failing target during pattern expansion
271+
272+
-**Do use**: Explicitly specify which targets or packages to include:
273+
```bash
274+
--cqueryExpression "deps(//:target1) + deps(//:target2)"
275+
--cqueryExpression "deps(//src/...:*) + deps(//lib/...:*)"
276+
```
277+
278+
See [GitHub Issue #301](https://github.com/Tinder/bazel-diff/issues/301) for more details.
279+
242280
### What does the SHA256 value of `generate-hashes` represent?
243281

244282
`generate-hashes` is a canonical SHA256 value representing all attributes and inputs into a target. These inputs

cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.koin.core.component.inject
77

88
class BazelClient(
99
private val useCquery: Boolean,
10+
private val cqueryExpression: String?,
1011
private val fineGrainedHashExternalRepos: Set<String>,
1112
private val excludeExternalTargets: Boolean,
1213
) : KoinComponent {
@@ -42,7 +43,8 @@ class BazelClient(
4243
// In addition, we must include all source dependencies in this query in order for them to
4344
// show up in
4445
// `configuredRuleInput`. Hence, one must not filter them out with `kind(rule, deps(..))`.
45-
val mainTargets = queryService.query("deps(//...:all-targets)", useCquery = true)
46+
val expression = cqueryExpression ?: "deps(//...:all-targets)"
47+
val mainTargets = queryService.query(expression, useCquery = true)
4648
val repoTargets =
4749
if (repoTargetsQuery.isNotEmpty()) {
4850
queryService.query(repoTargetsQuery.joinToString(" + ") { "'$it'" })

cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ class GenerateHashesCommand : Callable<Int> {
124124
)
125125
var cqueryCommandOptions: List<String> = emptyList()
126126

127+
@CommandLine.Option(
128+
names = ["--cqueryExpression"],
129+
description =
130+
[
131+
"Custom cquery expression to use instead of the default 'deps(//...:all-targets)'. This allows you to exclude problematic targets (e.g., analysis_test targets that are designed to fail). Example: 'deps(//...:all-targets) except //path/to/failing:target'. This flag has no effect if `--useCquery` is false."],
132+
scope = CommandLine.ScopeType.INHERIT)
133+
var cqueryExpression: String? = null
134+
127135
@CommandLine.Option(
128136
names = ["-k", "--keep_going"],
129137
negatable = true,
@@ -200,6 +208,7 @@ class GenerateHashesCommand : Callable<Int> {
200208
bazelCommandOptions,
201209
cqueryCommandOptions,
202210
useCquery,
211+
cqueryExpression,
203212
keepGoing,
204213
depsMappingJSONPath != null,
205214
fineGrainedHashExternalRepos,

cli/src/main/kotlin/com/bazel_diff/di/Modules.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fun hasherModule(
2828
commandOptions: List<String>,
2929
cqueryOptions: List<String>,
3030
useCquery: Boolean,
31+
cqueryExpression: String?,
3132
keepGoing: Boolean,
3233
trackDeps: Boolean,
3334
fineGrainedHashExternalRepos: Set<String>,
@@ -75,7 +76,7 @@ fun hasherModule(
7576
single {
7677
BazelModService(workingDirectory, bazelPath, startupOptions, debug)
7778
}
78-
single { BazelClient(useCquery, updatedFineGrainedHashExternalRepos, excludeExternalTargets) }
79+
single { BazelClient(useCquery, cqueryExpression, updatedFineGrainedHashExternalRepos, excludeExternalTargets) }
7980
single { BuildGraphHasher(get()) }
8081
single { TargetHasher() }
8182
single { RuleHasher(useCquery, trackDeps, updatedFineGrainedHashExternalRepos) }

cli/src/test/kotlin/com/bazel_diff/Modules.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ fun testModule(): Module = module {
1414
val outputBase = Paths.get("output-base")
1515
val workingDirectory = Paths.get("working-directory")
1616
single<Logger> { SilentLogger }
17-
single { BazelClient(false, emptySet(), false) }
17+
single { BazelClient(false, null, emptySet(), false) }
1818
single { BuildGraphHasher(get()) }
1919
single { TargetHasher() }
2020
single { RuleHasher(false, true, emptySet()) }

cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,101 @@ class E2ETest {
720720
}
721721
}
722722

723+
@Test
724+
fun testCqueryWithFailingAnalysisTargets() {
725+
// Reproducer for https://github.com/Tinder/bazel-diff/issues/301
726+
// This test demonstrates the issue where cquery executes implementation functions
727+
// for all repository targets, causing targets designed to fail (like analysis_test
728+
// from rules_testing) to fail during cquery execution.
729+
//
730+
// The workspace contains:
731+
// - normal_target: A regular target that works fine
732+
// - dependent_target: Another regular target
733+
// - failing_analysis_target: A target designed to fail during analysis
734+
//
735+
// Expected behavior:
736+
// - With query: All targets are found without executing implementation functions
737+
// - With cquery: The failing_analysis_target causes analysis to fail
738+
// - With cquery + keep_going: Partial results should be returned (only the non-failing targets)
739+
//
740+
// This test verifies the current behavior and demonstrates the issue.
741+
742+
val workspace = copyTestWorkspace("cquery_failing_target")
743+
val outputDir = temp.newFolder()
744+
val from = File(outputDir, "starting_hashes.json")
745+
746+
val cli = CommandLine(BazelDiff())
747+
748+
// First, verify that generate-hashes works without --useCquery
749+
val exitCodeWithoutCquery = cli.execute(
750+
"generate-hashes",
751+
"-w",
752+
workspace.absolutePath,
753+
"-b",
754+
"bazel",
755+
from.absolutePath)
756+
757+
assertThat(exitCodeWithoutCquery).isEqualTo(0)
758+
759+
// Now, verify that generate-hashes fails with --useCquery due to the failing target
760+
// This demonstrates the issue described in #301
761+
val exitCodeWithCquery = cli.execute(
762+
"generate-hashes",
763+
"-w",
764+
workspace.absolutePath,
765+
"-b",
766+
"bazel",
767+
"--useCquery",
768+
from.absolutePath)
769+
770+
// The cquery should fail because it tries to analyze the failing_analysis_target
771+
// which is designed to fail during analysis
772+
assertThat(exitCodeWithCquery).isEqualTo(1)
773+
774+
// Test with --keep_going enabled (default behavior)
775+
// With keep_going, cquery returns partial results but still exits with code 1
776+
// The current implementation allows exit codes 0 and 3, but cquery with keep_going
777+
// returns exit code 1 when some targets fail analysis
778+
val exitCodeWithCqueryKeepGoing = cli.execute(
779+
"generate-hashes",
780+
"-w",
781+
workspace.absolutePath,
782+
"-b",
783+
"bazel",
784+
"--useCquery",
785+
"--keep_going",
786+
from.absolutePath)
787+
788+
// This currently fails (exit code 1) because bazel-diff only allows exit codes 0 and 3
789+
// but cquery with --keep_going returns exit code 1 when partial results are available
790+
assertThat(exitCodeWithCqueryKeepGoing).isEqualTo(1)
791+
792+
// Test with custom cquery expression to exclude the failing target
793+
// Note: We use explicit target selection instead of wildcard + except because
794+
// cquery analyzes targets during pattern expansion, so "//...:all-targets except X"
795+
// would still try to analyze X. The solution is to explicitly specify which targets to query.
796+
val exitCodeWithCustomExpression = cli.execute(
797+
"generate-hashes",
798+
"-w",
799+
workspace.absolutePath,
800+
"-b",
801+
"bazel",
802+
"--useCquery",
803+
"--cqueryExpression",
804+
"deps(//:normal_target) + deps(//:dependent_target)",
805+
from.absolutePath)
806+
807+
// With the custom expression that explicitly lists only the non-failing targets, this should succeed
808+
assertThat(exitCodeWithCustomExpression).isEqualTo(0)
809+
810+
// Verify the hashes were generated successfully and contain the expected targets
811+
val hashes = from.readText()
812+
assertThat(hashes.contains("normal_target")).isEqualTo(true)
813+
assertThat(hashes.contains("dependent_target")).isEqualTo(true)
814+
// The failing target should not be in the hashes since it wasn't included in the query
815+
assertThat(hashes.contains("failing_analysis_target")).isEqualTo(false)
816+
}
817+
723818
private fun copyTestWorkspace(path: String): File {
724819
val testProject = temp.newFolder()
725820

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
load(":failing_rule.bzl", "failing_rule")
2+
load("@bazel_skylib//rules:write_file.bzl", "write_file")
3+
4+
# A normal target that should work fine
5+
write_file(
6+
name = "normal_target",
7+
out = "normal.txt",
8+
content = ["This is a normal target"],
9+
)
10+
11+
# A target designed to fail during analysis
12+
# This simulates analysis_test targets that are meant to validate build failures
13+
failing_rule(
14+
name = "failing_analysis_target",
15+
)
16+
17+
# Another normal target that depends on the normal target
18+
write_file(
19+
name = "dependent_target",
20+
out = "dependent.txt",
21+
content = ["This depends on normal_target"],
22+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module(
2+
name = "cquery_failing_target_test",
3+
version = "0.0.0",
4+
)
5+
6+
bazel_dep(name = "bazel_skylib", version = "1.9.0")

0 commit comments

Comments
 (0)