From 8c73949c7f9314094998e77c6a1e485c60645966 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 14:12:32 +0000 Subject: [PATCH] Remove the Kotlin/JVM implementation; the Rust CLI is now the only bazel-diff The Rust implementation under src/ has shipped alongside the original Kotlin CLI as `//:bazel-diff-rust` and passes the same behavioral suite. This makes it the only implementation: `//:bazel-diff` now resolves to `//src:bazel-diff`, and everything that existed only to build, test, compare or publish the JVM CLI is gone. Removed - cli/ (Kotlin sources, JUnit tests, ktfmt/format targets) and proto/ (the java_proto_library wrappers; the Rust build generates its bindings from rust/proto/*.proto via the cargo build script). - rules_java, rules_kotlin, rules_jvm_external, the maven extension, stardoc, rules_proto, grpc and the ktfmt repo from MODULE.bazel, plus the rules_kotlin LCOV-merger patch. MODULE.bazel.lock regenerated. - WORKSPACE support (WORKSPACE, WORKSPACE.bzlmod, repositories.bzl, constants.bzl, artifacts.bzl, extensions.bzl, maven_install.json): the Rust build is crate_universe/bzlmod-only, so nothing could be built through it. - The Kotlin-vs-Rust comparison tooling (tools/benchmark.py, tools/perf_gate.py, tools/perf_workload.py and their tests, the perf-gate workflow) and docs/kotlin-*.md, which have no second implementation to compare against. - The Kotlin half of the e2e splitter (tools/e2e), the ktfmt format cron and the JVM CI jobs (test-jre21, test-jre11-run-example's JDK setup, release.yaml's java-tests, the deploy JAR build and release asset). Moved / repointed - E2E fixtures: cli/src/test/resources/{fixture,workspaces} -> tests/resources/, with .bazelignore, .gitattributes, tests/BUILD and tests/e2e/support/mod.rs following. - Formatter targets: cli/format -> tools/format (buildifier, rustfmt); `//:format` is now the rustfmt fixer. - //release:bazel-diff-rust -> //release:bazel-diff (asset file names keep the bazel-diff-rust-- prefix so existing download URLs still resolve). //:bazel-diff-rust stays as an alias for BCR verify_targets and existing consumers. - The serve harnesses, the Alpine stress container and the Firecracker bench/guest-image scripts run the static musl binary instead of the deploy JAR; the corresponding workflows build `//release:bazel-diff --config=release-musl`. - tools/coverage_check.py defaults to `src/`; CI's coverage matrix is now the `coverage` job and gates Rust and Go independently. - README regenerated from the template with the Rust CLI's help text; installation now covers prebuilt binaries and bzlmod only. Verified: `bazel build //:bazel-diff` (Bazel 8.7.0) with the trimmed module graph, `cargo test --lib --bin`, and the Python tooling tests. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01BBybxwigT5qLjL6iuXC463 --- .bazelignore | 2 +- .bazelrc | 15 +- .bcr/presubmit.yml | 3 +- .claude/skills/coverage-status/SKILL.md | 23 +- .claude/skills/improve-coverage/SKILL.md | 54 +- .gitattributes | 7 +- .github/workflows/assert_static_binary.sh | 2 +- .github/workflows/buildifier.yaml | 13 +- .github/workflows/ci.yaml | 185 +- .github/workflows/coverage_badge.yaml | 7 +- .github/workflows/firecracker-e2e.yml | 18 +- .github/workflows/format.yaml | 63 - .../workflows/integration_external_target.yml | 9 +- .github/workflows/perf-gate.yaml | 85 - .github/workflows/release.yaml | 99 +- .github/workflows/release_prep.sh | 21 +- .github/workflows/serve-consistency.yml | 5 - .github/workflows/serve-harness.yml | 9 +- .../workflows/serve-stress-alpine-real.yml | 15 +- .github/workflows/serve-stress-alpine.yml | 36 +- .github/workflows/serve-stress-real.yml | 5 - .github/workflows/serve-stress.yml | 5 - BUILD | 18 +- MODULE.bazel | 95 +- MODULE.bazel.lock | 1025 +----- Makefile | 87 +- README.md | 789 ++-- WORKSPACE | 80 - WORKSPACE.bzlmod | 1 - artifacts.bzl | 14 - bazel-diff-example.ps1 | 5 - cli/BUILD | 500 --- cli/src/main/kotlin/com/bazel_diff/Main.kt | 15 - .../com/bazel_diff/bazel/BazelClient.kt | 140 - .../com/bazel_diff/bazel/BazelModService.kt | 135 - .../com/bazel_diff/bazel/BazelQueryService.kt | 651 ---- .../kotlin/com/bazel_diff/bazel/BazelRule.kt | 169 - .../bazel_diff/bazel/BazelSourceFileTarget.kt | 6 - .../com/bazel_diff/bazel/BazelTarget.kt | 50 - .../com/bazel_diff/bazel/BazelTargetType.kt | 10 - .../com/bazel_diff/bazel/ModuleGraphParser.kt | 309 -- .../kotlin/com/bazel_diff/cli/BazelDiff.kt | 44 - .../com/bazel_diff/cli/FingerprintCommand.kt | 154 - .../com/bazel_diff/cli/FingerprintGatherer.kt | 113 - .../bazel_diff/cli/GenerateHashesCommand.kt | 276 -- .../cli/GetImpactedTargetsCommand.kt | 219 -- .../kotlin/com/bazel_diff/cli/ServeCommand.kt | 594 --- .../com/bazel_diff/cli/VersionProvider.kt | 20 - .../com/bazel_diff/cli/WarmupCommand.kt | 98 - .../cli/converter/ByteSizeConverter.kt | 39 - .../converter/CommaSeparatedValueConverter.kt | 9 - .../cli/converter/DurationConverter.kt | 40 - .../cli/converter/NormalisingPathConverter.kt | 9 - .../cli/converter/OptionsConverter.kt | 9 - .../main/kotlin/com/bazel_diff/di/Modules.kt | 190 - .../com/bazel_diff/extensions/ByteArray.kt | 15 - .../com/bazel_diff/extensions/ByteBuffer.kt | 9 - .../extensions/HashingExtensions.kt | 35 - .../com/bazel_diff/hash/BuildGraphHasher.kt | 281 -- .../bazel_diff/hash/ExternalRepoResolver.kt | 98 - .../bazel_diff/hash/HashInvocationContext.kt | 23 - .../kotlin/com/bazel_diff/hash/RuleHasher.kt | 206 -- .../com/bazel_diff/hash/SourceFileHasher.kt | 169 - .../com/bazel_diff/hash/TargetDigest.kt | 56 - .../kotlin/com/bazel_diff/hash/TargetHash.kt | 41 - .../com/bazel_diff/hash/TargetHasher.kt | 78 - .../CalculateImpactedTargetsInteractor.kt | 473 --- .../interactor/DeserialiseHashesInteractor.kt | 95 - .../interactor/FingerprintInteractor.kt | 96 - .../interactor/GenerateHashesInteractor.kt | 97 - .../bazel_diff/interactor/TargetTypeFilter.kt | 21 - .../bazel_diff/io/ByteBufferObjectFactory.kt | 29 - .../com/bazel_diff/io/ByteBufferPool.kt | 23 - .../com/bazel_diff/io/ContentHashProvider.kt | 14 - .../kotlin/com/bazel_diff/log/StderrLogger.kt | 34 - .../kotlin/com/bazel_diff/process/Process.kt | 106 - .../com/bazel_diff/process/ProcessResult.kt | 6 - .../kotlin/com/bazel_diff/process/Redirect.kt | 59 - .../com/bazel_diff/server/BazelDiffServer.kt | 367 -- .../com/bazel_diff/server/CachePruner.kt | 72 - .../kotlin/com/bazel_diff/server/GitClient.kt | 229 -- .../com/bazel_diff/server/HashCacheStorage.kt | 226 -- .../com/bazel_diff/server/HashService.kt | 262 -- .../server/ImpactedTargetsService.kt | 304 -- .../com/bazel_diff/server/MetricsService.kt | 107 - .../com/bazel_diff/server/QueryProfiler.kt | 159 - .../bazel_diff/server/S3HashCacheStorage.kt | 122 - .../server/TieredHashCacheStorage.kt | 40 - cli/src/test/kotlin/com/bazel_diff/Modules.kt | 38 - .../com/bazel_diff/bazel/BazelClientTest.kt | 373 -- .../bazel_diff/bazel/BazelModServiceTest.kt | 248 -- .../com/bazel_diff/bazel/BazelRuleTest.kt | 524 --- .../com/bazel_diff/bazel/BazelTargetTest.kt | 76 - .../bazel_diff/bazel/BazelTargetTypeTest.kt | 33 - .../bazel_diff/bazel/ModuleGraphParserTest.kt | 730 ---- .../bazel/StderrPollutionRegressionTest.kt | 166 - .../com/bazel_diff/cli/BazelDiffTest.kt | 67 - .../bazel_diff/cli/FingerprintCommandTest.kt | 110 - .../bazel_diff/cli/FingerprintGathererTest.kt | 95 - .../com/bazel_diff/cli/ServeCommandTest.kt | 698 ---- .../com/bazel_diff/cli/VersionProviderTest.kt | 46 - .../com/bazel_diff/cli/WarmupCommandTest.kt | 137 - .../cli/converter/ByteSizeConverterTest.kt | 36 - .../CommaSeparatedValueConverterTest.kt | 13 - .../cli/converter/DurationConverterTest.kt | 38 - .../converter/NormalisingPathConverterTest.kt | 15 - .../cli/converter/OptionsConverterTest.kt | 13 - .../test/kotlin/com/bazel_diff/e2e/E2ETest.kt | 3244 ----------------- .../bazel_diff/hash/BuildGraphHasherTest.kt | 515 --- .../bazel_diff/hash/FakeSourceFileHasher.kt | 37 - .../kotlin/com/bazel_diff/hash/HashDiffer.kt | 45 - .../hash/RuleHasherAlwaysAffectedTagsTest.kt | 128 - .../com/bazel_diff/hash/RuleHasherTest.kt | 440 --- .../bazel_diff/hash/SourceFileHasherTest.kt | 444 --- .../com/bazel_diff/hash/TargetHashTest.kt | 49 - .../kotlin/com/bazel_diff/hash/fixture/foo.ts | 1 - ...teImpactedTargetsInteractorIssue335Test.kt | 145 - ...mpactedTargetsInteractorModuleQueryTest.kt | 374 -- .../CalculateImpactedTargetsInteractorTest.kt | 1352 ------- .../DeserialiseHashesInteractorTest.kt | 132 - .../interactor/FingerprintInteractorTest.kt | 110 - .../bazel_diff/io/ContentHashProviderTest.kt | 48 - .../com/bazel_diff/io/fixture/correct.json | 4 - .../com/bazel_diff/io/fixture/wrong.json | 5 - .../com/bazel_diff/log/StderrLoggerTest.kt | 67 - .../process/ProcessStdinHangRegressionTest.kt | 43 - .../bazel_diff/server/BazelDiffServerTest.kt | 683 ---- .../com/bazel_diff/server/CachePrunerTest.kt | 105 - .../com/bazel_diff/server/GitClientTest.kt | 243 -- .../com/bazel_diff/server/HashServiceTest.kt | 337 -- .../server/ImpactedTargetsServiceTest.kt | 503 --- .../server/LocalDiskHashCacheStorageTest.kt | 285 -- .../bazel_diff/server/MetricsServiceTest.kt | 91 - .../bazel_diff/server/QueryProfilerTest.kt | 122 - .../server/S3HashCacheStorageTest.kt | 181 - .../server/TieredHashCacheStorageTest.kt | 124 - .../org.mockito.plugins.MockMaker | 1 - constants.bzl | 10 - docs/firecracker-snapshots.md | 27 +- docs/kotlin-rust-perf-gate.md | 150 - docs/kotlin-rust-test-parity.md | 63 - docs/kotlin-vs-rust-benchmark.md | 108 - extensions.bzl | 14 - maven_install.json | 1599 -------- proto/BUILD | 26 - proto/analysis_v2.proto | 294 -- proto/build.proto | 601 --- release/BUILD | 6 +- release/defs.bzl | 8 +- repositories.bzl | 123 - src/main.rs | 2 +- tests/BUILD | 32 +- tests/e2e/support/mod.rs | 2 +- .../fixture/bzlmod-cc-transitive-test-1.zip | Bin .../fixture/bzlmod-cc-transitive-test-2.zip | Bin ...od-cc-transitive-test-impacted-targets.txt | 0 .../fixture/bzlmod-show-repo-test-1.zip | Bin .../fixture/bzlmod-show-repo-test-2.zip | Bin .../fixture/bzlmod-transitive-test-1.zip | Bin .../fixture/bzlmod-transitive-test-2.zip | Bin ...ransitive-test-cquery-impacted-targets.txt | 0 ...zlmod-transitive-test-impacted-targets.txt | 0 ...d-code-change-android-impacted-targets.txt | 0 ...droid-code-change-jre-impacted-targets.txt | 0 .../cquery-test-android-code-change.zip | Bin .../resources/fixture/cquery-test-base.zip | Bin ...guava-upgrade-android-impacted-targets.txt | 0 ...est-guava-upgrade-jre-impacted-targets.txt | 0 .../fixture/cquery-test-guava-upgrade.zip | Bin ...sh-bzlmod-cquery-test-impacted-targets.txt | 0 .../fine-grained-hash-bzlmod-test-1.zip | Bin .../fine-grained-hash-bzlmod-test-2.zip | Bin ...ined-hash-bzlmod-test-impacted-targets.txt | 0 ...fine-grained-hash-external-repo-test-1.zip | Bin ...fine-grained-hash-external-repo-test-2.zip | Bin ...sh-external-repo-test-impacted-targets.txt | 0 .../impacted_targets-1-2-rule-sourcefile.txt | 0 .../fixture/impacted_targets-1-2.txt | 0 .../resources/fixture/integration-test-1.zip | Bin .../resources/fixture/integration-test-2.zip | Bin .../workspaces/always_affected_external/BUILD | 0 .../always_affected_external/MODULE.bazel | 0 .../always_affected_external/declared_src.txt | 0 .../always_affected_external/scanned_data.txt | 0 .../bzl_seed_overtrigger/MODULE.bazel | 0 .../workspaces/bzl_seed_overtrigger/WORKSPACE | 0 .../workspaces/bzl_seed_overtrigger/pkg/BUILD | 0 .../workspaces/bzlmod_local_repo/.bazelignore | 0 .../workspaces/bzlmod_local_repo/BUILD | 0 .../workspaces/bzlmod_local_repo/MODULE.bazel | 0 .../bzlmod_local_repo/MODULE.bazel.lock | 0 .../workspaces/bzlmod_local_repo/WORKSPACE | 0 .../bzlmod_local_repo/dep_repo/BUILD | 0 .../bzlmod_local_repo/dep_repo/MODULE.bazel | 0 .../bzlmod_local_repo/dep_repo/WORKSPACE | 0 .../bzlmod_local_repo/dep_repo/hello | 0 .../bzlmod_transitive_external/.bazelignore | 0 .../bzlmod_transitive_external/BUILD | 0 .../bzlmod_transitive_external/MODULE.bazel | 0 .../MODULE.bazel.lock | 0 .../bzlmod_transitive_external/WORKSPACE | 0 .../bzlmod_transitive_external/inner/BUILD | 0 .../inner/MODULE.bazel | 0 .../inner/WORKSPACE | 0 .../bzlmod_transitive_external/inner/data.txt | 0 .../bzlmod_transitive_external/outer/BUILD | 0 .../outer/MODULE.bazel | 0 .../outer/WORKSPACE | 0 .../workspaces/cquery_failing_target/BUILD | 0 .../cquery_failing_target/MODULE.bazel | 0 .../cquery_failing_target/MODULE.bazel.lock | 0 .../cquery_failing_target/README.md | 0 .../cquery_failing_target/WORKSPACE | 0 .../cquery_failing_target/failing_rule.bzl | 0 .../cross_file_macro_rule/MODULE.bazel | 0 .../cross_file_macro_rule/WORKSPACE | 0 .../cross_file_macro_rule/app/BUILD | 0 .../cross_file_macro_rule/defs/BUILD | 0 .../cross_file_macro_rule/defs/macro.bzl | 0 .../cross_file_macro_rule/defs/rule.bzl | 0 .../workspaces/distance_metrics/A/BUILD | 0 .../workspaces/distance_metrics/A/one.sh | 0 .../workspaces/distance_metrics/A/three.sh | 0 .../workspaces/distance_metrics/BUILD | 0 .../workspaces/distance_metrics/MODULE.bazel | 0 .../distance_metrics/MODULE.bazel.lock | 0 .../workspaces/distance_metrics/WORKSPACE | 0 .../workspaces/distance_metrics/lib.sh | 0 .../resources/workspaces/go_mod_change/BUILD | 0 .../workspaces/go_mod_change/MODULE.bazel | 0 .../go_mod_change/MODULE.bazel.lock | 0 .../workspaces/go_mod_change/WORKSPACE | 0 .../resources/workspaces/go_mod_change/go.mod | 0 .../resources/workspaces/go_mod_change/go.sum | 0 .../resources/workspaces/go_mod_change/lib.go | 0 .../workspaces/go_mod_change/lib_test.go | 0 .../workspaces/hub_spoke_external/.bazelrc | 0 .../workspaces/hub_spoke_external/BUILD | 0 .../workspaces/hub_spoke_external/WORKSPACE | 0 .../hub_spoke_external/fake_pip.bzl | 0 .../workspaces/keep_going_repo_failure/BUILD | 0 .../keep_going_repo_failure/MODULE.bazel | 0 .../keep_going_repo_failure/bad/BUILD | 0 .../keep_going_repo_failure/ext.bzl | 0 .../keep_going_repo_failure/good/BUILD | 0 .../workspaces/macro_invalidation/BUILD | 0 .../macro_invalidation/MODULE.bazel | 0 .../macro_invalidation/MODULE.bazel.lock | 0 .../workspaces/macro_invalidation/WORKSPACE | 0 .../workspaces/macro_invalidation/image.png | 0 .../macro_invalidation/miniature.bzl | 0 .../workspaces/module_bazel_comment/BUILD | 0 .../module_bazel_comment/MODULE.bazel | 0 .../module_bazel_comment/MODULE.bazel.lock | 0 .../workspaces/module_bazel_comment/WORKSPACE | 0 .../workspaces/module_bazel_comment/bin.sh | 0 .../workspaces/module_bazel_comment/lib.sh | 0 .../package_group_dropped/MODULE.bazel | 0 .../package_group_dropped/consumer/BUILD | 0 .../package_group_dropped/lib/BUILD | 0 .../package_group_dropped/lib/defs.bzl | 0 .../proto_external_version_bump/.bazelignore | 0 .../proto_external_version_bump/BUILD | 0 .../proto_external_version_bump/Consumer.java | 0 .../proto_external_version_bump/MODULE.bazel | 0 .../MODULE.bazel.lock | 0 .../proto_external_version_bump/WORKSPACE | 0 .../proto_dep/BUILD | 0 .../proto_dep/MODULE.bazel | 0 .../proto_dep/WORKSPACE | 0 .../proto_dep/greeting.proto | 0 .../rule_bzl_overtrigger/MODULE.bazel | 0 .../workspaces/rule_bzl_overtrigger/WORKSPACE | 0 .../workspaces/rule_bzl_overtrigger/app/BUILD | 0 .../rule_bzl_overtrigger/app/data.txt | 0 .../rule_bzl_overtrigger/defs/BUILD | 0 .../rule_bzl_overtrigger/defs/thing.bzl | 0 .../workspaces/serve_bzl_cache/BUILD | 0 .../workspaces/serve_bzl_cache/MODULE.bazel | 0 .../workspaces/serve_bzl_cache/README.md | 0 .../workspaces/serve_bzl_cache/WORKSPACE | 0 .../workspaces/serve_bzl_cache/defs.bzl | 0 .../wrapped_external_repo/.bazelignore | 0 .../workspaces/wrapped_external_repo/BUILD | 0 .../wrapped_external_repo/MODULE.bazel | 0 .../wrapped_external_repo/MODULE.bazel.lock | 0 .../wrapped_external_repo/WORKSPACE | 0 .../wrapped_external_repo/inner_repo/BUILD | 0 .../inner_repo/MODULE.bazel | 0 .../inner_repo/WORKSPACE | 0 .../wrapped_external_repo/inner_repo/data.txt | 0 .../wrapped_external_repo/middle_repo/BUILD | 0 .../middle_repo/MODULE.bazel | 0 .../middle_repo/WORKSPACE | 0 tools/BUILD | 90 +- tools/benchmark.py | 1028 ------ tools/benchmark_test.py | 230 -- tools/coverage/README.md | 28 +- tools/coverage/defs.bzl | 11 +- tools/coverage/rules_kotlin_lcov_merger.patch | 19 - tools/coverage/src/pattern.rs | 2 +- tools/coverage_check.py | 19 +- tools/coverage_check_test.py | 50 +- tools/e2e/BUILD | 34 +- tools/e2e/README.md | 97 +- tools/e2e/defs.bzl | 149 +- tools/e2e/kotlin_e2e_cases.bzl | 176 - tools/e2e/split_e2e_tests.py | 218 +- tools/e2e/split_e2e_tests_test.py | 326 +- tools/firecracker/README.md | 24 +- tools/firecracker/bench/Dockerfile | 12 +- tools/firecracker/bench/build_guest_image.sh | 22 +- tools/firecracker/bench/run_docker_bench.sh | 17 +- {cli => tools}/format/BUILD | 19 +- tools/mock_s3.py | 6 +- tools/perf_gate.py | 970 ----- tools/perf_gate_test.py | 1152 ------ tools/perf_workload.py | 639 ---- tools/readme_template.md | 226 +- tools/serve_consistency.py | 26 +- tools/serve_consistency_test.py | 6 +- tools/serve_harness.py | 12 +- tools/serve_stress.py | 6 +- tools/serve_stress_alpine.sh | 43 +- 324 files changed, 957 insertions(+), 31625 deletions(-) delete mode 100644 .github/workflows/format.yaml delete mode 100644 .github/workflows/perf-gate.yaml delete mode 100644 WORKSPACE delete mode 100644 WORKSPACE.bzlmod delete mode 100644 artifacts.bzl delete mode 100644 cli/BUILD delete mode 100644 cli/src/main/kotlin/com/bazel_diff/Main.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/bazel/BazelModService.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/bazel/BazelRule.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/bazel/BazelSourceFileTarget.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/bazel/BazelTarget.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/bazel/BazelTargetType.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/bazel/ModuleGraphParser.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/BazelDiff.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/FingerprintCommand.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/FingerprintGatherer.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/GetImpactedTargetsCommand.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/ServeCommand.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/VersionProvider.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/WarmupCommand.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/converter/ByteSizeConverter.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/converter/CommaSeparatedValueConverter.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/converter/DurationConverter.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/converter/NormalisingPathConverter.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/cli/converter/OptionsConverter.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/di/Modules.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/extensions/ByteArray.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/extensions/ByteBuffer.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/extensions/HashingExtensions.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/hash/ExternalRepoResolver.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/hash/HashInvocationContext.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/hash/RuleHasher.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/hash/TargetDigest.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/hash/TargetHash.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/hash/TargetHasher.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/interactor/CalculateImpactedTargetsInteractor.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/interactor/FingerprintInteractor.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/interactor/GenerateHashesInteractor.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/interactor/TargetTypeFilter.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/io/ByteBufferObjectFactory.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/io/ByteBufferPool.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/io/ContentHashProvider.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/log/StderrLogger.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/process/Process.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/process/ProcessResult.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/process/Redirect.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/BazelDiffServer.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/CachePruner.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/GitClient.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/HashCacheStorage.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/HashService.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/ImpactedTargetsService.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/MetricsService.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/QueryProfiler.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/S3HashCacheStorage.kt delete mode 100644 cli/src/main/kotlin/com/bazel_diff/server/TieredHashCacheStorage.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/Modules.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/bazel/BazelClientTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/bazel/BazelModServiceTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/bazel/BazelRuleTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/bazel/BazelTargetTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/bazel/BazelTargetTypeTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/bazel/ModuleGraphParserTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/bazel/StderrPollutionRegressionTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/BazelDiffTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/FingerprintCommandTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/FingerprintGathererTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/ServeCommandTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/VersionProviderTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/WarmupCommandTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/converter/ByteSizeConverterTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/converter/CommaSeparatedValueConverterTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/converter/DurationConverterTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/converter/NormalisingPathConverterTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/cli/converter/OptionsConverterTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/BuildGraphHasherTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/FakeSourceFileHasher.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/HashDiffer.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/RuleHasherAlwaysAffectedTagsTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/RuleHasherTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/SourceFileHasherTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/TargetHashTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/hash/fixture/foo.ts delete mode 100644 cli/src/test/kotlin/com/bazel_diff/interactor/CalculateImpactedTargetsInteractorIssue335Test.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/interactor/CalculateImpactedTargetsInteractorModuleQueryTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/interactor/CalculateImpactedTargetsInteractorTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractorTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/interactor/FingerprintInteractorTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/io/ContentHashProviderTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/io/fixture/correct.json delete mode 100644 cli/src/test/kotlin/com/bazel_diff/io/fixture/wrong.json delete mode 100644 cli/src/test/kotlin/com/bazel_diff/log/StderrLoggerTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/process/ProcessStdinHangRegressionTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/BazelDiffServerTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/CachePrunerTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/GitClientTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/HashServiceTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/ImpactedTargetsServiceTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/LocalDiskHashCacheStorageTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/MetricsServiceTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/QueryProfilerTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/S3HashCacheStorageTest.kt delete mode 100644 cli/src/test/kotlin/com/bazel_diff/server/TieredHashCacheStorageTest.kt delete mode 100644 cli/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker delete mode 100644 constants.bzl delete mode 100644 docs/kotlin-rust-perf-gate.md delete mode 100644 docs/kotlin-rust-test-parity.md delete mode 100644 docs/kotlin-vs-rust-benchmark.md delete mode 100644 extensions.bzl delete mode 100644 maven_install.json delete mode 100644 proto/BUILD delete mode 100644 proto/analysis_v2.proto delete mode 100644 proto/build.proto delete mode 100644 repositories.bzl rename {cli/src/test => tests}/resources/fixture/bzlmod-cc-transitive-test-1.zip (100%) rename {cli/src/test => tests}/resources/fixture/bzlmod-cc-transitive-test-2.zip (100%) rename {cli/src/test => tests}/resources/fixture/bzlmod-cc-transitive-test-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/bzlmod-show-repo-test-1.zip (100%) rename {cli/src/test => tests}/resources/fixture/bzlmod-show-repo-test-2.zip (100%) rename {cli/src/test => tests}/resources/fixture/bzlmod-transitive-test-1.zip (100%) rename {cli/src/test => tests}/resources/fixture/bzlmod-transitive-test-2.zip (100%) rename {cli/src/test => tests}/resources/fixture/bzlmod-transitive-test-cquery-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/bzlmod-transitive-test-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/cquery-test-android-code-change-android-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/cquery-test-android-code-change-jre-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/cquery-test-android-code-change.zip (100%) rename {cli/src/test => tests}/resources/fixture/cquery-test-base.zip (100%) rename {cli/src/test => tests}/resources/fixture/cquery-test-guava-upgrade-android-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/cquery-test-guava-upgrade-jre-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/cquery-test-guava-upgrade.zip (100%) rename {cli/src/test => tests}/resources/fixture/fine-grained-hash-bzlmod-cquery-test-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/fine-grained-hash-bzlmod-test-1.zip (100%) rename {cli/src/test => tests}/resources/fixture/fine-grained-hash-bzlmod-test-2.zip (100%) rename {cli/src/test => tests}/resources/fixture/fine-grained-hash-bzlmod-test-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/fine-grained-hash-external-repo-test-1.zip (100%) rename {cli/src/test => tests}/resources/fixture/fine-grained-hash-external-repo-test-2.zip (100%) rename {cli/src/test => tests}/resources/fixture/fine-grained-hash-external-repo-test-impacted-targets.txt (100%) rename {cli/src/test => tests}/resources/fixture/impacted_targets-1-2-rule-sourcefile.txt (100%) rename {cli/src/test => tests}/resources/fixture/impacted_targets-1-2.txt (100%) rename {cli/src/test => tests}/resources/fixture/integration-test-1.zip (100%) rename {cli/src/test => tests}/resources/fixture/integration-test-2.zip (100%) rename {cli/src/test => tests}/resources/workspaces/always_affected_external/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/always_affected_external/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/always_affected_external/declared_src.txt (100%) rename {cli/src/test => tests}/resources/workspaces/always_affected_external/scanned_data.txt (100%) rename {cli/src/test => tests}/resources/workspaces/bzl_seed_overtrigger/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/bzl_seed_overtrigger/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/bzl_seed_overtrigger/pkg/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_local_repo/.bazelignore (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_local_repo/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_local_repo/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_local_repo/MODULE.bazel.lock (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_local_repo/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_local_repo/dep_repo/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_local_repo/dep_repo/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_local_repo/dep_repo/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_local_repo/dep_repo/hello (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/.bazelignore (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/MODULE.bazel.lock (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/inner/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/inner/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/inner/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/inner/data.txt (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/outer/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/outer/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/bzlmod_transitive_external/outer/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/cquery_failing_target/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/cquery_failing_target/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/cquery_failing_target/MODULE.bazel.lock (100%) rename {cli/src/test => tests}/resources/workspaces/cquery_failing_target/README.md (100%) rename {cli/src/test => tests}/resources/workspaces/cquery_failing_target/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/cquery_failing_target/failing_rule.bzl (100%) rename {cli/src/test => tests}/resources/workspaces/cross_file_macro_rule/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/cross_file_macro_rule/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/cross_file_macro_rule/app/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/cross_file_macro_rule/defs/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/cross_file_macro_rule/defs/macro.bzl (100%) rename {cli/src/test => tests}/resources/workspaces/cross_file_macro_rule/defs/rule.bzl (100%) rename {cli/src/test => tests}/resources/workspaces/distance_metrics/A/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/distance_metrics/A/one.sh (100%) rename {cli/src/test => tests}/resources/workspaces/distance_metrics/A/three.sh (100%) rename {cli/src/test => tests}/resources/workspaces/distance_metrics/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/distance_metrics/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/distance_metrics/MODULE.bazel.lock (100%) rename {cli/src/test => tests}/resources/workspaces/distance_metrics/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/distance_metrics/lib.sh (100%) rename {cli/src/test => tests}/resources/workspaces/go_mod_change/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/go_mod_change/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/go_mod_change/MODULE.bazel.lock (100%) rename {cli/src/test => tests}/resources/workspaces/go_mod_change/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/go_mod_change/go.mod (100%) rename {cli/src/test => tests}/resources/workspaces/go_mod_change/go.sum (100%) rename {cli/src/test => tests}/resources/workspaces/go_mod_change/lib.go (100%) rename {cli/src/test => tests}/resources/workspaces/go_mod_change/lib_test.go (100%) rename {cli/src/test => tests}/resources/workspaces/hub_spoke_external/.bazelrc (100%) rename {cli/src/test => tests}/resources/workspaces/hub_spoke_external/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/hub_spoke_external/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/hub_spoke_external/fake_pip.bzl (100%) rename {cli/src/test => tests}/resources/workspaces/keep_going_repo_failure/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/keep_going_repo_failure/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/keep_going_repo_failure/bad/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/keep_going_repo_failure/ext.bzl (100%) rename {cli/src/test => tests}/resources/workspaces/keep_going_repo_failure/good/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/macro_invalidation/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/macro_invalidation/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/macro_invalidation/MODULE.bazel.lock (100%) rename {cli/src/test => tests}/resources/workspaces/macro_invalidation/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/macro_invalidation/image.png (100%) rename {cli/src/test => tests}/resources/workspaces/macro_invalidation/miniature.bzl (100%) rename {cli/src/test => tests}/resources/workspaces/module_bazel_comment/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/module_bazel_comment/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/module_bazel_comment/MODULE.bazel.lock (100%) rename {cli/src/test => tests}/resources/workspaces/module_bazel_comment/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/module_bazel_comment/bin.sh (100%) rename {cli/src/test => tests}/resources/workspaces/module_bazel_comment/lib.sh (100%) rename {cli/src/test => tests}/resources/workspaces/package_group_dropped/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/package_group_dropped/consumer/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/package_group_dropped/lib/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/package_group_dropped/lib/defs.bzl (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/.bazelignore (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/Consumer.java (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/MODULE.bazel.lock (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/proto_dep/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/proto_dep/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/proto_dep/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/proto_external_version_bump/proto_dep/greeting.proto (100%) rename {cli/src/test => tests}/resources/workspaces/rule_bzl_overtrigger/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/rule_bzl_overtrigger/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/rule_bzl_overtrigger/app/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/rule_bzl_overtrigger/app/data.txt (100%) rename {cli/src/test => tests}/resources/workspaces/rule_bzl_overtrigger/defs/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/rule_bzl_overtrigger/defs/thing.bzl (100%) rename {cli/src/test => tests}/resources/workspaces/serve_bzl_cache/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/serve_bzl_cache/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/serve_bzl_cache/README.md (100%) rename {cli/src/test => tests}/resources/workspaces/serve_bzl_cache/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/serve_bzl_cache/defs.bzl (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/.bazelignore (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/MODULE.bazel.lock (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/inner_repo/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/inner_repo/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/inner_repo/WORKSPACE (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/inner_repo/data.txt (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/middle_repo/BUILD (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/middle_repo/MODULE.bazel (100%) rename {cli/src/test => tests}/resources/workspaces/wrapped_external_repo/middle_repo/WORKSPACE (100%) delete mode 100644 tools/benchmark.py delete mode 100644 tools/benchmark_test.py delete mode 100644 tools/coverage/rules_kotlin_lcov_merger.patch delete mode 100644 tools/e2e/kotlin_e2e_cases.bzl rename {cli => tools}/format/BUILD (56%) delete mode 100644 tools/perf_gate.py delete mode 100644 tools/perf_gate_test.py delete mode 100644 tools/perf_workload.py diff --git a/.bazelignore b/.bazelignore index ce33f0a9..49eae431 100644 --- a/.bazelignore +++ b/.bazelignore @@ -1 +1 @@ -cli/src/test/resources/workspaces +tests/resources/workspaces diff --git a/.bazelrc b/.bazelrc index 39610cdf..27526157 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,10 +1,5 @@ run -c opt --show_loading_progress=false --show_progress=false --ui_event_filters=error run:verbose -c dbg --show_loading_progress=true --show_progress=true --ui_event_filters=info,error,debug -# https://github.com/mockito/mockito/issues/1879 -test --sandbox_tmpfs_path=/tmp - -# Use a hermetic JDK for tests. -common --java_runtime_version=remotejdk_21 # Enable platform-specific config common --enable_platform_specific_config @@ -19,7 +14,7 @@ build:macos --host_cxxopt=-std=c++17 build:windows --cxxopt=/std:c++17 build:windows --host_cxxopt=/std:c++17 -# Release artifacts: `bazel build //release:bazel-diff-rust --config=release`. +# Release artifacts: `bazel build //release:bazel-diff --config=release`. # CI uses nothing but this config, so the flags a published binary is built with # live here rather than in the workflow files. The C++ standard flags come from # the platform-specific configs above. @@ -39,7 +34,7 @@ build:release --@rules_rust//rust/settings:lto=thin # property of the toolchain, which would change every build, not just releases. build:release --@rules_rust//rust/settings:extra_rustc_flags=-Cstrip=symbols -# Linux release artifacts: `bazel build //release:bazel-diff-rust --config=release-musl` +# Linux release artifacts: `bazel build //release:bazel-diff --config=release-musl` # (amd64) or `--config=release-musl-arm64` (arm64). Everything --config=release # does, but built for a musl platform, which makes the published Linux binary # statically linked: it runs on any distribution instead of requiring the glibc @@ -78,10 +73,8 @@ build --output_groups=+rustfmt_checks # coverage mode anyway, so plain `bazel test` runs are never affected. coverage --coverage_output_generator=//tools/coverage:lcov_merger -# Releases run the suite on cold runners (see release.yaml, which splits it -# into a Java job and a Rust job), where the long suites -- //tests:e2e_test -# (size = "enormous") and //cli:E2ETest (timeout = "eternal") -- have no -# headroom left: v44.0.0 died on +# Releases run the suite on cold runners (see release.yaml), where the long +# e2e suite under //tests has no headroom left: v44.0.0 died on # "//tests:e2e_test TIMEOUT in 3600.0s" and published nothing. Per-size timeouts # are there to catch hangs on PRs, where each suite gets its own runner and an # overrun means something is wrong. A release has no such signal to give, so the diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml index 493fbda5..95d3c30b 100644 --- a/.bcr/presubmit.yml +++ b/.bcr/presubmit.yml @@ -74,7 +74,6 @@ bcr_test_module: - '--cxxopt=-std=c++17' - '--host_cxxopt=-std=c++17' build_targets: - - '//:bazel-diff-rust' + - '//:bazel-diff' test_targets: - - '//cli/...' - '//:rust_tests' \ No newline at end of file diff --git a/.claude/skills/coverage-status/SKILL.md b/.claude/skills/coverage-status/SKILL.md index 9c28fd6a..bc63f9f3 100644 --- a/.claude/skills/coverage-status/SKILL.md +++ b/.claude/skills/coverage-status/SKILL.md @@ -5,7 +5,7 @@ description: Use to check the current main-source line coverage of the bazel-dif # Checking coverage status -bazel-diff has a 90% main-source line-coverage gate enforced in CI ([.github/workflows/ci.yaml](../../../.github/workflows/ci.yaml)). The enforcement code lives in [tools/coverage_check.py](../../../tools/coverage_check.py); the gate runs on every `test-jre21` matrix entry as `bazelisk run //tools:coverage-check -- bazel-out/_coverage/_coverage_report.dat`. +bazel-diff has a 90% main-source line-coverage gate enforced in CI ([.github/workflows/ci.yaml](../../../.github/workflows/ci.yaml)). The repo-wide enforcement code lives in [tools/coverage_check.py](../../../tools/coverage_check.py); the gate runs on every `coverage` matrix entry as `bazelisk run //tools:coverage-check -- bazel-out/_coverage/_coverage_report.dat`. On top of that, individual test targets declare per-target minimums that the Rust LCOV merger under [tools/coverage/](../../../tools/coverage/) enforces during the coverage run itself. ## Fastest path: just check the current state @@ -13,7 +13,7 @@ bazel-diff has a 90% main-source line-coverage gate enforced in CI ([.github/wor make coverage ``` -This runs `bazel coverage --combined_report=lcov //cli/... //tools:coverage_check_test`, then `bazel run //tools:coverage-check` against the combined report. It prints a per-file table sorted worst-first, the overall percentage, and PASS/FAIL against the threshold. Exits non-zero when below. +This runs `bazel coverage --combined_report=lcov //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/...`, then `bazel run //tools:coverage-check` against the combined report (twice: once for `src/`, once scoped to `tools/go/`). It prints a per-file table sorted worst-first, the overall percentage, and PASS/FAIL against the threshold. Exits non-zero when below. If a coverage report already exists in `bazel-out/_coverage/_coverage_report.dat` (e.g. from a prior `bazel coverage` invocation) and you only want to re-check the threshold without re-running the test suite: @@ -36,27 +36,28 @@ The per-file table looks like: ``` COV% LINES (hit/total) FILE -------- ----------------- ---- - 0.00% 0 / 4 cli/src/main/kotlin/com/bazel_diff/Main.kt - 62.67% 47 / 75 cli/src/main/kotlin/com/bazel_diff/bazel/BazelModService.kt + 85.10% 401 / 471 src/main.rs + 93.42% 1263 / 1352 src/server.rs ... - 100.00% 23 / 23 cli/src/main/kotlin/com/bazel_diff/hash/TargetHash.kt + 100.00% 23 / 23 src/fingerprint.rs -Overall main-source line coverage: 90.37% (1455 / 1610) +Overall main-source line coverage: 94.37% (4816 / 5103) Threshold: 90.00% PASS ``` - **Sort order is ascending** — worst-covered files appear first, so the gap is at the top. -- **Test sources (`/test/`) are excluded** — Bazel's Kotlin instrumentation reports them too, but they don't count toward the threshold (otherwise well-covered tests would hide thin production-code coverage). -- **Files with `LF:0` are dropped** — that's an "no instrumentation data" signal (e.g. Bazel's Python coverage when no toolchain coverage tool is configured), and treating it as 0% would tank the threshold. +- **Only `src/` counts** (plus `tools/coverage_check.py`) — the e2e crate under `tests/` and the tooling under `tools/` are excluded, so well-covered test code cannot hide thin production coverage. The Rust unit tests live in `#[cfg(test)]` modules inside the `src/` files, so their lines are counted along with the code they cover; that is the same scope the per-target minimum on `//src:rust_tests` uses. +- **Files with `LF:0` are dropped** — that's a "no instrumentation data" signal (e.g. Bazel's Python coverage when no toolchain coverage tool is configured), and treating it as 0% would tank the threshold. - **Threshold is inclusive at the boundary** — exactly 90.00% passes; 89.99% fails. ## Looking at CI's number, not local -Local coverage may be lower than CI's because the slow `//cli:E2ETest` is often skipped or fails on dev machines (JDK-env sandbox issues). The number CI sees comes from the `test-jre21` matrix; download the `coverage-report-jre21-*` artifact from a recent run for an exact snapshot, or just read the "Overall main-source line coverage:" line in the workflow log. +The number CI sees comes from the `coverage` matrix; download the `coverage-report-*` artifact from a recent run for an exact snapshot, or just read the "Overall main-source line coverage:" line in the workflow log. ## Configuration - **Threshold**: `COVERAGE_THRESHOLD` env var (default 90). Set in [.github/workflows/ci.yaml](../../../.github/workflows/ci.yaml) for CI, or on the command line to experiment: `COVERAGE_THRESHOLD=85 make coverage`. -- **Include prefixes**: `--include` flag or `$COVERAGE_INCLUDE` env var (default `cli/src/main/,tools/coverage_check.py`). Only files starting with one of these prefixes count. -- **Self-coverage of the checker**: `tools/coverage_check.py` is itself in the default include list, but Bazel's Python coverage emits `LF:0` for it today because the Python toolchain in `MODULE.bazel` has no `coverage_tool` configured. The 24-case py_test (`bazel test //tools:coverage_check_test`) is the actual proof the checker is exercised. +- **Include prefixes**: `--include` flag or `$COVERAGE_INCLUDE` env var (default `src/,tools/coverage_check.py`). Only files starting with one of these prefixes count. CI runs a second pass with `--include tools/go/` so Go is gated independently. +- **Self-coverage of the checker**: `tools/coverage_check.py` is itself in the default include list, but Bazel's Python coverage emits `LF:0` for it today because the Python toolchain in `MODULE.bazel` has no `coverage_tool` configured. The py_test (`bazel test //tools:coverage_check_test`) is the actual proof the checker is exercised. +- **Per-target minimums**: declared through `coverage_enforced_test` / `coverage_minimum_env` in [tools/coverage/defs.bzl](../../../tools/coverage/defs.bzl); a failing target exits with code 33 and its test log carries the per-file breakdown. diff --git a/.claude/skills/improve-coverage/SKILL.md b/.claude/skills/improve-coverage/SKILL.md index fe51a888..f24adea5 100644 --- a/.claude/skills/improve-coverage/SKILL.md +++ b/.claude/skills/improve-coverage/SKILL.md @@ -1,62 +1,46 @@ --- name: improve-coverage -description: Use when you need to raise main-source line coverage in the bazel-diff repo, write tests for an under-covered Kotlin file, fix a CI failure on the 90% coverage gate, or pick the highest-leverage files to test next. Triggers on requests like "the coverage gate is failing, fix it", "write tests for X", "we need more coverage", or "what should I test to get to 90%". +description: Use when you need to raise main-source line coverage in the bazel-diff repo, write tests for an under-covered Rust module, fix a CI failure on the 90% coverage gate, or pick the highest-leverage files to test next. Triggers on requests like "the coverage gate is failing, fix it", "write tests for X", "we need more coverage", or "what should I test to get to 90%". --- # Improving coverage to clear the 90% gate -bazel-diff enforces a 90% main-source line-coverage gate on every PR (see [coverage-status](../coverage-status/SKILL.md) for the inspection side). When the gate fails or you want to raise the bar, the workflow is: pick the worst-covered files, write small focused unit tests, re-run the gate locally before pushing. +bazel-diff enforces a 90% main-source line-coverage gate on every PR (see [coverage-status](../coverage-status/SKILL.md) for the inspection side), plus per-target minimums on `//src:rust_tests` and `//src:cli_tests` that fire during `bazel coverage`. When the gate fails or you want to raise the bar, the workflow is: pick the worst-covered files, write small focused unit tests, re-run the gate locally before pushing. ## 1. Pick the right files to target Run `make coverage` (or check the latest CI artifact) and look at the top of the sorted table. Prioritise files by **uncovered-lines-per-test-effort**, not by lowest percentage: -- **Highest-leverage**: small files at 0% (e.g. enum classes, value objects, single-method utilities) — one short unit test usually moves the needle without much code. -- **Highest absolute gain**: large files with moderate coverage (e.g. `BazelQueryService.kt` at 303 lines / 93%) — closing a small percentage gap covers many lines. +- **Highest-leverage**: small helpers at 0% (a parser, a converter, a value type's `Display`) — one short unit test usually moves the needle without much code. +- **Highest absolute gain**: large files with moderate coverage (`src/server.rs`, `src/bazel.rs`, `src/main.rs`) — closing a small percentage gap covers many lines. - **Lowest leverage**: tiny files at 50–80% where the remaining branches are error paths needing fault injection or refactors. -A worked example: the PR that added the gate ([#356](https://github.com/Tinder/bazel-diff/pull/356)) raised coverage from 88.76% → 90.37% by adding five small files of tests — `BazelTargetTypeTest`, `VersionProviderTest`, `BazelDiffTest`, `StderrLoggerTest`, `BazelTargetTest` — for a total of 26 newly-covered lines. - ## 2. Write the test Existing tests follow a consistent shape: -- Live under `cli/src/test/kotlin/...` mirroring the main source path. -- Use **JUnit 4** (`@Test`, `@Before`, `@After`, `org.junit.Assert.assertThrows`). -- Use **assertk** for assertions (`assertk.assertThat`, with explicit imports for each assertion like `assertk.assertions.isEqualTo`). Forgetting `import assertk.assertions.contains` on a `String.contains` assertion produces a confusing receiver-mismatch error — import every assertion you use. -- Use **mockito-kotlin** when mocking is required (existing examples: `cli/src/test/kotlin/com/bazel_diff/bazel/BazelClientTest.kt`). -- Use **koin** for DI-test setup (existing pattern: `cli/src/test/kotlin/com/bazel_diff/interactor/CalculateImpactedTargetsInteractorIssue335Test.kt`). - -Tiny files (enums, value objects, small command classes) usually only need a few targeted tests. Looking at the bytes via `assertThat(BazelTargetType.entries).hasSize(N).containsExactlyInAnyOrder(...)` is enough to cover an enum's declaration lines. - -## 3. Register the test target in cli/BUILD - -Every test needs its own `kt_jvm_test` entry: +- Unit tests live in a `#[cfg(test)] mod tests` at the bottom of the `src/` file they cover, and are run by `//src:rust_tests` (library) and `//src:cli_tests` (`main.rs`). +- They test pure transformations with injected data: query planning, repository lowering, hash computation and module-impact decisions take their inputs as values, not as a live Bazel. Nothing under `src/` spawns a fake `bazel` executable — subprocess integration is the e2e suite's job. +- Filesystem cases use `tempfile::TempDir`; server cases in `src/server.rs` bind a loopback port and drive the real HTTP handler. +- Anything that needs a real Bazel workspace is an e2e case under `tests/e2e/` (see [tools/e2e/README.md](../../../tools/e2e/README.md) for the per-case target split and `make regen-e2e`). E2E cases are slow and carry no per-target coverage minimum, so prefer a unit test whenever the logic can be reached without Bazel. -```python -kt_jvm_test( - name = "BazelTargetTypeTest", - test_class = "com.bazel_diff.bazel.BazelTargetTypeTest", - runtime_deps = [":cli-test-lib"], -) -``` - -The `:cli-test-lib` glob picks up the new test source automatically; the explicit `kt_jvm_test` rule is what makes it executable via `bazel test //cli:`. - -## 4. Verify locally before pushing +## 3. Verify locally before pushing ```bash -bazel test //cli: # one-off run of the new test -make coverage # full gate +cargo test # fastest inner loop (unit + e2e crate) +bazel test //:rust_tests # what CI runs, with the pinned toolchain +make coverage # the full gate ``` -The local number may be lower than CI's because `//cli:E2ETest` often fails or is excluded on dev machines (JDK-env sandbox issues). If you've added tests for a file that's also exercised by E2E (e.g. `BazelQueryService.kt`), the CI delta will be smaller than the local delta — count only the lines that weren't already covered by E2E. +`bazel coverage` is what makes the per-target minimums fire: plain `bazel test` never invokes the LCOV merger. If a target falls below its minimum, its test log ends with the merger's per-file breakdown and the action exits 33. + +Lint gates run on every build through the clippy and rustfmt aspects in `.bazelrc`; `make format` (`bazel run //tools/format:rustfmt`) fixes formatting with the exact rustfmt CI uses. -## 5. Things that don't work / aren't worth attempting +## 4. Things that don't work / aren't worth attempting -- **`Main.kt`** — calls `exitProcess(...)` which kills the JVM; can't be tested in-process without a SecurityManager hack or refactor. Stays at 0%; the threshold tolerates it. -- **`throw IllegalArgumentException(...)` branches in resource-loading code** like `VersionProvider.kt` — the production code resolves the classloader from `this::class.java`, with no injection seam to swap it for one missing the resource. Refactor or skip. -- **`else -> BazelTargetType.UNKNOWN` branches** — only reachable when Bazel's `Build.Target.Discriminator` adds a new enum value the production code doesn't recognise. Triggering today would require a hand-forged proto with a reserved discriminator number, which the protobuf builder rejects. +- **`main()` in `src/main.rs`** — exits the process; the testable surface is the command functions it dispatches to, which the `cli_tests` target already covers. Test those, not `main`. +- **`unreachable!` / exhaustive-match fallbacks** on generated protobuf enums — only reachable when Bazel's `Target.Discriminator` grows a new value. Not worth a hand-forged proto. +- **Network error paths in the S3 cache tier** — covered by the loopback mock in `src/server.rs`'s tests; do not add real-bucket tests. ## When the gate fails on a flake, not on a coverage drop diff --git a/.gitattributes b/.gitattributes index b3d263ff..4ec31f43 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ # Test fixture workspaces are deliberately constructed inputs to the E2E tests # (e.g. module_bazel_comment exercises comment preservation, cquery_failing_target # encodes a specific failure scenario). Exclude them from aspect_rules_lint -# formatters (buildifier/ktfmt) so the cron jobs never rewrite the very content -# under test. `rules-lint-ignored` is honored by //cli/format and //cli/format:buildifier. -cli/src/test/resources/** rules-lint-ignored +# formatters (buildifier/rustfmt) so the cron jobs never rewrite the very content +# under test. `rules-lint-ignored` is honored by //tools/format:buildifier and +# //tools/format:rustfmt. +tests/resources/** rules-lint-ignored diff --git a/.github/workflows/assert_static_binary.sh b/.github/workflows/assert_static_binary.sh index fb66745c..382a61ee 100755 --- a/.github/workflows/assert_static_binary.sh +++ b/.github/workflows/assert_static_binary.sh @@ -2,7 +2,7 @@ # Asserts that a published Linux binary is statically linked, i.e. that it # carries no dependency on the glibc of the runner that built it. Shared by the -# `release-artifacts` job in ci.yaml (per-PR check) and the `rust-binaries` job +# `release-artifacts` job in ci.yaml (per-PR check) and the `binaries` job # in release.yaml (before the asset is uploaded), so a regression cannot reach a # release without failing a PR first. # diff --git a/.github/workflows/buildifier.yaml b/.github/workflows/buildifier.yaml index a36e1e03..6d148e3e 100644 --- a/.github/workflows/buildifier.yaml +++ b/.github/workflows/buildifier.yaml @@ -1,7 +1,7 @@ name: Buildifier # Runs buildifier over all Starlark/BUILD files on a daily schedule and opens a -# PR only if the formatter produced changes. Mirrors the ktfmt format cron: it +# PR only if the formatter produced changes. Mirrors the coverage-badge cron: it # force-pushes a dedicated, bot-owned branch so re-runs reuse one PR instead of # accumulating stale branches, and it never touches master directly. # @@ -10,7 +10,7 @@ name: Buildifier # generated PR limited to formatting-only changes. on: schedule: - # 12:30 UTC daily (between the ktfmt format and coverage-badge crons). + # 12:30 UTC daily (half an hour before the coverage-badge cron). - cron: '30 12 * * *' workflow_dispatch: @@ -21,11 +21,6 @@ jobs: contents: write pull-requests: write steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - name: Setup Go environment uses: actions/setup-go@v5 with: @@ -35,7 +30,7 @@ jobs: run: go install github.com/bazelbuild/bazelisk@latest && export PATH=$PATH:$(go env GOPATH)/bin - uses: actions/checkout@v4 - name: Run buildifier - run: ~/go/bin/bazelisk run //cli/format:buildifier --enable_bzlmod=true --enable_workspace=false --lockfile_mode=off + run: ~/go/bin/bazelisk run //tools/format:buildifier --enable_bzlmod=true --enable_workspace=false --lockfile_mode=off - name: Open PR if formatting changed env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -60,5 +55,5 @@ jobs: --base master \ --head "$BRANCH" \ --title "ci: apply buildifier formatting" \ - --body "Automated buildifier run via \`bazel run //cli/format:buildifier\`. This PR contains formatting-only changes to BUILD/Starlark files." + --body "Automated buildifier run via \`bazel run //tools/format:buildifier\`. This PR contains formatting-only changes to BUILD/Starlark files." fi diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 955953c3..049387b1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,8 +12,8 @@ jobs: # and a local `bazel test` agree on the exact rustc. //:rust_clippy_check and # //:rust_format_check below stand in for `cargo clippy -- -D warnings` and # `cargo fmt --all -- --check`. - rust-candidate: - name: Rust candidate (Bazel ${{ matrix.bazel }}) + rust: + name: Rust (Bazel ${{ matrix.bazel }}) runs-on: ubuntu-latest strategy: fail-fast: false @@ -31,12 +31,14 @@ jobs: env: USE_BAZEL_VERSION: ${{ matrix.bazel }} CARGO_BAZEL_ISOLATED: 'false' - run: ~/go/bin/bazelisk test //:rust_tests //:rust_clippy_check //:rust_format_check //tools:benchmark_test //tools:perf_gate_test --enable_bzlmod=true --enable_workspace=false + run: ~/go/bin/bazelisk test //:rust_tests //:rust_clippy_check //:rust_format_check //tools:all --enable_bzlmod=true --enable_workspace=false - rust-candidate-e2e: - name: Rust candidate E2E + rust-e2e: + name: Rust E2E runs-on: ubuntu-latest steps: + # The JDK is for the nested Bazel the fixtures are built with (several + # fixture workspaces contain Java and proto targets), not for bazel-diff. - name: Setup Java JDK uses: actions/setup-java@v4 with: @@ -57,18 +59,18 @@ jobs: echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" echo "BAZEL=$(go env GOPATH)/bin/bazelisk" >> "$GITHUB_ENV" - uses: actions/checkout@v4 - - name: Run native behavioral suite + - name: Run behavioral suite env: # Read twice: by the outer bazelisk, and by the nested one. USE_BAZEL_VERSION: '8.x' run: bazelisk test //tests:e2e_test --enable_bzlmod=true --enable_workspace=false - # The e2e suites are split into one Bazel test target per case by + # The e2e suite is split into one Bazel test target per case by # //tools/e2e:regen, whose output is checked in. This job is the gate that - # keeps the checked-in split honest: :regen_check re-derives the case lists - # from the Kotlin and Rust sources and fails when they differ, so an e2e test - # added, renamed or removed without `make regen-e2e` cannot merge. - # :split_e2e_tests_test covers the generator itself. + # keeps the checked-in split honest: :regen_check re-derives the case list + # from the sources and fails when they differ, so an e2e test added, renamed + # or removed without `make regen-e2e` cannot merge. :split_e2e_tests_test + # covers the generator itself. e2e-split-regen: name: E2E split regen runs-on: ubuntu-latest @@ -85,8 +87,8 @@ jobs: USE_BAZEL_VERSION: '8.x' run: ~/go/bin/bazelisk test //tools/e2e:regen_check //tools/e2e:split_e2e_tests_test --enable_bzlmod=true --enable_workspace=false --test_output=errors - rust-candidate-msrv: - name: Rust candidate MSRV + rust-msrv: + name: Rust MSRV runs-on: ubuntu-latest steps: - name: Setup Go environment @@ -117,7 +119,12 @@ jobs: --output_groups=-clippy_checks,-rustfmt_checks \ --enable_bzlmod=true --enable_workspace=false - test-jre21: + # The coverage gate. `bazel coverage` (not `bazel test`) is what makes the + # per-target minimums in //tools/coverage fire, and the combined report feeds + # the repo-wide check plus the badge. Rust, Go and the Python checker are + # gated independently so thin coverage in one cannot hide behind another. + coverage: + name: coverage (${{ matrix.os }}, Bazel ${{ matrix.bazel }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -132,11 +139,6 @@ jobs: - os: macos-latest bazel: '9.x' steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - name: Setup Go environment uses: actions/setup-go@v5 with: @@ -145,77 +147,46 @@ jobs: - name: Setup Bazelisk run: go install github.com/bazelbuild/bazelisk@latest && export PATH=$PATH:$(go env GOPATH)/bin - uses: actions/checkout@v4 - - name: Free disk space on Ubuntu - if: runner.os == 'Linux' - run: | - # //cli:E2ETest plus coverage can exhaust the default ubuntu-latest image. - # Reclaim space from preinstalled toolchains this job does not use. - # Keep /usr/local/lib/android: rules_android resolves ANDROID_HOME there. - sudo rm -rf /usr/share/dotnet /opt/ghc - sudo docker image prune --all --force || true - # /mnt has more headroom than / on hosted runners; keep Bazel outputs off root. - if [ -d /mnt ]; then - sudo mkdir -p /mnt/bazel-output - sudo chown "$USER" /mnt/bazel-output - echo "BAZEL_OUTPUT_USER_ROOT=/mnt/bazel-output" >> "$GITHUB_ENV" - fi - df -h / /mnt 2>/dev/null || df -h / - name: Build license target env: USE_BAZEL_VERSION: ${{ matrix.bazel }} run: ~/go/bin/bazelisk build //:license --enable_bzlmod=true --enable_workspace=false - - name: Run bazel-diff tests + - name: Run tests with coverage env: USE_BAZEL_VERSION: ${{ matrix.bazel }} - run: | - BAZEL_STARTUP=() - if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then - BAZEL_STARTUP=(--output_user_root="${BAZEL_OUTPUT_USER_ROOT}") - fi - ~/go/bin/bazelisk "${BAZEL_STARTUP[@]}" coverage --combined_report=lcov //cli/... //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/... --enable_bzlmod=true --enable_workspace=false + run: ~/go/bin/bazelisk coverage --combined_report=lcov //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/... --enable_bzlmod=true --enable_workspace=false - name: Upload coverage report uses: actions/upload-artifact@v4 if: always() with: - name: coverage-report-jre21-${{ matrix.os }}-bazel-${{ matrix.bazel }} + name: coverage-report-${{ matrix.os }}-bazel-${{ matrix.bazel }} path: bazel-out/_coverage/_coverage_report.dat if-no-files-found: warn - name: Enforce coverage threshold (>= 90% main-source line coverage) env: - # Must match the `Run bazel-diff tests` step above, otherwise bazelisk - # downloads the default bazel from .bazelversion and starts a fresh - # server whose output base loses track of the coverage report symlink - # (seen flaking on macos-latest x bazel 9.x). + # Must match the coverage step above, otherwise bazelisk downloads the + # default bazel from .bazelversion and starts a fresh server whose + # output base loses track of the coverage report symlink (seen flaking + # on macos-latest x bazel 9.x). USE_BAZEL_VERSION: ${{ matrix.bazel }} COVERAGE_THRESHOLD: '90' - run: | - BAZEL_STARTUP=() - if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then - BAZEL_STARTUP=(--output_user_root="${BAZEL_OUTPUT_USER_ROOT}") - fi - ~/go/bin/bazelisk "${BAZEL_STARTUP[@]}" run //tools:coverage-check -- --badge-json coverage.json bazel-out/_coverage/_coverage_report.dat + run: ~/go/bin/bazelisk run //tools:coverage-check -- --badge-json coverage.json bazel-out/_coverage/_coverage_report.dat - name: Enforce Go coverage threshold (>= 90% line coverage under tools/go/) env: - # Must match the `Run bazel-diff tests` step above; see the note on the - # Kotlin enforcement step for why USE_BAZEL_VERSION has to be pinned here. + # Must match the coverage step above; see the note on the previous step. USE_BAZEL_VERSION: ${{ matrix.bazel }} - # Gate Go independently of the Kotlin overall so thin Go coverage can't hide - # behind well-covered Kotlin (and vice versa). --include scopes the report to + # Gate Go independently of the Rust overall so thin Go coverage can't hide + # behind well-covered Rust (and vice versa). --include scopes the report to # Go production source only (tools/go/, which excludes tools/coverage_check.py). - run: | - BAZEL_STARTUP=() - if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then - BAZEL_STARTUP=(--output_user_root="${BAZEL_OUTPUT_USER_ROOT}") - fi - ~/go/bin/bazelisk "${BAZEL_STARTUP[@]}" run //tools:coverage-check -- --include tools/go/ --threshold 90 bazel-out/_coverage/_coverage_report.dat + run: ~/go/bin/bazelisk run //tools:coverage-check -- --include tools/go/ --threshold 90 bazel-out/_coverage/_coverage_report.dat - name: Upload test logs uses: actions/upload-artifact@v4 if: always() with: - name: test-logs-jre21-${{ matrix.os }}-bazel-${{ matrix.bazel }} + name: test-logs-${{ matrix.os }}-bazel-${{ matrix.bazel }} path: bazel-testlogs/ if-no-files-found: warn - test-jre11-run-example: + run-example: runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -260,12 +231,6 @@ jobs: disable_workspace: 'true' extra_flags: '' steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '11' - id: java - name: Setup Go environment uses: actions/setup-go@v5 with: @@ -281,6 +246,16 @@ jobs: go install github.com/bazelbuild/bazelisk@latest $env:PATH = "$env:PATH;$(go env GOPATH)\bin" echo "$env:USERPROFILE\go\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + # The example script builds //:bazel-diff with the default output root, so + # the Windows legs need the same MAX_PATH workaround the release jobs pass + # as startup flags (see `release-artifacts` below). Startup options cannot + # be set from an environment variable, so they go in the user rc file + # instead, which every Bazel the script launches reads. + - name: Shorten the Bazel output root (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + "startup --output_user_root=C:/b`nbuild --legacy_external_runfiles" | Out-File -FilePath "$env:USERPROFILE\.bazelrc" -Encoding utf8 - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -306,16 +281,16 @@ jobs: .\bazel-diff-example.ps1 -WorkspacePath "$env:GITHUB_WORKSPACE" -BazelPath "$env:USERPROFILE\go\bin\bazelisk.exe" -PreviousRevision $prevRev -FinalRevision $currRev # Verifies per-PR that everything a release ships still builds, on every # platform it ships for. It publishes nothing: release.yaml owns that, via - # release_prep.sh (JAR + source archive) and its rust-binaries matrix. This - # job predates that flow -- it used to *be* the deploy -- so its uploads are - # a build check whose output happens to be downloadable, nothing more. + # release_prep.sh (source archive) and its binaries matrix. This job predates + # that flow -- it used to *be* the deploy -- so its uploads are a build check + # whose output happens to be downloadable, nothing more. release-artifacts: # Explicit name so the status check is stable. Left implicit, GitHub derives # it from every matrix value -- which is how branch protection ended up # requiring `deploy (11)`, a check that stopped existing the moment this job - # grew an os/rust_asset matrix. Adding a matrix key must not rename a check. + # grew an os/asset matrix. Adding a matrix key must not rename a check. name: release-artifacts (${{ matrix.os }}) - needs: [test-jre21] + needs: [coverage] runs-on: ${{ matrix.os }} permissions: # Read-only: nothing here attests or publishes. The id-token/attestations @@ -324,8 +299,8 @@ jobs: strategy: fail-fast: false matrix: - # rust_asset is the name //release:bazel-diff-rust gives the binary on - # that platform -- it is asserted, not applied (see the upload step). + # asset is the name //release:bazel-diff gives the binary on that + # platform -- it is asserted, not applied (see the upload step). # # The two Windows-only Bazel flags can't move into --config=release: # startup flags can't live in a --config at all, and .bazelrc expands @@ -346,33 +321,24 @@ jobs: # config. include: - os: ubuntu-latest - java: '11' - rust_asset: bazel-diff-rust-linux-amd64 + asset: bazel-diff-rust-linux-amd64 release_config: release-musl bazel_startup_flags: '' bazel_extra_flags: '' - upload_jar_and_archive: true + upload_archive: true - os: macos-latest - java: '11' - rust_asset: bazel-diff-rust-macos-arm64 + asset: bazel-diff-rust-macos-arm64 release_config: release bazel_startup_flags: '' bazel_extra_flags: '' - upload_jar_and_archive: false + upload_archive: false - os: windows-latest - java: '11' - rust_asset: bazel-diff-rust-windows-amd64.exe + asset: bazel-diff-rust-windows-amd64.exe release_config: release bazel_startup_flags: '--output_user_root=C:/b' bazel_extra_flags: '--legacy_external_runfiles' - upload_jar_and_archive: false + upload_archive: false steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: ${{ matrix.java }} - id: java - name: Setup Go environment uses: actions/setup-go@v5 with: @@ -390,35 +356,26 @@ jobs: go install github.com/bazelbuild/bazelisk@latest echo "$(go env GOPATH)\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - uses: actions/checkout@v4 - - name: Build deployable JAR - if: matrix.upload_jar_and_archive - run: bazelisk build //cli:bazel-diff_deploy.jar - - uses: actions/upload-artifact@v4 - if: matrix.upload_jar_and_archive - with: - name: bazel-diff_deploy.jar - path: bazel-bin/cli/bazel-diff_deploy.jar - if-no-files-found: error # The same script release_prep.sh packs the shipped archive with, so this # check can't pass on an archive the release would not produce. It used to # call `make release_source_archive`, a second tar recipe that had already # drifted (no __pycache__/*.pyc excludes). - name: Build release source archive - if: matrix.upload_jar_and_archive + if: matrix.upload_archive run: .github/workflows/pack_release_archive.sh archives/release.tar.gz - uses: actions/upload-artifact@v4 - if: matrix.upload_jar_and_archive + if: matrix.upload_archive with: name: release.tar.gz path: archives/release.tar.gz if-no-files-found: error - # //release:bazel-diff-rust names the binary for the platform Bazel built - # it for, so this step neither renames nor relocates anything: whatever - # lands in bazel-bin/release/ is the published asset. matrix.release_config + # //release:bazel-diff names the binary for the platform Bazel built it + # for, so this step neither renames nor relocates anything: whatever lands + # in bazel-bin/release/ is the published asset. matrix.release_config # carries the flags (see .bazelrc) -- including, on Linux, the musl target # platform. `if-no-files-found: error` below is the assertion that Bazel's - # name still matches matrix.rust_asset. - - name: Build Rust binary + # name still matches matrix.asset. + - name: Build binary shell: bash env: # Windows runs this under Git bash, whose MSYS runtime rewrites any @@ -426,21 +383,21 @@ jobs: # Bazel then rejects "invalid package name '/release'". (`//:target` # survived only because it has no path-like segment to convert.) MSYS2_ARG_CONV_EXCL: '//' - run: bazelisk ${{ matrix.bazel_startup_flags }} build //release:bazel-diff-rust --config=${{ matrix.release_config }} ${{ matrix.bazel_extra_flags }} + run: bazelisk ${{ matrix.bazel_startup_flags }} build //release:bazel-diff --config=${{ matrix.release_config }} ${{ matrix.bazel_extra_flags }} - name: Assert Linux binary is statically linked if: runner.os == 'Linux' - run: .github/workflows/assert_static_binary.sh "bazel-bin/release/${{ matrix.rust_asset }}" + run: .github/workflows/assert_static_binary.sh "bazel-bin/release/${{ matrix.asset }}" - uses: actions/upload-artifact@v4 with: - name: ${{ matrix.rust_asset }} - path: bazel-bin/release/${{ matrix.rust_asset }} + name: ${{ matrix.asset }} + path: bazel-bin/release/${{ matrix.asset }} if-no-files-found: error # Second published Linux asset, built in this same job so the status check # stays `release-artifacts (ubuntu-latest)`. A matrix row with the same # `os` would duplicate that name and break branch protection. - - name: Build Rust binary (linux-arm64 musl) + - name: Build binary (linux-arm64 musl) if: runner.os == 'Linux' - run: bazelisk build //release:bazel-diff-rust --config=release-musl-arm64 + run: bazelisk build //release:bazel-diff --config=release-musl-arm64 - name: Assert Linux arm64 binary is statically linked if: runner.os == 'Linux' run: .github/workflows/assert_static_binary.sh bazel-bin/release/bazel-diff-rust-linux-arm64 diff --git a/.github/workflows/coverage_badge.yaml b/.github/workflows/coverage_badge.yaml index 0954d602..2bc2d956 100644 --- a/.github/workflows/coverage_badge.yaml +++ b/.github/workflows/coverage_badge.yaml @@ -16,11 +16,6 @@ jobs: contents: write pull-requests: write steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - name: Setup Go environment uses: actions/setup-go@v5 with: @@ -32,7 +27,7 @@ jobs: - name: Run bazel-diff tests with coverage env: USE_BAZEL_VERSION: '9.x' - run: ~/go/bin/bazelisk coverage --combined_report=lcov //cli/... //src:cli_tests //src:rust_tests //tools:coverage_check_test --enable_bzlmod=true --enable_workspace=false + run: ~/go/bin/bazelisk coverage --combined_report=lcov //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/... --enable_bzlmod=true --enable_workspace=false - name: Regenerate coverage badge env: USE_BAZEL_VERSION: '9.x' diff --git a/.github/workflows/firecracker-e2e.yml b/.github/workflows/firecracker-e2e.yml index b1afb4be..a985b056 100644 --- a/.github/workflows/firecracker-e2e.yml +++ b/.github/workflows/firecracker-e2e.yml @@ -49,12 +49,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - - name: Setup Go environment uses: actions/setup-go@v5 with: @@ -82,11 +76,13 @@ jobs: sudo chmod 666 /dev/kvm ls -l /dev/kvm - - name: Build bazel-diff (deploy jar + launcher) + # One static musl binary serves both the host (local-driver baseline) and + # the guest image: it has no runtime dependencies on either side. + - name: Build bazel-diff (static musl binary) run: | - ~/go/bin/bazelisk build //cli:bazel-diff_deploy.jar - ~/go/bin/bazelisk run //:bazel-diff --script_path=/tmp/bazel_diff - ls -l bazel-bin/cli/bazel-diff_deploy.jar /tmp/bazel_diff + ~/go/bin/bazelisk build //release:bazel-diff --config=release-musl + install -m755 bazel-bin/release/bazel-diff-rust-linux-amd64 /tmp/bazel_diff + ls -l /tmp/bazel_diff - name: Build + unit-test the orchestrator working-directory: tools/firecracker @@ -131,7 +127,7 @@ jobs: run: | sudo -E env \ ARCH="$ARCH" KERNEL_VER="$KERNEL_VER" OUT="$IMG" SIZE_MB="$SIZE_MB" \ - BAZEL_DIFF_JAR="$PWD/bazel-bin/cli/bazel-diff_deploy.jar" \ + BAZEL_DIFF_BIN=/tmp/bazel_diff \ BAZEL_BIN="${{ steps.bazel.outputs.bin }}" \ WORKSPACE_SRC="$WS" SSH_PUBKEY=/tmp/fc_guest.pub \ tools/firecracker/bench/build_guest_image.sh diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml deleted file mode 100644 index 41acc34d..00000000 --- a/.github/workflows/format.yaml +++ /dev/null @@ -1,63 +0,0 @@ -name: Format - -# Runs ktfmt over the codebase on a daily schedule and opens a PR only if the -# formatter produced changes. Mirrors the coverage-badge cron: it force-pushes a -# dedicated, bot-owned branch so re-runs reuse one PR instead of accumulating -# stale branches, and it never touches master directly. -on: - schedule: - # 12:00 UTC daily (one hour before the coverage-badge cron). - - cron: '0 12 * * *' - workflow_dispatch: - -jobs: - format: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - - name: Setup Go environment - uses: actions/setup-go@v5 - with: - go-version: ^1.17 - id: go - - name: Setup Bazelisk - run: go install github.com/bazelbuild/bazelisk@latest && export PATH=$PATH:$(go env GOPATH)/bin - - uses: actions/checkout@v4 - - name: Run ktfmt - # Pinned to the repo's .bazelversion (no USE_BAZEL_VERSION) and run with - # --lockfile_mode=off so MODULE.bazel.lock is never rewritten, keeping the - # generated PR limited to formatting-only changes. - run: ~/go/bin/bazelisk run //cli/format --enable_bzlmod=true --enable_workspace=false --lockfile_mode=off - - name: Open PR if formatting changed - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [ -z "$(git status --porcelain)" ]; then - echo "No formatting changes; nothing to do." - exit 0 - fi - BRANCH="ci/ktfmt-format" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git checkout -B "$BRANCH" - git add -A - git commit -m "ci: apply ktfmt formatting" - # Force-push the dedicated, bot-owned branch so re-runs reuse one PR - # instead of accumulating stale branches. This never touches master. - git push --force origin "$BRANCH" - if gh pr view "$BRANCH" --json state --jq '.state' 2>/dev/null | grep -q OPEN; then - echo "PR already open for $BRANCH; it now points at the latest formatting." - else - gh pr create \ - --base master \ - --head "$BRANCH" \ - --title "ci: apply ktfmt formatting" \ - --body "Automated ktfmt run via \`bazel run //cli/format\`. This PR contains formatting-only changes." - fi diff --git a/.github/workflows/integration_external_target.yml b/.github/workflows/integration_external_target.yml index 7a749233..3c4b5fb2 100644 --- a/.github/workflows/integration_external_target.yml +++ b/.github/workflows/integration_external_target.yml @@ -13,15 +13,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '11' ] bazel: ['8.x'] steps: - - name: Setup Java JDK - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: ${{ matrix.java }} - id: java - uses: actions/checkout@v3 with: repository: tinder-maxwellelliott/bazel-diff-repro-1 @@ -39,7 +32,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: bazel-diff-artifacts-java-${{ matrix.java }}-bazel-${{ matrix.bazel }} + name: bazel-diff-artifacts-bazel-${{ matrix.bazel }} path: | /tmp/impacted_targets.txt /tmp/bazel-diff*.log diff --git a/.github/workflows/perf-gate.yaml b/.github/workflows/perf-gate.yaml deleted file mode 100644 index fd67f5de..00000000 --- a/.github/workflows/perf-gate.yaml +++ /dev/null @@ -1,85 +0,0 @@ -name: Performance Gate - -# Fails the build when the Rust implementation is not faster than the Kotlin one. -# -# tools/perf_gate.py generates its own workloads (a synthetic `streamed_proto` fixture, the -# workspace it describes, and hash-file pairs), so this job needs no checkout of another -# repository, no Bazel server and no Hyperfine -- only the two bazel-diff binaries. Both are -# built `-c opt`, because comparing a debug build against an optimized one measures the build -# flags rather than the code. -# -# Runs on every pull request: "Rust is faster" is a property of the code, and a regression is -# cheapest to catch in the change that caused it. The runner is a shared 2-vCPU VM, so the gate -# pairs its measurements (both binaries every round, alternating which goes first) and takes -# medians over several rounds rather than trusting a single timing. See -# docs/kotlin-rust-perf-gate.md for the protocol and for what to do when this job fails. -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - workflow_dispatch: - -jobs: - perf-gate: - name: Rust vs Kotlin performance gate - runs-on: ubuntu-latest - timeout-minutes: 45 - steps: - # No host Rust toolchain: the Rust binary is built by Bazel, which brings - # its own rustc pinned in MODULE.bazel. - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - - name: Setup Go environment - uses: actions/setup-go@v5 - with: - go-version: ^1.17 - - name: Setup Bazelisk - run: go install github.com/bazelbuild/bazelisk@latest - - uses: actions/checkout@v4 - - name: Unit-test the gate itself - env: - USE_BAZEL_VERSION: '8.x' - CARGO_BAZEL_ISOLATED: 'false' - run: ~/go/bin/bazelisk test //tools:perf_gate_test - - name: Build both implementations - env: - USE_BAZEL_VERSION: '8.x' - CARGO_BAZEL_ISOLATED: 'false' - run: ~/go/bin/bazelisk build -c opt //cli:bazel-diff //src:bazel-diff - - name: Run the performance gate - env: - USE_BAZEL_VERSION: '8.x' - CARGO_BAZEL_ISOLATED: 'false' - # Invoked directly rather than through `bazel run` so the JSON report lands in the - # workspace instead of the runfiles tree, and so Bazel is not holding the output-base - # lock (or competing for cores) while timings are taken. - run: | - python3 tools/perf_gate.py \ - --kotlin-binary bazel-bin/cli/bazel-diff \ - --rust-binary bazel-bin/src/bazel-diff \ - --rounds 7 \ - --warmup-rounds 2 \ - --json perf-gate.json \ - | tee perf-gate.txt - - name: Publish summary - if: always() - run: | - { - echo '## Rust vs Kotlin performance gate' - echo '```' - cat perf-gate.txt || echo 'the gate produced no report' - echo '```' - } >> "$GITHUB_STEP_SUMMARY" - - name: Upload report - if: always() - uses: actions/upload-artifact@v4 - with: - name: perf-gate-report - path: | - perf-gate.json - perf-gate.txt - if-no-files-found: ignore diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index dacbfe35..c541adea 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,18 +1,14 @@ # Cut a release whenever a new tag is pushed or via workflow_dispatch. # Uses bazel-contrib release ruleset: build, attest, and publish to GitHub Releases. -# A separate matrix job attaches host-native Rust CLI binaries to the draft release. +# A separate matrix job attaches host-native CLI binaries to the draft release. # -# The test suites gate the release from two independent jobs -- `java-tests` and -# `rust-tests` -- rather than from one `bazel test //...` inside the reusable -# workflow's build job. They run concurrently on their own runners, so a Kotlin -# failure no longer cancels the Rust suite's results (or vice versa): a release -# candidate gets both verdicts from one run instead of one verdict at a time. -# It also splits the two long suites -- //cli:E2ETest and //tests:e2e_test -- -# across two cold runners, each with its own 6h GitHub job limit. +# The test suite gates the release from its own `rust-tests` job rather than +# from `bazel test //...` inside the reusable workflow's build job, so the long +# e2e suite under //tests gets a cold runner to itself, with its own 6h GitHub +# job limit. # -# Between them the three test commands cover every test in //...; keep it that +# Between them the two test commands cover every test in //...; keep it that # way when adding a package: -# java-tests //cli/... (Kotlin/Java) # rust-tests //src/..., //tests/..., //tools/coverage/..., the Rust lint # gates //:rust_clippy_check and //:rust_format_check # release //tools:all, //tools/e2e/..., //tools/go/... @@ -33,49 +29,18 @@ permissions: attestations: write contents: write jobs: - # The Kotlin/Java half of the release gate. //cli/... is the whole JVM CLI, - # including //cli:E2ETest (timeout = "eternal"), which is why this suite gets - # a runner to itself. Java is set up explicitly even though .bazelrc pins a - # hermetic --java_runtime_version for tests, so this job matches what the - # `test-jre21` job in ci.yaml already runs these suites under. - java-tests: - name: Java tests - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ inputs.tag_name || github.ref_name }} - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: "21" - - name: Setup Go environment - uses: actions/setup-go@v5 - with: - go-version: ^1.17 - - name: Setup Bazelisk - run: | - go install github.com/bazelbuild/bazelisk@latest - echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - # No USE_BAZEL_VERSION: bazelisk takes the version from .bazelversion, - # which is the one a release is cut with. --config=no-test-timeouts is in - # .bazelrc; it is what keeps the long suites from timing out on a cold - # release runner (see the comment on that config). - - name: Run Kotlin/Java tests - run: bazelisk test //cli/... --config=no-test-timeouts - # The Rust half: the three Rust packages plus the two lint gates, which live - # in the root package because they pin their own roots (see //BUILD). The - # packages are named recursively rather than through the //:rust_tests suite - # ci.yaml uses, so a Rust test added to //src or //tests is in the release - # gate whether or not it also gets added to that hand-maintained suite. + # The three Rust packages plus the two lint gates, which live in the root + # package because they pin their own roots (see //BUILD). The packages are + # named recursively rather than through the //:rust_tests suite ci.yaml uses, + # so a test added to //src or //tests is in the release gate whether or not + # it also gets added to that hand-maintained suite. # # //tests:e2e_test spawns a nested Bazel per fixture workspace, so this job - # needs the Java and $BAZEL setup that `rust-candidate-e2e` in ci.yaml uses -- - # without $BAZEL the suite shells out to the outer Bazel's own binary. - # USE_BAZEL_VERSION keeps those nested Bazelisk processes on the same exact - # release version the outer invocation reads from .bazelversion. + # needs the Java and $BAZEL setup that `rust-e2e` in ci.yaml uses -- the JDK + # is for the fixtures' Java targets, and without $BAZEL the suite shells out + # to the outer Bazel's own binary. USE_BAZEL_VERSION keeps those nested + # Bazelisk processes on the same exact release version the outer invocation + # reads from .bazelversion. rust-tests: name: Rust tests runs-on: ubuntu-latest @@ -99,6 +64,9 @@ jobs: echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" echo "BAZEL=$(go env GOPATH)/bin/bazelisk" >> "$GITHUB_ENV" echo "USE_BAZEL_VERSION=$(<.bazelversion)" >> "$GITHUB_ENV" + # --config=no-test-timeouts is in .bazelrc; it is what keeps the long + # e2e suite from timing out on a cold release runner (see the comment on + # that config). - name: Run Rust tests run: | bazelisk test \ @@ -117,17 +85,15 @@ jobs: path: bazel-testlogs/ if-no-files-found: warn release: - # Both suites gate the release, but neither gates the other: they run - # concurrently and the reusable workflow starts only once both are green. - needs: [java-tests, rust-tests] + needs: [rust-tests] uses: bazel-contrib/.github/.github/workflows/release_ruleset.yaml@v7.4.0 with: release_files: archives/*.* # Replaces the reusable workflow's default (`bazel test //...`), which is - # the only way to change what its build job runs. The Kotlin and Rust - # suites moved to the two jobs above; what is left is the Python and Go - # release tooling, which is fast enough to keep here rather than spend a - # third runner on. --config=no-test-timeouts is in .bazelrc. + # the only way to change what its build job runs. The Rust suites moved + # to the job above; what is left is the Python and Go release tooling, + # which is fast enough to keep here rather than spend another runner on. + # --config=no-test-timeouts is in .bazelrc. bazel_test_command: "bazel test //tools:all //tools/e2e/... //tools/go/... --config=no-test-timeouts" prerelease: false draft: true @@ -144,7 +110,7 @@ jobs: tag_name: ${{ inputs.tag_name || github.ref_name }} secrets: BCR_PUBLISH_TOKEN: ${{ secrets.BCR_PUBLISH_TOKEN }} - rust-binaries: + binaries: needs: release runs-on: ${{ matrix.os }} permissions: @@ -188,11 +154,6 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ inputs.tag_name || github.ref_name }} - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: "21" - name: Setup Go environment uses: actions/setup-go@v5 with: @@ -208,11 +169,11 @@ jobs: run: | go install github.com/bazelbuild/bazelisk@latest echo "$(go env GOPATH)\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - # //release:bazel-diff-rust names the binary for the platform Bazel built - # it for, so nothing here renames or relocates it: bazel-bin/release/ holds + # //release:bazel-diff names the binary for the platform Bazel built it + # for, so nothing here renames or relocates it: bazel-bin/release/ holds # the published asset, and `gh release upload` keeps that file name. The # build flags live in .bazelrc under matrix.release_config. - - name: Build Rust binary + - name: Build binary shell: bash env: # Windows runs this under Git bash, whose MSYS runtime rewrites any @@ -220,7 +181,7 @@ jobs: # Bazel then rejects "invalid package name '/release'". (`//:target` # survived only because it has no path-like segment to convert.) MSYS2_ARG_CONV_EXCL: '//' - run: bazelisk ${{ matrix.bazel_startup_flags }} build //release:bazel-diff-rust --config=${{ matrix.release_config }} ${{ matrix.bazel_extra_flags }} + run: bazelisk ${{ matrix.bazel_startup_flags }} build //release:bazel-diff --config=${{ matrix.release_config }} ${{ matrix.bazel_extra_flags }} # Last gate before the asset is published: a glibc-linked binary here # would strand every user on an older distribution than the runner. - name: Assert Linux binary is statically linked @@ -236,7 +197,7 @@ jobs: --clobber \ --repo "$GITHUB_REPOSITORY" finalize: - needs: [publish, rust-binaries] + needs: [publish, binaries] runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/release_prep.sh b/.github/workflows/release_prep.sh index 23563b90..d6ac227b 100755 --- a/.github/workflows/release_prep.sh +++ b/.github/workflows/release_prep.sh @@ -8,10 +8,6 @@ TAG=$1 "$(dirname "$0")/pack_release_archive.sh" archives/release.tar.gz -make release_deploy_jar &> /dev/null - -cp bazel-bin/cli/bazel-diff_deploy.jar archives/bazel-diff_deploy.jar - SHA=$(shasum -a 256 archives/release.tar.gz | awk '{print $1}') cat << EOF @@ -23,21 +19,14 @@ Add to your \`MODULE.bazel\` file: bazel_dep(name = "bazel-diff", version = "${TAG#v}") \`\`\` -## Using WORKSPACE +Then run \`bazel run @bazel-diff//:bazel-diff -- --help\`. -\`\`\`starlark -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -http_archive( - name = "bazel-diff", - sha256 = "${SHA}", - strip_prefix = "", - url = "https://github.com/Tinder/bazel-diff/releases/download/${TAG}/release.tar.gz", -) -\`\`\` +Source archive (sha256 \`${SHA}\`): +https://github.com/Tinder/bazel-diff/releases/download/${TAG}/release.tar.gz -## Experimental Rust binary +## Prebuilt binaries -Prebuilt host-native CLIs (attached by the release workflow): +Statically linked, self-contained CLIs (attached by the release workflow): - Linux amd64: https://github.com/Tinder/bazel-diff/releases/download/${TAG}/bazel-diff-rust-linux-amd64 - Linux arm64: https://github.com/Tinder/bazel-diff/releases/download/${TAG}/bazel-diff-rust-linux-arm64 diff --git a/.github/workflows/serve-consistency.yml b/.github/workflows/serve-consistency.yml index 786a3b22..93eccfe6 100644 --- a/.github/workflows/serve-consistency.yml +++ b/.github/workflows/serve-consistency.yml @@ -27,11 +27,6 @@ jobs: matrix: bazel: [ '8.x', '9.x' ] steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - name: Setup Go environment uses: actions/setup-go@v5 with: diff --git a/.github/workflows/serve-harness.yml b/.github/workflows/serve-harness.yml index fa8afd57..1bc48240 100644 --- a/.github/workflows/serve-harness.yml +++ b/.github/workflows/serve-harness.yml @@ -1,8 +1,8 @@ name: Serve Harness # Integration validation for the `bazel-diff serve` query service. Runs tools/serve_harness.py, -# which builds //cli:bazel-diff and drives the real binary over HTTP against a live git:// remote -# (git daemon) across full/shallow/partial clones -- coverage the in-process E2ETest cannot reach. +# which builds //src:bazel-diff and drives the real binary over HTTP against a live git:// remote +# (git daemon) across full/shallow/partial clones -- coverage the in-process e2e cases cannot reach. # See the harness module docstring for the scenario matrix. # Cron-only for now while we confirm the harness is stable on CI runners. Once it has a healthy @@ -24,11 +24,6 @@ jobs: matrix: bazel: [ '8.x', '9.x' ] steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - name: Setup Go environment uses: actions/setup-go@v5 with: diff --git a/.github/workflows/serve-stress-alpine-real.yml b/.github/workflows/serve-stress-alpine-real.yml index 0984e2f8..9793b95f 100644 --- a/.github/workflows/serve-stress-alpine-real.yml +++ b/.github/workflows/serve-stress-alpine-real.yml @@ -57,11 +57,6 @@ jobs: steps: - name: Compute stage dir run: echo "STAGE=$RUNNER_TEMP/alpine-stage-${{ matrix.size }}" >> "$GITHUB_ENV" - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - name: Setup Go environment uses: actions/setup-go@v5 with: @@ -70,12 +65,16 @@ jobs: - name: Setup Bazelisk run: go install github.com/bazelbuild/bazelisk@latest - uses: actions/checkout@v4 - - name: Build bazel-diff deploy jar (glibc host) - run: ~/go/bin/bazelisk build //cli:bazel-diff_deploy.jar + # The published Linux asset: statically linked against musl, so it runs + # on Alpine as-is. Built here rather than in the container because the + # build needs the full Bazel + Rust toolchain, which only has to work on + # the glibc host. + - name: Build bazel-diff (static musl binary) + run: ~/go/bin/bazelisk build //release:bazel-diff --config=release-musl - name: Stage container inputs run: | mkdir -p "$STAGE/tools" - cp bazel-bin/cli/bazel-diff_deploy.jar "$STAGE/bazel-diff.jar" + cp bazel-bin/release/bazel-diff-rust-linux-amd64 "$STAGE/bazel-diff" cp tools/serve_stress.py tools/serve_harness.py tools/serve_stress_alpine.sh "$STAGE/tools/" cp .bazelversion "$STAGE/" FALLBACK_BV=$(tr -d '[:space:]' < .bazelversion) diff --git a/.github/workflows/serve-stress-alpine.yml b/.github/workflows/serve-stress-alpine.yml index f891921c..20491a31 100644 --- a/.github/workflows/serve-stress-alpine.yml +++ b/.github/workflows/serve-stress-alpine.yml @@ -4,21 +4,22 @@ name: Serve Stress (Alpine 8 GiB) # harness as serve-stress.yml (tools/serve_stress.py -- concurrent cold/hot load, error mix, # index.lock heal/storm, remote outage, lame-duck), but run inside an x86_64 Alpine (musl) # container hard-capped at 8 GiB RAM and a small CPU quota. The goal is to see where serve -# chokes when memory and cores are scarce -- JVM heap pressure (container-aware default heap is -# ~2 GiB at this cap), `bazel query` server cost, git checkout latency under CPU starvation, -# and OOM-kill behavior -- signals the 4-core/16 GiB glibc runners of the other stress crons -# never produce. Alongside the harness's metrics/summary, the run emits a footprint JSON +# chokes when memory and cores are scarce -- serve's own memory footprint, `bazel query` +# server cost (the Bazel server JVM is the big consumer at this cap), git checkout latency +# under CPU starvation, and OOM-kill behavior -- signals the 4-core/16 GiB glibc runners of +# the other stress crons never produce. Alongside the harness's metrics/summary, the run emits a footprint JSON # (cgroup memory.peak against the cap + oom/oom_kill counts) as a 90-day artifact so the # low-resource numbers can be trended over time and compared against serve-stress.yml's. # Real-repo coverage under the same Alpine/8 GiB path lives in serve-stress-alpine-real.yml. # # Split design (see tools/serve_stress_alpine.sh for the container side): bazel-diff is BUILT # on the glibc host -- building it inside Alpine would need the whole Bazel toolchain working -# under musl, the most fragile possible path -- and only RUN in the container, on Alpine's musl -# OpenJDK. The one glibc-bound runtime piece is the `bazel` binary serve shells out to for -# `bazel query`; the container script proves a flavor works (official release under gcompat -# with the server JVM redirected to the musl JDK, falling back to Alpine's packaged bazel) -# with a real probe query before committing the 30+ minute harness run to it. +# under musl, the most fragile possible path -- and only RUN in the container. The published +# Linux asset is statically linked against musl, so it needs nothing from the image. The one +# glibc-bound runtime piece is the `bazel` binary serve shells out to for `bazel query`; the +# container script proves a flavor works (official release under gcompat with the server JVM +# redirected to Alpine's musl OpenJDK, falling back to Alpine's packaged bazel) with a real +# probe query before committing the 30+ minute harness run to it. # # Scheduled runs use the full profile; on-demand runs from the Actions tab can select the # reduced quick profile and a different CPU quota. Note: scheduled runs execute from the @@ -53,11 +54,6 @@ jobs: # directory shared with the container) is exported here instead. - name: Compute stage dir run: echo "STAGE=$RUNNER_TEMP/alpine-stage" >> "$GITHUB_ENV" - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - name: Setup Go environment uses: actions/setup-go@v5 with: @@ -66,17 +62,21 @@ jobs: - name: Setup Bazelisk run: go install github.com/bazelbuild/bazelisk@latest - uses: actions/checkout@v4 - - name: Build bazel-diff deploy jar (glibc host) - run: ~/go/bin/bazelisk build //cli:bazel-diff_deploy.jar + # The published Linux asset: statically linked against musl, so it runs + # on Alpine as-is. Built here rather than in the container because the + # build needs the full Bazel + Rust toolchain, which only has to work on + # the glibc host. + - name: Build bazel-diff (static musl binary) + run: ~/go/bin/bazelisk build //release:bazel-diff --config=release-musl - name: Stage container inputs - # Everything the container needs goes into one mounted directory: the deploy jar, the + # Everything the container needs goes into one mounted directory: the static binary, the # harness sources, .bazelversion (the harness copies it into fabricated workspaces), # and the official bazel binary matching .bazelversion -- downloaded here on the host # so the container needs no network beyond apk. bazel-bin is a symlink into the host's # output base, hence the copy rather than mounting the repo. run: | mkdir -p "$STAGE/tools" - cp bazel-bin/cli/bazel-diff_deploy.jar "$STAGE/bazel-diff.jar" + cp bazel-bin/release/bazel-diff-rust-linux-amd64 "$STAGE/bazel-diff" cp tools/serve_stress.py tools/serve_harness.py tools/serve_stress_alpine.sh "$STAGE/tools/" cp .bazelversion "$STAGE/" BV=$(cat .bazelversion) diff --git a/.github/workflows/serve-stress-real.yml b/.github/workflows/serve-stress-real.yml index 37a6c0e6..9f7c66ec 100644 --- a/.github/workflows/serve-stress-real.yml +++ b/.github/workflows/serve-stress-real.yml @@ -45,11 +45,6 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 90 steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - name: Setup Go environment uses: actions/setup-go@v5 with: diff --git a/.github/workflows/serve-stress.yml b/.github/workflows/serve-stress.yml index 1748f667..1b239372 100644 --- a/.github/workflows/serve-stress.yml +++ b/.github/workflows/serve-stress.yml @@ -25,11 +25,6 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 45 steps: - - name: Setup Java JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '21' - name: Setup Go environment uses: actions/setup-go@v5 with: diff --git a/BUILD b/BUILD index cdae898b..61c07811 100644 --- a/BUILD +++ b/BUILD @@ -1,4 +1,3 @@ -load("@rules_kotlin//kotlin:core.bzl", "define_kt_toolchain") load("@rules_license//rules:license.bzl", "license") load("@rules_rust//rust:defs.bzl", "rust_clippy_test", "rustfmt_test") @@ -11,15 +10,19 @@ exports_files( visibility = ["//src:__pkg__"], ) +# The bazel-diff CLI. `bazel run //:bazel-diff -- --help`. alias( name = "bazel-diff", - actual = "//cli:bazel-diff", + actual = "//src:bazel-diff", ) +# Kept as a second name for the same binary: it is what the BCR presubmit's +# verify_targets, the release rule under //release and existing consumers +# build, from when the JVM CLI owned the `bazel-diff` name and this was the +# Rust candidate. Both aliases resolve to the one implementation. alias( name = "bazel-diff-rust", actual = "//src:bazel-diff", - visibility = ["//visibility:public"], ) test_suite( @@ -64,9 +67,11 @@ rustfmt_test( transitive = True, ) +# `bazel run //:format` rewrites the Rust sources with the pinned rustfmt; see +# //tools/format for the Starlark formatter. alias( name = "format", - actual = "//cli/format:format", + actual = "//tools/format:rustfmt", ) package( @@ -83,8 +88,3 @@ license( package_url = "https://github.com/Tinder/bazel-diff", package_version = "46.0.0", ) - -define_kt_toolchain( - name = "kotlin_toolchain", - jvm_target = "11", -) diff --git a/MODULE.bazel b/MODULE.bazel index fd504348..f4bf159b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -13,35 +13,12 @@ bazel_dep(name = "bazel_skylib", version = "1.9.0") # Direct dep because //release selects on @platforms//os and @platforms//cpu to # name the release binaries. bazel_dep(name = "platforms", version = "1.1.0") -bazel_dep(name = "rules_proto", version = "7.1.0") -bazel_dep(name = "rules_java", version = "9.7.0") -bazel_dep(name = "rules_kotlin", version = "2.4.0") bazel_dep(name = "rules_license", version = "1.0.0") -# kt_jvm_test hardcodes its _lcov_merger to Bazel's built-in merger instead of -# reading the coverage fragment's output_generator configuration field, so it -# ignores --coverage_output_generator (see tools/coverage/README.md — that flag -# is how per-target coverage minimums are enforced). Patch it to use the -# configuration field, like rules_go/rules_rust/rules_java do. Root-module-only: -# overrides are ignored when bazel-diff is consumed as a dependency. -single_version_override( - module_name = "rules_kotlin", - patch_strip = 1, - patches = ["//tools/coverage:rules_kotlin_lcov_merger.patch"], -) - -bazel_dep(name = "rules_jvm_external", version = "6.10") - -# Add protobuf and grpc for Bazel 9 compatibility +# Supplies `@protobuf//:protoc`, which the cargo_build_script in //src runs to +# generate the Rust bindings for rust/proto/*.proto (Bazel's own query and +# analysis protos). Pinned to a Bazel 9 compatible release. bazel_dep(name = "protobuf", version = "33.4") -bazel_dep(name = "stardoc", version = "0.7.2") - -single_version_override( - module_name = "grpc-java", - version = "1.78.0", -) - -bazel_dep(name = "grpc", version = "1.76.0", repo_name = "com_github_grpc_grpc") # Override transitive dependencies to Bazel 9 compatible versions. # Marked dev_dependency: these are only needed when building bazel-diff itself @@ -61,10 +38,10 @@ bazel_dep(name = "gazelle", version = "0.47.0", dev_dependency = True) go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk", dev_dependency = True) go_sdk.download(version = "1.23.1") -# Rust is a first-class published implementation (`//:bazel-diff-rust`) and is -# also used by the LCOV merger under //tools/coverage. Kept as a non-dev -# dependency so BCR consumers and top-level verify_targets can build the Rust -# binary without --ignore_dev_dependency stripping the crate repos/toolchains. +# Rust builds the published implementation (`//:bazel-diff`) and the LCOV merger +# under //tools/coverage. Kept as a non-dev dependency so BCR consumers and +# top-level verify_targets can build the binary without --ignore_dev_dependency +# stripping the crate repos/toolchains. bazel_dep(name = "rules_rust", version = "0.73.0") rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") @@ -233,62 +210,6 @@ rust.repository_set( versions = ["1.85.0"], ) -maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") -maven.install( - name = "bazel_diff_maven", - artifacts = [ - "com.google.code.gson:gson:2.14.0@jar", - "com.google.guava:guava:33.6.0-jre", - "com.willowtreeapps.assertk:assertk-jvm:0.28.1", - "info.picocli:picocli:4.7.7@jar", - "io.insert-koin:koin-core-jvm:4.2.2", - "io.insert-koin:koin-test-junit4:4.2.2", - "junit:junit:4.13.2", - "org.apache.commons:commons-pool2:2.13.1", - "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0", - "org.mockito.kotlin:mockito-kotlin:6.3.0", - "software.amazon.awssdk:url-connection-client:2.46.7", - ], - fail_if_repin_required = True, - lock_file = "//:maven_install.json", -) - -# AWS SDK for the `serve` S3 hash-cache backend. The Netty/Apache HTTP clients are -# excluded (the service explicitly uses the lightweight url-connection-client above) -# to keep the deploy jar lean. `sts` is runtime-only: DefaultCredentialsProvider needs -# it to assume the pod's IAM role via web identity (IRSA) when running on EKS. -maven.artifact( - name = "bazel_diff_maven", - artifact = "s3", - exclusions = [ - "software.amazon.awssdk:netty-nio-client", - "software.amazon.awssdk:apache-client", - "software.amazon.awssdk:apache5-client", - ], - group = "software.amazon.awssdk", - version = "2.46.7", -) -maven.artifact( - name = "bazel_diff_maven", - artifact = "sts", - exclusions = [ - "software.amazon.awssdk:netty-nio-client", - "software.amazon.awssdk:apache-client", - "software.amazon.awssdk:apache5-client", - ], - group = "software.amazon.awssdk", - version = "2.46.7", -) -use_repo( - maven, - bazel_diff_maven = "bazel_diff_maven", -) - -non_module_repositories = use_extension("//:extensions.bzl", "non_module_repositories", dev_dependency = True) -use_repo(non_module_repositories, "ktfmt") - -register_toolchains("//:kotlin_toolchain") - crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") crate.from_cargo( name = "bazel_diff_crates", @@ -296,7 +217,7 @@ crate.from_cargo( # crate_universe refuses to run from_cargo in a non-root module without a # checked-in cargo-bazel lockfile (repinning can't cross module boundaries), # so BCR consumers need this file shipped in the release archive. - # Regenerate with: CARGO_BAZEL_REPIN=1 bazel build //:bazel-diff-rust + # Regenerate with: CARGO_BAZEL_REPIN=1 bazel build //:bazel-diff # (`bazel sync` is WORKSPACE-only and this module is bzlmod-only). lockfile = "//:cargo-bazel-lock.json", manifests = ["//:Cargo.toml"], diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index a0274719..0c87d621 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -4,39 +4,24 @@ "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", - "https://bcr.bazel.build/modules/abseil-cpp/20220623.1/MODULE.bazel": "73ae41b6818d423a11fd79d95aedef1258f304448193d4db4ff90e5e7a0f076c", "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.0/MODULE.bazel": "98dc378d64c12a4e4741ad3362f87fb737ee6a0886b2d90c3cdbb4d93ea3e0bf", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", - "https://bcr.bazel.build/modules/abseil-cpp/20240722.0/MODULE.bazel": "88668a07647adbdc14cb3a7cd116fb23c9dda37a90a1681590b6c9d8339a5b84", - "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/MODULE.bazel": "d1086e248cda6576862b4b3fe9ad76a214e08c189af5b42557a6e1888812c5d5", "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", - "https://bcr.bazel.build/modules/abseil-cpp/20250512.0/MODULE.bazel": "c4d02dd22cd87458516655a45512060246ee2a4732f1fbe948a5bd9eb614e626", "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", - "https://bcr.bazel.build/modules/abseil-cpp/20250814.0/MODULE.bazel": "c43c16ca2c432566cdb78913964497259903ebe8fb7d9b57b38e9f1425b427b8", - "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", - "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", - "https://bcr.bazel.build/modules/abseil-py/2.1.0/MODULE.bazel": "5ebe5bf853769c65707e5c28f216798f7a4b1042015e6a36e6d03094d94bec8a", - "https://bcr.bazel.build/modules/abseil-py/2.1.0/source.json": "0e8fc4f088ce07099c1cd6594c20c7ddbb48b4b3c0849b7d94ba94be88ff042b", + "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/source.json": "d725d73707d01bb46ab3ca59ba408b8e9bd336642ca77a2269d4bfb8bbfd413d", "https://bcr.bazel.build/modules/ape/1.0.1/MODULE.bazel": "37411cfd13bfc28cd264674d660a3ecb3b5b35b9dbe4c0b2be098683641b3fee", "https://bcr.bazel.build/modules/ape/1.0.1/source.json": "96bc5909d1e3ccc4203272815ef874dbfd99651e240c05049f12193d16c1110b", "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", - "https://bcr.bazel.build/modules/apple_support/1.13.0/MODULE.bazel": "7c8cdea7e031b7f9f67f0b497adf6d2c6a2675e9304ca93a9af6ed84eef5a524", "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", - "https://bcr.bazel.build/modules/apple_support/1.17.1/MODULE.bazel": "655c922ab1209978a94ef6ca7d9d43e940cd97d9c172fb55f94d91ac53f8610b", - "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", - "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", "https://bcr.bazel.build/modules/apple_support/1.24.1/MODULE.bazel": "f46e8ddad60aef170ee92b2f3d00ef66c147ceafea68b6877cb45bd91737f5f8", - "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", - "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", + "https://bcr.bazel.build/modules/apple_support/1.24.1/source.json": "cf725267cbacc5f028ef13bb77e7f2c2e0066923a4dab1025e4a0511b1ed258a", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.31.2/MODULE.bazel": "7bee702b4862612f29333590f4b658a5832d433d6f8e4395f090e8f4e85d442f", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.38.0/MODULE.bazel": "6307fec451ba9962c1c969eb516ebfe1e46528f7fa92e1c9ac8646bef4cdaa3f", - "https://bcr.bazel.build/modules/aspect_bazel_lib/1.40.3/MODULE.bazel": "668e6bcb4d957fc0e284316dba546b705c8d43c857f87119619ee83c4555b859", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.42.2/MODULE.bazel": "2e0d8ab25c57a14f56ace1c8e881b69050417ff91b2fb7718dc00d201f3c3478", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.0.0/MODULE.bazel": "e118477db5c49419a88d78ebc7a2c2cea9d49600fe0f490c1903324a2c16ecd9", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", @@ -46,7 +31,6 @@ "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", "https://bcr.bazel.build/modules/aspect_rules_js/1.33.1/MODULE.bazel": "db3e7f16e471cf6827059d03af7c21859e7a0d2bc65429a3a11f005d46fc501b", - "https://bcr.bazel.build/modules/aspect_rules_js/1.39.0/MODULE.bazel": "aece421d479e3c31dc3e5f6d49a12acc2700457c03c556650ec7a0ff23fc0d95", "https://bcr.bazel.build/modules/aspect_rules_js/1.40.0/MODULE.bazel": "01a1014e95e6816b68ecee2584ae929c7d6a1b72e4333ab1ff2d2c6c30babdf1", "https://bcr.bazel.build/modules/aspect_rules_js/1.40.0/source.json": "b6fd491369e9ef888fdef64b839023a2360caaea8eb370d2cfbfdd2a96721311", "https://bcr.bazel.build/modules/aspect_rules_lint/0.12.0/MODULE.bazel": "e767c5dbfeb254ec03275a7701b5cfde2c4d2873676804bc7cb27ddff3728fed", @@ -58,7 +42,6 @@ "https://bcr.bazel.build/modules/bazel_features/1.0.0/MODULE.bazel": "d7f022dc887efb96e1ee51cec7b2e48d41e36ff59a6e4f216c40e4029e1585bf", "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", - "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.13.0/MODULE.bazel": "c14c33c7c3c730612bdbe14ebbb5e61936b6f11322ea95a6e91cd1ba962f94df", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", @@ -66,7 +49,6 @@ "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", - "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", @@ -81,12 +63,10 @@ "https://bcr.bazel.build/modules/bazel_features/1.50.0/source.json": "e0ee3debde2789ff56e4452e612d126925ba9ab64d4bde79c67f099d2902df9b", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", - "https://bcr.bazel.build/modules/bazel_jar_jar/0.1.7/MODULE.bazel": "d2736a1dbfd8f72befc532823b5112f2597a28064ce4aac280c65bee7d8830a0", "https://bcr.bazel.build/modules/bazel_lib/3.0.0-beta.1/MODULE.bazel": "407729e232f611c3270005b016b437005daa7b1505826798ea584169a476e878", "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", - "https://bcr.bazel.build/modules/bazel_lib/3.1.0/MODULE.bazel": "6809765c14e3c766a9b9286c7b0ec56ed87a73326e48fe01749f0c0fdcfe3287", - "https://bcr.bazel.build/modules/bazel_lib/3.1.0/source.json": "aaf7c2dc816219f4cb356c9d65f2555fb7f9543e537199f74a921f7877d23dfb", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -103,45 +83,13 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7", "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0", - "https://bcr.bazel.build/modules/bazel_worker_api/0.0.1/MODULE.bazel": "02a13b77321773b2042e70ee5e4c5e099c8ddee4cf2da9cd420442c36938d4bd", - "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/MODULE.bazel": "460aa12d01231a80cce03c548287b433b321d205b0028ae596728c35e5ee442e", - "https://bcr.bazel.build/modules/bazel_worker_api/0.0.8/MODULE.bazel": "396c1ef53835aafe3d42ce6619080531ee770648303731f16cfaa33fa056bf0c", - "https://bcr.bazel.build/modules/bazel_worker_api/0.0.8/source.json": "abaf8ac9d2ab2f47bda9af4c0c080ff7907378888e1f4bc62a0539dd13ba61e8", - "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/MODULE.bazel": "82494a01018bb7ef06d4a17ec4cd7a758721f10eb8b6c820a818e70d669500db", - "https://bcr.bazel.build/modules/bazel_worker_java/0.0.8/MODULE.bazel": "e76479eae70bd4e8f5f4c2dfc5d03ab971cfb18750246c7b3f3454c5c2ee6629", - "https://bcr.bazel.build/modules/bazel_worker_java/0.0.8/source.json": "9395c4679444bc47bf7e51a710366a4480aa371c6f6bed01868e2fabcf11acec", - "https://bcr.bazel.build/modules/boringssl/0.0.0-20211025-d4f1ab9/MODULE.bazel": "6ee6353f8b1a701fe2178e1d925034294971350b6d3ac37e67e5a7d463267834", - "https://bcr.bazel.build/modules/boringssl/0.0.0-20230215-5c22014/MODULE.bazel": "4b03dc0d04375fa0271174badcd202ed249870c8e895b26664fd7298abea7282", - "https://bcr.bazel.build/modules/boringssl/0.0.0-20240530-2db0eb3/MODULE.bazel": "d0405b762c5e87cd445b7015f2b8da5400ef9a8dbca0bfefa6c1cea79d528a97", - "https://bcr.bazel.build/modules/boringssl/0.20240913.0/MODULE.bazel": "fcaa7503a5213290831a91ed1eb538551cf11ac0bc3a6ad92d0fef92c5bd25fb", - "https://bcr.bazel.build/modules/boringssl/0.20241024.0/MODULE.bazel": "b540cff73d948cb79cb0bc108d7cef391d2098a25adabfda5043e4ef548dbc87", - "https://bcr.bazel.build/modules/boringssl/0.20241024.0/source.json": "d843092e682b84188c043ac742965d7f96e04c846c7e338187e03238674909a9", "https://bcr.bazel.build/modules/buildifier_prebuilt/6.4.0/MODULE.bazel": "37389c6b5a40c59410b4226d3bb54b08637f393d66e2fa57925c6fcf68e64bf4", "https://bcr.bazel.build/modules/buildifier_prebuilt/8.5.1.2/MODULE.bazel": "9a6e0a2e87d1e3da679e157da5192ea351d5739ca1ff51831c2b736d5b6034de", "https://bcr.bazel.build/modules/buildifier_prebuilt/8.5.1.2/source.json": "33e11b3bf11e39cb762480a7e6ea1d24d044636135cdd8b8e74b07ebcd3b8d8b", "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", - "https://bcr.bazel.build/modules/c-ares/1.15.0/MODULE.bazel": "ba0a78360fdc83f02f437a9e7df0532ad1fbaa59b722f6e715c11effebaa0166", - "https://bcr.bazel.build/modules/c-ares/1.19.1/MODULE.bazel": "73bca21720772370ff91cc8e88bbbaf14897720c6473e87c1ddc0f848284c313", - "https://bcr.bazel.build/modules/c-ares/1.34.5.bcr.1/MODULE.bazel": "f4632f68dbc075342966477d9c94a8a4a299d91e155980b042e1cd9f5a7ebcf5", - "https://bcr.bazel.build/modules/c-ares/1.34.5.bcr.1/source.json": "55fae1e004176f6cb36efcc77de9894f799d985f482b8f82fec989d556745a84", - "https://bcr.bazel.build/modules/cel-spec/0.15.0/MODULE.bazel": "e1eed53d233acbdcf024b4b0bc1528116d92c29713251b5154078ab1348cb600", - "https://bcr.bazel.build/modules/cel-spec/0.24.0/MODULE.bazel": "e310c7aff8490ed689ccafd32729b77a660b9547f5a5ba9b20e967011c324b36", - "https://bcr.bazel.build/modules/cel-spec/0.24.0/source.json": "522d08bc22524e07863276dd0f038f446a83166e91281dcfc07d5b8433c8d89e", - "https://bcr.bazel.build/modules/civetweb/1.16/MODULE.bazel": "46a38f9daeb57392e3827fce7d40926be0c802bd23cdd6bfd3a96c804de42fae", - "https://bcr.bazel.build/modules/civetweb/1.16/source.json": "ba8b9585adb8355cb51b999d57172fd05e7a762c56b8d4bac6db42c99de3beb7", - "https://bcr.bazel.build/modules/curl/8.4.0/MODULE.bazel": "0bc250aa1cb69590049383df7a9537c809591fcf876c620f5f097c58fdc9bc10", - "https://bcr.bazel.build/modules/curl/8.7.1/MODULE.bazel": "088221c35a2939c555e6e47cb31a81c15f8b59f4daa8009b1e9271a502d33485", - "https://bcr.bazel.build/modules/curl/8.8.0/MODULE.bazel": "7da3b3e79b0b4ee8f8c95d640bc6ad7b430ce66ef6e9c9d2bc29b3b5ef85f6fe", - "https://bcr.bazel.build/modules/curl/8.8.0/source.json": "d7d138b6878cf38891692fee0649ace35357fd549b425614d571786f054374d4", - "https://bcr.bazel.build/modules/cython/3.0.11-1/MODULE.bazel": "868b3f5c956c3657420d2302004c6bb92606bfa47e314bab7f2ba0630c7c966c", - "https://bcr.bazel.build/modules/cython/3.0.11-1/source.json": "da318be900b8ca9c3d1018839d3bebc5a8e1645620d0848fa2c696d4ecf7c296", "https://bcr.bazel.build/modules/download_utils/1.0.1/MODULE.bazel": "f1d0afade59e37de978506d6bbf08d7fe5f94964e86944aaf58efcead827b41b", "https://bcr.bazel.build/modules/download_utils/1.0.1/source.json": "05ddc5a3b1f7d8f3e5e0fd1617479e1cf72d63d59ab2b1f0463557a14fc6be0a", - "https://bcr.bazel.build/modules/envoy_api/0.0.0-20241214-918efc9/MODULE.bazel": "24e05f6f52f37be63a795192848555a2c8c855e7814dbc1ed419fb04a7005464", - "https://bcr.bazel.build/modules/envoy_api/0.0.0-20250128-4de3c74/MODULE.bazel": "1fe72489212c530086e3ffb0e018b2bfef4663200ca03571570f9f006bef1d75", - "https://bcr.bazel.build/modules/envoy_api/0.0.0-20251105-4a2b9a3/MODULE.bazel": "b66e87a0e0c2207f07e35c321388eb1feb036344565977444b52912c53a84466", - "https://bcr.bazel.build/modules/envoy_api/0.0.0-20251105-4a2b9a3/source.json": "c4780edf780977f2ab7d00a189432c5b0b2fa08c6e4e2e09d2950499364a687d", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", "https://bcr.bazel.build/modules/gazelle/0.27.0/MODULE.bazel": "3446abd608295de6d90b4a8a118ed64a9ce11dcb3dda2dc3290a22056bd20996", @@ -150,88 +98,28 @@ "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", - "https://bcr.bazel.build/modules/gazelle/0.37.0/MODULE.bazel": "d1327ba0907d0275ed5103bfbbb13518f6c04955b402213319d0d6c0ce9839d4", - "https://bcr.bazel.build/modules/gazelle/0.39.1/MODULE.bazel": "1fa3fefad240e535066fd0e6950dfccd627d36dc699ee0034645e51dbde3980f", - "https://bcr.bazel.build/modules/gazelle/0.40.0/MODULE.bazel": "42ba5378ebe845fca43989a53186ab436d956db498acde790685fe0e8f9c6146", - "https://bcr.bazel.build/modules/gazelle/0.46.0/MODULE.bazel": "3dec215dacf2427df87b524a2c99da387882a18d753f0b1b38675992bd0a99c6", "https://bcr.bazel.build/modules/gazelle/0.47.0/MODULE.bazel": "b61bb007c4efad134aa30ee7f4a8e2a39b22aa5685f005edaa022fbd1de43ebc", "https://bcr.bazel.build/modules/gazelle/0.47.0/source.json": "aeb2e5df14b7fb298625d75d08b9c65bdb0b56014c5eb89da9e5dd0572280ae6", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", - "https://bcr.bazel.build/modules/google_benchmark/1.8.4/MODULE.bazel": "c6d54a11dcf64ee63545f42561eda3fd94c1b5f5ebe1357011de63ae33739d5e", - "https://bcr.bazel.build/modules/google_benchmark/1.8.5/MODULE.bazel": "9ba9b31b984022828a950e3300410977eda2e35df35584c6b0b2d0c2e52766b7", - "https://bcr.bazel.build/modules/google_benchmark/1.9.4/MODULE.bazel": "3bab7c17c10580f87b647478a72a05621f88abc275afb97b578c828f56e59d45", - "https://bcr.bazel.build/modules/google_benchmark/1.9.4/source.json": "8e0036f76a5c2aa9c16ca0da57d8065cff69edeed58f1f85584c588c0ef723a5", - "https://bcr.bazel.build/modules/googleapis-cc/1.0.0/MODULE.bazel": "cf01757e7590c56140a4b81638ff2b3e7074769e6271720bbf738fcda25b6fc2", - "https://bcr.bazel.build/modules/googleapis-cc/1.0.0/source.json": "ab0e3a2ee9968a8848f59872fbbfa3e1f768597d71d2229e6caa319d357967c7", - "https://bcr.bazel.build/modules/googleapis-go/1.0.0/MODULE.bazel": "0a207a4c49da28c5cc1f7b3aeb23c2f7828c85c14aa8d9db0e30357a8d2250ed", - "https://bcr.bazel.build/modules/googleapis-go/1.0.0/source.json": "ef189be4e7853e1ebc6123fe20b71822bf9896bd1f8eed8f68505c4585f72a48", - "https://bcr.bazel.build/modules/googleapis-java/1.0.0/MODULE.bazel": "d633989337d069b5a95e6101777319681d7a4af4677e36801f11839d6512095c", - "https://bcr.bazel.build/modules/googleapis-java/1.0.0/source.json": "ee59e2de37e4b531172870ac0296afa38f1ea004105ee21b2793c31a9d0ddccd", - "https://bcr.bazel.build/modules/googleapis-rules-registry/1.0.0/MODULE.bazel": "97c6a4d413b373d4cc97065da3de1b2166e22cbbb5f4cc9f05760bfa83619e24", - "https://bcr.bazel.build/modules/googleapis-rules-registry/1.0.0/source.json": "cf611c836a60e98e2e2ab2de8004f119e9f06878dcf4ea2d95a437b1b7a89fe9", - "https://bcr.bazel.build/modules/googleapis/0.0.0-20240326-1c8d509c5/MODULE.bazel": "a4b7e46393c1cdcc5a00e6f85524467c48c565256b22b5fae20f84ab4a999a68", - "https://bcr.bazel.build/modules/googleapis/0.0.0-20240819-fe8ba054a/MODULE.bazel": "117b7c7be7327ed5d6c482274533f2dbd78631313f607094d4625c28203cacdf", - "https://bcr.bazel.build/modules/googleapis/0.0.0-20241220-5e258e33.bcr.1/MODULE.bazel": "ee6c30f82ecd476e61f019fb1151aaab380ea419958ff274ef2f0efca7969f5c", - "https://bcr.bazel.build/modules/googleapis/0.0.0-20251003-2193a2bf/MODULE.bazel": "cc9e5ed294ed9ebf42cdbbdddd2df29048519e3797004df1e3f369f31ff4f2d4", - "https://bcr.bazel.build/modules/googleapis/0.0.0-20251003-2193a2bf/source.json": "21558a194c519e27262cca9cf031bb166a666a8b7fb89993b700c828f8dc0857", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", - "https://bcr.bazel.build/modules/googletest/1.16.0/MODULE.bazel": "a175623c69e94fca4ca7acbc12031e637b0c489318cd4805606981d4d7adb34a", "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", "https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713", - "https://bcr.bazel.build/modules/grpc-java/1.78.0/MODULE.bazel": "48f790fbb95625245295df1283e0dba344a4e30b4a9a9cefbabfa92bd84f3691", - "https://bcr.bazel.build/modules/grpc-proto/0.0.0-20240627-ec30f58/MODULE.bazel": "88de79051e668a04726e9ea94a481ec6f1692086735fd6f488ab908b3b909238", - "https://bcr.bazel.build/modules/grpc/1.41.0/MODULE.bazel": "5bcbfc2b274dabea628f0649dc50c90cf36543b1cfc31624832538644ad1aae8", - "https://bcr.bazel.build/modules/grpc/1.56.3.bcr.1/MODULE.bazel": "cd5b1eb276b806ec5ab85032921f24acc51735a69ace781be586880af20ab33f", - "https://bcr.bazel.build/modules/grpc/1.62.1/MODULE.bazel": "2998211594b8a79a6b459c4e797cfa19f0fb8b3be3149760ec7b8c99abfd426f", - "https://bcr.bazel.build/modules/grpc/1.63.1.bcr.1/MODULE.bazel": "d7b9fef03bd175e6825237b521b18a3c29f1ac15f8aa52c8a1a0f3bd8f33d54b", - "https://bcr.bazel.build/modules/grpc/1.66.0.bcr.2/MODULE.bazel": "0fa2b0fd028ce354febf0fe90f1ed8fecfbfc33118cddd95ac0418cc283333a0", - "https://bcr.bazel.build/modules/grpc/1.66.0.bcr.3/MODULE.bazel": "f6047e89faf488f5e3e65cb2594c6f5e86992abec7487163ff6b623526e543b0", - "https://bcr.bazel.build/modules/grpc/1.69.0/MODULE.bazel": "4e26e05c9e1ef291ccbc96aad8e457b1b8abedbc141623831629da2f8168eef6", - "https://bcr.bazel.build/modules/grpc/1.71.0/MODULE.bazel": "7fcab2c05530373f1a442c362b17740dd0c75b6a2a975eec8f5bf4c70a37928a", - "https://bcr.bazel.build/modules/grpc/1.74.1/MODULE.bazel": "09523be10ba2bfd999683671d0f8f22fb5b20ec77ad89b05ef58ff19a1b65c82", - "https://bcr.bazel.build/modules/grpc/1.76.0/MODULE.bazel": "7373bd407fcb183b8bf379fa35dfe29feebdc6d8ec1bc40004cec6c7b2544be0", - "https://bcr.bazel.build/modules/grpc/1.76.0/source.json": "5c1deef5b02cb7dd52df9cb628d956e02db3ec40bd165a465bc10b7bf47aa16a", "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", "https://bcr.bazel.build/modules/jq.bzl/0.4.0/MODULE.bazel": "a7b39b37589f2b0dad53fd6c1ccaabbdb290330caa920d7ef3e6aad068cd4ab2", "https://bcr.bazel.build/modules/jq.bzl/0.4.0/source.json": "52ec7530c4618e03f634b30ff719814a68d7d39c235938b7aa2abbfe1eb1c52c", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", - "https://bcr.bazel.build/modules/libpfm/4.11.0.bcr.1/MODULE.bazel": "e5362dadc90aab6724c83a2cc1e67cbed9c89a05d97fb1f90053c8deb1e445c8", - "https://bcr.bazel.build/modules/libpfm/4.11.0.bcr.1/source.json": "0646414d9037f8aad148781dd760bec90b0b25ac12fda5e03f8aadbd6b9c61e6", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", - "https://bcr.bazel.build/modules/mbedtls/3.6.0/MODULE.bazel": "8e380e4698107c5f8766264d4df92e36766248447858db28187151d884995a09", - "https://bcr.bazel.build/modules/mbedtls/3.6.0/source.json": "1dbe7eb5258050afcc3806b9d43050f71c6f539ce0175535c670df606790b30c", - "https://bcr.bazel.build/modules/nlohmann_json/3.11.3/MODULE.bazel": "87023db2f55fc3a9949c7b08dc711fae4d4be339a80a99d04453c4bb3998eefc", - "https://bcr.bazel.build/modules/nlohmann_json/3.11.3/source.json": "296c63a90c6813e53b3812d24245711981fc7e563d98fe15625f55181494488a", "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", - "https://bcr.bazel.build/modules/opencensus-cpp/0.0.0-20230502-50eb5de.bcr.2/MODULE.bazel": "cc18734138dd18c912c6ce2a59186db28f85d8058c99c9f21b46ca3e0aba0ebe", - "https://bcr.bazel.build/modules/opencensus-cpp/0.0.0-20230502-50eb5de.bcr.2/source.json": "7c135f9d42bb3b045669c3c6ab3bb3c208e00b46aca4422eea64c29811a5b240", - "https://bcr.bazel.build/modules/opencensus-cpp/0.0.0-20230502-50eb5de/MODULE.bazel": "02201d2921dadb4ec90c4980eca4b2a02904eddcf6fa02f3da7594fb7b0d821c", - "https://bcr.bazel.build/modules/opencensus-proto/0.4.1.bcr.2/MODULE.bazel": "789706a714855f92c5c8cfcf1ef32bbb64dcd3b7c9066756ad7986ec59709d29", - "https://bcr.bazel.build/modules/opencensus-proto/0.4.1.bcr.2/source.json": "aadf3f53e08b72376506b7c4ea3d167010c9efb160d7d6e1e304ed646bac1b36", - "https://bcr.bazel.build/modules/opencensus-proto/0.4.1/MODULE.bazel": "4a2e8b4d0b544002502474d611a5a183aa282251e14f6a01afe841c0c1b10372", - "https://bcr.bazel.build/modules/openssl/3.3.1.bcr.1/MODULE.bazel": "49c0c07e8fb87b480bccb842cfee1b32617f11dac590f732573c69058699a3d1", - "https://bcr.bazel.build/modules/openssl/3.3.1.bcr.1/source.json": "0c0872e048bbea052a9c541fb47019481a19201ba5555a71d762ad591bf94e1f", - "https://bcr.bazel.build/modules/opentelemetry-cpp/1.14.2/MODULE.bazel": "089a5613c2a159c7dfde098dabfc61e966889c7d6a81a98422a84c51535ed17d", - "https://bcr.bazel.build/modules/opentelemetry-cpp/1.16.0/MODULE.bazel": "b7379a140f538cea3f749179a2d481ed81942cc6f7b05a6113723eb34ac3b3e7", - "https://bcr.bazel.build/modules/opentelemetry-cpp/1.19.0/MODULE.bazel": "3455326c08b28415648a3d60d8e3c811847ebdbe64474f75b25878f25585aea1", - "https://bcr.bazel.build/modules/opentelemetry-cpp/1.19.0/source.json": "4e48137e4c3ecb99401ff99876df8fa330598d7da051869bec643446e8a8ff95", - "https://bcr.bazel.build/modules/opentelemetry-proto/1.1.0/MODULE.bazel": "a49f406e99bf05ab43ed4f5b3322fbd33adfd484b6546948929d1316299b68bf", - "https://bcr.bazel.build/modules/opentelemetry-proto/1.3.1/MODULE.bazel": "0141a50e989576ee064c11ce8dd5ec89993525bd9f9a09c5618e4dacc8df9352", - "https://bcr.bazel.build/modules/opentelemetry-proto/1.4.0.bcr.1/MODULE.bazel": "5ceaf25e11170d22eded4c8032728b4a3f273765fccda32f9e94f463755c4167", - "https://bcr.bazel.build/modules/opentelemetry-proto/1.5.0/MODULE.bazel": "7543d91a53b98e7b5b37c5a0865b93bff12c1ee022b1e322cd236b968894b030", - "https://bcr.bazel.build/modules/opentelemetry-proto/1.5.0/source.json": "046b721ce203e88cdaad44d7dd17a86b7200eab9388b663b234e72e13ff7b143", - "https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/MODULE.bazel": "b3925269f63561b8b880ae7cf62ccf81f6ece55b62cd791eda9925147ae116ec", - "https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/source.json": "da1cb1add160f5e5074b7272e9db6fd8f1b3336c15032cd0a653af9d2f484aed", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", "https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83", "https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4", - "https://bcr.bazel.build/modules/package_metadata/0.0.7/MODULE.bazel": "7adb03933fc8401f495800cf4eafcff0edc6da0ff55c7db223ef69d19f689486", - "https://bcr.bazel.build/modules/package_metadata/0.0.7/source.json": "50639625e937b56115012674c797cca7a05a96b4878c87d803c13dc2b31de8a0", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/source.json": "2326db2f6592578177751c3e1f74786b79382cd6008834c9d01ec865b9126a85", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", @@ -243,22 +131,9 @@ "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", "https://bcr.bazel.build/modules/platforms/1.1.0/MODULE.bazel": "1c0c09f5bdcf4b3f924720d2478a3711cb39f4977019ca5988685e5b7e18b3d2", "https://bcr.bazel.build/modules/platforms/1.1.0/source.json": "fcf351c47596c939140ab0d333dfdd08ed1ea6ce33c2fe70c12493a301cf1344", - "https://bcr.bazel.build/modules/prometheus-cpp/1.2.4/MODULE.bazel": "0fbe5dcff66311947a3f6b86ebc6a6d9328e31a28413ca864debc4a043f371e5", - "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0.bcr.1/MODULE.bazel": "116ad46e97c1d2aeb020fe2899a342a7e703574ce7c0faf7e4810f938c974a9a", - "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0.bcr.1/source.json": "e813cce2d450708cfcb26e309c5172583a7440776edf354e83e6788c768e5cca", - "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0/MODULE.bazel": "ce82e086bbc0b60267e970f6a54b2ca6d0f22d3eb6633e00e2cc2899c700f3d8", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", - "https://bcr.bazel.build/modules/protobuf/23.1/MODULE.bazel": "88b393b3eb4101d18129e5db51847cd40a5517a53e81216144a8c32dfeeca52a", - "https://bcr.bazel.build/modules/protobuf/24.4/MODULE.bazel": "7bc7ce5f2abf36b3b7b7c8218d3acdebb9426aeb35c2257c96445756f970eb12", - "https://bcr.bazel.build/modules/protobuf/25.6/MODULE.bazel": "fc0ae073b47c7ede88b825ff79e64f1c058967c7a87a86cdf4abecd9e0516625", - "https://bcr.bazel.build/modules/protobuf/26.0.bcr.1/MODULE.bazel": "8f04d38c2da40a3715ff6bdce4d32c5981e6432557571482d43a62c31a24c2cf", - "https://bcr.bazel.build/modules/protobuf/26.0.bcr.2/MODULE.bazel": "62e0b84ca727bdeb55a6fe1ef180e6b191bbe548a58305ea1426c158067be534", - "https://bcr.bazel.build/modules/protobuf/26.0/MODULE.bazel": "8402da964092af40097f4a205eec2a33fd4a7748dc43632b7d1629bfd9a2b856", - "https://bcr.bazel.build/modules/protobuf/27.0-rc2/MODULE.bazel": "b2b0dbafd57b6bec0ca9b251da02e628c357dab53a097570aa7d79d020f107cf", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", - "https://bcr.bazel.build/modules/protobuf/27.2/MODULE.bazel": "32450b50673882e4c8c3d10a83f3bc82161b213ed2f80d17e38bece8f165c295", - "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", @@ -266,40 +141,19 @@ "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", - "https://bcr.bazel.build/modules/protobuf/30.0/MODULE.bazel": "0e736de5d52ad7824113f47e65256a26ee74b689ba859c5447a0663e5a075409", - "https://bcr.bazel.build/modules/protobuf/31.1/MODULE.bazel": "379a389bb330b7b8c1cdf331cc90bf3e13de5614799b3b52cdb7c6f389f6b38e", - "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", - "https://bcr.bazel.build/modules/protobuf/33.0/MODULE.bazel": "c5270efb4aad37a2f893536076518793f409ea7df07a06df995d848d1690f21c", "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", - "https://bcr.bazel.build/modules/protoc-gen-validate/1.0.4.bcr.2/MODULE.bazel": "c4bd2c850211ff5b7dadf9d2d0496c1c922fdedc303c775b01dfd3b3efc907ed", - "https://bcr.bazel.build/modules/protoc-gen-validate/1.0.4/MODULE.bazel": "b8913c154b16177990f6126d2d2477d187f9ddc568e95ee3e2d50fc65d2c494a", - "https://bcr.bazel.build/modules/protoc-gen-validate/1.2.1.bcr.1/MODULE.bazel": "4bf09676b62fa587ae07e073420a76ec8766dcce7545e5f8c68cfa8e484b5120", - "https://bcr.bazel.build/modules/protoc-gen-validate/1.2.1.bcr.2/MODULE.bazel": "3bd4b14a8e7c78dbef973280deabaa139db1fe350aa92da03730a31f59082068", - "https://bcr.bazel.build/modules/protoc-gen-validate/1.2.1.bcr.2/source.json": "14c28a5527fcd699f5efbf83a046666efabed3384364bd48428de89dfdc8110e", - "https://bcr.bazel.build/modules/protoc-gen-validate/1.2.1/MODULE.bazel": "52b51f50533ec4fbd5d613cd093773f979ac2e035d954e02ca11de383f502505", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", - "https://bcr.bazel.build/modules/pybind11_bazel/3.0.0/MODULE.bazel": "a2bfa6020ed603a00d944161c63173c7f109774e99bee0c2cd8dbf24159f8134", - "https://bcr.bazel.build/modules/pybind11_bazel/3.0.0/source.json": "d8f5104d4c21d272bf327ebe44366fb0b4c036cdaa1f5cceb21a408ca4ef2ef8", - "https://bcr.bazel.build/modules/rapidjson/1.1.0.bcr.20241007/MODULE.bazel": "82fbcb2e42f9e0040e76ccc74c06c3e46dfd33c64ca359293f8b84df0e6dff4c", - "https://bcr.bazel.build/modules/rapidjson/1.1.0.bcr.20241007/source.json": "5c42389ad0e21fc06b95ad7c0b730008271624a2fa3292e0eab5f30e15adeee3", - "https://bcr.bazel.build/modules/re2/2021-09-01/MODULE.bazel": "bcb6b96f3b071e6fe2d8bed9cc8ada137a105f9d2c5912e91d27528b3d123833", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", - "https://bcr.bazel.build/modules/re2/2024-05-01/MODULE.bazel": "55a3f059538f381107824e7d00df5df6d061ba1fb80e874e4909c0f0549e8f3e", "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4", "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", - "https://bcr.bazel.build/modules/re2/2025-11-05.bcr.1/MODULE.bazel": "3d9d4995833fc0334fc5c88b56a05288dd25d651544cd7b2233bbd6357bbeba0", - "https://bcr.bazel.build/modules/re2/2025-11-05.bcr.1/source.json": "7df1394aabda1c9bc188a302f5d54b1c657924edd04ebc57d2be29dbd7efd141", "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", - "https://bcr.bazel.build/modules/rules_android/0.6.6/MODULE.bazel": "b0fb569752aab65ab1a9db0a8f6cfaf5aa1754965e17e95dcf0e4d88e192a68d", - "https://bcr.bazel.build/modules/rules_android/0.7.1/MODULE.bazel": "a806fc382a774252f228a40e3b11b9fcc6276f8778c7fb33e9f72937c6258363", - "https://bcr.bazel.build/modules/rules_android/0.7.1/source.json": "151440aed3f0f73a00d4ed5cec5d31f63a6fef9b95d8fab1eb1810150fa525f2", - "https://bcr.bazel.build/modules/rules_apple/3.13.0/MODULE.bazel": "b4559a2c6281ca3165275bb36c1f0ac74666632adc5bdb680e366de7ce845f43", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", - "https://bcr.bazel.build/modules/rules_apple/3.5.1/MODULE.bazel": "3d1bbf65ad3692003d36d8a29eff54d4e5c1c5f4bfb60f79e28646a924d9101c", - "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", - "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366", "https://bcr.bazel.build/modules/rules_buf/0.1.1/MODULE.bazel": "6189aec18a4f7caff599ad41b851ab7645d4f1e114aa6431acf9b0666eb92162", "https://bcr.bazel.build/modules/rules_buf/0.5.2/MODULE.bazel": "5f2492d284ab9bedf2668178303abf5f3cd7d8cdf85d768951008e88456e9c6a", "https://bcr.bazel.build/modules/rules_buf/0.5.2/source.json": "41876d4834c0832de4b393de6e55dfd1cb3b25d3109e4ba90eb7fb57c560e0d9", @@ -311,26 +165,18 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", - "https://bcr.bazel.build/modules/rules_cc/0.0.5/MODULE.bazel": "be41f87587998fe8890cd82ea4e848ed8eb799e053c224f78f3ff7fe1a1d9b74", "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", - "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", - "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", - "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", - "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", "https://bcr.bazel.build/modules/rules_cc/0.2.18/MODULE.bazel": "4460ec36adc8f722a6a2a4ac9374cb91f2acebadaa93fc37966129afb3dece87", "https://bcr.bazel.build/modules/rules_cc/0.2.18/source.json": "abad668ff2fd63ada1ac49bf386d37e27048b89a3465a6fd968bb832b00a09d3", "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", - "https://bcr.bazel.build/modules/rules_cc/0.2.9/MODULE.bazel": "34263f1dca62ea664265438cef714d7db124c03e1ed55ebb4f1dc860164308d1", "https://bcr.bazel.build/modules/rules_diff/1.0.0/MODULE.bazel": "1739509d8db9a6cd7d3584822340d3dfe1f9f27e62462fbca60aa061d88741b2", "https://bcr.bazel.build/modules/rules_diff/1.0.0/source.json": "fc3824aed007b4db160ffb994036c6e558550857b6634a8e9ccee3e74c659312", - "https://bcr.bazel.build/modules/rules_foreign_cc/0.10.1/MODULE.bazel": "b9527010e5fef060af92b6724edb3691970a5b1f76f74b21d39f7d433641be60", "https://bcr.bazel.build/modules/rules_foreign_cc/0.15.1/MODULE.bazel": "c2c60d26c79fda484acb95cdbec46e89d6b28b4845cb277160ce1e0c8622bb88", "https://bcr.bazel.build/modules/rules_foreign_cc/0.15.1/source.json": "a161811a63ba8a859086da3b7ff3ad04f2e9c255d7727b41087103fc0eb22f55", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", @@ -340,67 +186,40 @@ "https://bcr.bazel.build/modules/rules_go/0.39.1/MODULE.bazel": "d34fb2a249403a5f4339c754f1e63dc9e5ad70b47c5e97faee1441fc6636cd61", "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", - "https://bcr.bazel.build/modules/rules_go/0.45.1/MODULE.bazel": "6d7884f0edf890024eba8ab31a621faa98714df0ec9d512389519f0edff0281a", "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", - "https://bcr.bazel.build/modules/rules_go/0.48.0/MODULE.bazel": "d00ebcae0908ee3f5e6d53f68677a303d6d59a77beef879598700049c3980a03", - "https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0", - "https://bcr.bazel.build/modules/rules_go/0.51.0-rc2/MODULE.bazel": "edfc3a9cea7bedb0eaaff37b0d7817c1a4bf72b3c615580b0ffcee6c52690fd4", "https://bcr.bazel.build/modules/rules_go/0.53.0/MODULE.bazel": "a4ed760d3ac0dbc0d7b967631a9a3fd9100d28f7d9fcf214b4df87d4bfff5f9a", - "https://bcr.bazel.build/modules/rules_go/0.58.3/MODULE.bazel": "5582119a4a39558d8d1b1634bcae46043d4f43a31415e861c3551b2860040b5e", - "https://bcr.bazel.build/modules/rules_go/0.59.0/MODULE.bazel": "b7e43e7414a3139a7547d1b4909b29085fbe5182b6c58cbe1ed4c6272815aeae", "https://bcr.bazel.build/modules/rules_go/0.60.0/MODULE.bazel": "4a57ff2ffc2a3570e3c5646575c5a4b07287e91bcdac5d1f72383d51502b48cb", "https://bcr.bazel.build/modules/rules_go/0.60.0/source.json": "1e21368c5e0c3013a110bd79a8fcff8ca46b5bcb2b561713a7273cbfcff7c464", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", - "https://bcr.bazel.build/modules/rules_java/5.1.0/MODULE.bazel": "324b6478b0343a3ce7a9add8586ad75d24076d6d43d2f622990b9c1cfd8a1b15", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", - "https://bcr.bazel.build/modules/rules_java/5.5.0/MODULE.bazel": "486ad1aa15cdc881af632b4b1448b0136c76025a1fe1ad1b65c5899376b83a50", "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963", - "https://bcr.bazel.build/modules/rules_java/6.3.1/MODULE.bazel": "5a3471c8b84d53d58d5f6e316313680d7dd2c70afac696dbe14b761b0b5c6a06", "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", "https://bcr.bazel.build/modules/rules_java/7.0.6/MODULE.bazel": "6ddb07d9857a1a3accc9f6d005f20c969c4659c7710e6269a51db3527e0ea969", - "https://bcr.bazel.build/modules/rules_java/7.1.0/MODULE.bazel": "30d9135a2b6561c761bd67bd4990da591e6bdc128790ce3e7afd6a3558b2fb64", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", - "https://bcr.bazel.build/modules/rules_java/7.4.0/MODULE.bazel": "a592852f8a3dd539e82ee6542013bf2cadfc4c6946be8941e189d224500a8934", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1", - "https://bcr.bazel.build/modules/rules_java/8.13.0/MODULE.bazel": "0444ebf737d144cf2bb2ccb368e7f1cce735264285f2a3711785827c1686625e", "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", - "https://bcr.bazel.build/modules/rules_java/8.16.1/MODULE.bazel": "0f20b1cecaa8e52f60a8f071e59a20b4e3b9a67f6c56c802ea256f6face692d3", + "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", - "https://bcr.bazel.build/modules/rules_java/8.6.0/MODULE.bazel": "9c064c434606d75a086f15ade5edb514308cccd1544c2b2a89bbac4310e41c71", "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", - "https://bcr.bazel.build/modules/rules_java/8.9.0/MODULE.bazel": "e17c876cb53dcd817b7b7f0d2985b710610169729e8c371b2221cacdcd3dce4a", - "https://bcr.bazel.build/modules/rules_java/9.3.0/MODULE.bazel": "f657c72d65ac449caae9abf2e68e66c0d36f9416848c4c4903d0b3234229e7f2", - "https://bcr.bazel.build/modules/rules_java/9.7.0/MODULE.bazel": "3ce6bd55fdd4fcb3323197736b7d976e0eedcc0eea78ca6186d20d314ba12e12", - "https://bcr.bazel.build/modules/rules_java/9.7.0/source.json": "23b356565156e0fbc71e5dad7cf8e1b951466b7ae3fd731cb1d2c95ca82b1d70", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", - "https://bcr.bazel.build/modules/rules_jvm_external/6.0/MODULE.bazel": "37c93a5a78d32e895d52f86a8d0416176e915daabd029ccb5594db422e87c495", "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", - "https://bcr.bazel.build/modules/rules_jvm_external/6.10/MODULE.bazel": "33e636ca6bc9ee0fa090a38aa33c631ded2d8cf6fead4124181d1b35dc474f7c", - "https://bcr.bazel.build/modules/rules_jvm_external/6.10/source.json": "c191249787625db72616a3fb3cc2786ab57355a2e3b615402b8b3b66b0f995b7", - "https://bcr.bazel.build/modules/rules_jvm_external/6.2/MODULE.bazel": "36a6e52487a855f33cb960724eb56547fa87e2c98a0474c3acad94339d7f8e99", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", - "https://bcr.bazel.build/modules/rules_jvm_external/6.6/MODULE.bazel": "153042249c7060536dc95b6bb9f9bb8063b8a0b0cb7acdb381bddbc2374aed55", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", - "https://bcr.bazel.build/modules/rules_jvm_external/6.9/MODULE.bazel": "07c5db05527db7744a54fcffd653e1550d40e0540207a7f7e6d0a4de5bef8274", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.5/MODULE.bazel": "043a16a572f610558ec2030db3ff0c9938574e7dd9f58bded1bb07c0192ef025", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", - "https://bcr.bazel.build/modules/rules_kotlin/2.1.3/MODULE.bazel": "ce7def6d576aa8d3a9c6d10e13b4d157296229674371f67dbf788dae0afae3d5", - "https://bcr.bazel.build/modules/rules_kotlin/2.4.0/MODULE.bazel": "38dac18bb76c0a47ff60dfcd95c666985cbc46374f28ea4eeb868bdbc58c5bec", - "https://bcr.bazel.build/modules/rules_kotlin/2.4.0/source.json": "07b6a307448817c071c4ba90dcb03f801e008959f8dfd8b152a241cc0ee01a23", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", - "https://bcr.bazel.build/modules/rules_license/0.0.8/MODULE.bazel": "5669c6fe49b5134dbf534db681ad3d67a2d49cfc197e4a95f1ca2fd7f3aebe96", "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", "https://bcr.bazel.build/modules/rules_multirun/0.9.0/MODULE.bazel": "32d628ef586b5b23f67e55886b7bc38913ea4160420d66ae90521dda2ff37df0", @@ -411,8 +230,6 @@ "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/MODULE.bazel": "7f9ea68a0ce6d82905ce9f74e76ab8a8b4531ed4c747018c9d76424ad0b3370d", "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/MODULE.bazel": "c22a48b2a0dbf05a9dc5f83837bbc24c226c1f6e618de3c3a610044c9f336056", "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/source.json": "a3f966f4415a8a6545e560ee5449eac95cc633f96429d08e87c87775c72f5e09", - "https://bcr.bazel.build/modules/rules_perl/0.2.4/MODULE.bazel": "5f5af7be4bf5fb88d91af7469518f0fd2161718aefc606188f7cd51f436ca938", - "https://bcr.bazel.build/modules/rules_perl/0.2.4/source.json": "574317d6b3c7e4843fe611b76f15e62a1889949f5570702e1ee4ad335ea3c339", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", @@ -425,38 +242,23 @@ "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", - "https://bcr.bazel.build/modules/rules_python/0.20.0/MODULE.bazel": "bfe14d17f20e3fe900b9588f526f52c967a6f281e47a1d6b988679bd15082286", - "https://bcr.bazel.build/modules/rules_python/0.22.0/MODULE.bazel": "b8057bafa11a9e0f4b08fc3b7cd7bee0dcbccea209ac6fc9a3ff051cd03e19e9", - "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", "https://bcr.bazel.build/modules/rules_python/0.26.0/MODULE.bazel": "42cb98cd15954e83b96b540dcc6d5a618eb061f056147ac4ea46e687a066a7c7", "https://bcr.bazel.build/modules/rules_python/0.27.1/MODULE.bazel": "65dc875cc1a06c30d5bbdba7ab021fd9e551a6579e408a3943a61303e2228a53", "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", - "https://bcr.bazel.build/modules/rules_python/0.29.0/MODULE.bazel": "2ac8cd70524b4b9ec49a0b8284c79e4cd86199296f82f6e0d5da3f783d660c82", "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", - "https://bcr.bazel.build/modules/rules_python/0.32.2/MODULE.bazel": "01052470fc30b49de91fb8483d26bea6f664500cfad0b078d4605b03e3a83ed4", "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", - "https://bcr.bazel.build/modules/rules_python/0.35.0/MODULE.bazel": "c3657951764cdcdb5a7370d5e885fad5e8c1583320aad18d46f9f110d2c22755", - "https://bcr.bazel.build/modules/rules_python/0.37.1/MODULE.bazel": "3faeb2d9fa0a81f8980643ee33f212308f4d93eea4b9ce6f36d0b742e71e9500", - "https://bcr.bazel.build/modules/rules_python/0.37.2/MODULE.bazel": "b5ffde91410745750b6c13be1c5dc4555ef5bc50562af4a89fd77807fdde626a", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", "https://bcr.bazel.build/modules/rules_python/1.2.0/MODULE.bazel": "5aeeb48b2a6c19d668b48adf2b8a2b209a6310c230db0ce77450f148a89846e4", - "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", - "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", - "https://bcr.bazel.build/modules/rules_python/1.6.3/MODULE.bazel": "a7b80c42cb3de5ee2a5fa1abc119684593704fcd2fec83165ebe615dec76574f", "https://bcr.bazel.build/modules/rules_python/1.8.4/MODULE.bazel": "33e3971e66161a3e955f7a0d411a8d1f291c4ce4c561851512466f3c77ff8ece", "https://bcr.bazel.build/modules/rules_python/1.8.4/source.json": "9fbc0e57bae52cddcc3831d668bce87a47e0c655104a85098d4459dd9a3b0a10", - "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/MODULE.bazel": "d44fec647d0aeb67b9f3b980cf68ba634976f3ae7ccd6c07d790b59b87a4f251", - "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/source.json": "37c10335f2361c337c5c1f34ed36d2da70534c23088062b33a8bdaab68aa9dea", - "https://bcr.bazel.build/modules/rules_rust/0.51.0/MODULE.bazel": "2b6d1617ac8503bfdcc0e4520c20539d4bba3a691100bee01afe193ceb0310f9", "https://bcr.bazel.build/modules/rules_rust/0.67.0/MODULE.bazel": "87c3816c4321352dcfd9e9e26b58e84efc5b21351ae3ef8fb5d0d57bde7237f5", "https://bcr.bazel.build/modules/rules_rust/0.73.0/MODULE.bazel": "25e3b077128612754c4add1b4c90d20a6be06566b623dee6e32038d0e8f93062", "https://bcr.bazel.build/modules/rules_rust/0.73.0/source.json": "8eeb3d9ba7c57916b63887a651e8f84c2f68b7243af9e712d728c2a0b7882255", - "https://bcr.bazel.build/modules/rules_shell/0.1.2/MODULE.bazel": "66e4ca3ce084b04af0b9ff05ff14cab4e5df7503973818bb91cbc6cda08d32fc", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", @@ -464,11 +266,8 @@ "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", - "https://bcr.bazel.build/modules/rules_swift/1.18.0/MODULE.bazel": "a6aba73625d0dc64c7b4a1e831549b6e375fbddb9d2dde9d80c9de6ec45b24c9", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", - "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", - "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", - "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", "https://bcr.bazel.build/modules/stardoc/0.5.0/MODULE.bazel": "f9f1f46ba8d9c3362648eea571c6f9100680efc44913618811b58cc9c02cd678", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", @@ -480,8 +279,7 @@ "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", "https://bcr.bazel.build/modules/tar.bzl/0.5.1/source.json": "deed3094f7cc779ed1d37a68403847b0e38d9dd9d931e03cb90825f3368b515f", @@ -489,51 +287,17 @@ "https://bcr.bazel.build/modules/toolchain_utils/1.0.2/source.json": "88769ec576dddacafd8cca4631812cf8eead89f10a29d9405d9f7a553de6bf87", "https://bcr.bazel.build/modules/toolchains_musl/0.1.27.bcr.1/MODULE.bazel": "c5d1f5eb87305fefec90144c8b5677c917a3e5c212e045fd917e1637e59df39e", "https://bcr.bazel.build/modules/toolchains_musl/0.1.27.bcr.1/source.json": "f102c527042e7d213c4be481e479ea00b723edec385e9bf9df709d0b5be15ecb", - "https://bcr.bazel.build/modules/upb/0.0.0-20211020-160625a/MODULE.bazel": "6cced416be2dc5b9c05efd5b997049ba795e5e4e6fafbe1624f4587767638928", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", - "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9", - "https://bcr.bazel.build/modules/upb/0.0.0-20230907-e7430e6/MODULE.bazel": "3a7dedadf70346e678dc059dbe44d05cbf3ab17f1ce43a1c7a42edc7cbf93fd9", - "https://bcr.bazel.build/modules/xds/0.0.0-20240423-555b57e/MODULE.bazel": "cea509976a77e34131411684ef05a1d6ad194dd71a8d5816643bc5b0af16dc0f", - "https://bcr.bazel.build/modules/xds/0.0.0-20240423-555b57e/source.json": "7227e1fcad55f3f3cab1a08691ecd753cb29cc6380a47bc650851be9f9ad6d20", "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", "https://bcr.bazel.build/modules/yq.bzl/0.1.1/source.json": "2d2bad780a9f2b9195a4a370314d2c17ae95eaa745cefc2e12fbc49759b15aa3", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", - "https://bcr.bazel.build/modules/zlib/1.2.13/MODULE.bazel": "aa6deb1b83c18ffecd940c4119aff9567cd0a671d7bba756741cb2ef043a29d5", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.1/MODULE.bazel": "6a9fe6e3fc865715a7be9823ce694ceb01e364c35f7a846bf0d2b34762bc066b", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", - "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198", - "https://bcr.bazel.build/modules/zlib/1.3/MODULE.bazel": "6a9c02f19a24dcedb05572b2381446e27c272cd383aed11d41d99da9e3167a72" + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" }, "selectedYankedVersions": {}, "moduleExtensions": { - "//:extensions.bzl%non_module_repositories": { - "general": { - "bzlTransitiveDigest": "zCfz5Y/Y2oEPiKR8kMtWhPJvz8pncvnqG4M0okjVuwc=", - "usagesDigest": "VWX5RxPF3B7OH6yetiHcHSQ2sqPbT4/Te0I2lafZHdI=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "ktfmt": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "integrity": "sha256-l/x/vRlNAan6RdgUfAVSQDAD1VusSridhNe7TV4/SN4=", - "url": "https://repo1.maven.org/maven2/com/facebook/ktfmt/0.46/ktfmt-0.46-jar-with-dependencies.jar" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@aspect_rules_js+//npm:extensions.bzl%pnpm": { "general": { "bzlTransitiveDigest": "euYL3N7V/245TC8NJl5dm4Pq2GdagvtnpjGG8t9vBxY=", @@ -674,136 +438,89 @@ ] } }, - "@@envoy_api+//bazel:repositories.bzl%non_module_deps": { + "@@rules_buf+//buf:extensions.bzl%buf": { "general": { - "bzlTransitiveDigest": "GVR9ZmHmKQd+k6FY/jDmYTlwLU/H7IRoXDz3w4dqRdI=", - "usagesDigest": "lOhJkV09ITWn6LOK9fLMuf1t3969wdr45lToQ2MVQoU=", + "bzlTransitiveDigest": "dSWqckK2ILN7aDIDHfv+Qrl1fb1hF7o7MDXY6T8C41s=", + "usagesDigest": "vxN6C2h72rUERbAmd1476FWpxdxo1NhYoY5JSFXJT3g=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { - "prometheus_metrics_model": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "rules_buf_toolchains": { + "repoRuleId": "@@rules_buf+//buf/internal:toolchain.bzl%buf_download_releases", "attributes": { - "urls": [ - "https://github.com/prometheus/client_model/archive/v0.6.2.tar.gz" - ], - "sha256": "47c5ea7949f68e7f7b344350c59b6bd31eeb921f0eec6c3a566e27cf1951470c", - "strip_prefix": "client_model-0.6.2", - "build_file_content": "\nload(\"@envoy_api//bazel:api_build_system.bzl\", \"api_cc_py_proto_library\")\nload(\"@io_bazel_rules_go//proto:def.bzl\", \"go_proto_library\")\n\napi_cc_py_proto_library(\n name = \"client_model\",\n srcs = [\n \"io/prometheus/client/metrics.proto\",\n ],\n visibility = [\"//visibility:public\"],\n)\n\ngo_proto_library(\n name = \"client_model_go_proto\",\n importpath = \"github.com/prometheus/client_model/go\",\n proto = \":client_model\",\n visibility = [\"//visibility:public\"],\n)\n" + "version": "v1.47.2", + "sha256": "1b37b75dc0a777a0cba17fa2604bc9906e55bb4c578823d8b7a8fe3fc9fe4439" } } }, "recordedRepoMappingEntries": [ [ - "envoy_api+", + "rules_buf+", "bazel_tools", "bazel_tools" - ], - [ - "envoy_api+", - "envoy_api", - "envoy_api+" ] ] } }, - "@@googleapis+//:extensions.bzl%switched_rules": { + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "1ZTg77R3Ks/ksx8QMNIoTRJetbJPRlzGFlJa9hoUn+Y=", - "usagesDigest": "phuibqqjsm5RJqquWNJ7BXJvnPPgSCBreyRLEH/JjIA=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": {}, - "recordedRepoMappingEntries": [] - } - }, - "@@rules_android+//bzlmod_extensions:apksig.bzl%apksig_extension": { - "general": { - "bzlTransitiveDigest": "xeN+uPP1PKslnv5h3+uyhGaFXC4IIzcFwX/nQHlNwZk=", - "usagesDigest": "zr/niBQ/s2fHozWAsg4vI70wAxcuFjG+QtM15qGkq9o=", + "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { - "apksig": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", "attributes": { - "url": "https://android.googlesource.com/platform/tools/apksig/+archive/24e3075e68ebe17c0b529bb24bfda819db5e2f3b.tar.gz", - "build_file": "@@rules_android+//bzlmod_extensions:apksig.BUILD" + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_android+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_android+//bzlmod_extensions:com_android_dex.bzl%com_android_dex_extension": { - "general": { - "bzlTransitiveDigest": "2icqERrdWnpamgt/akEgAtfBsjNG1/st+AAFRZx9aH4=", - "usagesDigest": "c1Y/KGGjUYCyd8zNIVTUh1bynVXRFz6xGKaSCBpQANM=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "com_android_dex": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", "attributes": { - "url": "https://android.googlesource.com/platform/dalvik/+archive/5a81c499a569731e2395f7c8d13c0e0d4e17a2b6.tar.gz", - "build_file": "@@rules_android+//bzlmod_extensions:com_android_dex.BUILD" + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_android+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_android+//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": { - "general": { - "bzlTransitiveDigest": "+rMrzIrv7sImYmkbXJYv+gFpTJQ79X3MpwwMLI2A+oA=", - "usagesDigest": "iEGI2aNDMkHt9LXCdViLNUUOslpiVj2DrevWWXZEFnU=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "androidsdk": { - "repoRuleId": "@@rules_android+//rules/android_sdk_repository:rule.bzl%_android_sdk_repository", - "attributes": {} - } - }, - "recordedRepoMappingEntries": [] - } - }, - "@@rules_buf+//buf:extensions.bzl%buf": { - "general": { - "bzlTransitiveDigest": "dSWqckK2ILN7aDIDHfv+Qrl1fb1hF7o7MDXY6T8C41s=", - "usagesDigest": "vxN6C2h72rUERbAmd1476FWpxdxo1NhYoY5JSFXJT3g=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "rules_buf_toolchains": { - "repoRuleId": "@@rules_buf+//buf/internal:toolchain.bzl%buf_download_releases", + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", "attributes": { - "version": "v1.47.2", - "sha256": "1b37b75dc0a777a0cba17fa2604bc9906e55bb4c578823d8b7a8fe3fc9fe4439" + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] } } }, "recordedRepoMappingEntries": [ [ - "rules_buf+", + "rules_kotlin+", "bazel_tools", "bazel_tools" ] @@ -1047,80 +764,6 @@ "recordedRepoMappingEntries": [] } }, - "@@rules_perl+//perl:extensions.bzl%perl_repositories": { - "general": { - "bzlTransitiveDigest": "rEvFEEQbGp20qoG38ex+bV2NMG2GhUnSh3CZscNcozE=", - "usagesDigest": "qSSNDdCNVxNhY36wMndEAFacdhR0ooLTmumfad0km9s=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "perl_darwin_arm64": { - "repoRuleId": "@@rules_perl+//perl:repo.bzl%perl_download", - "attributes": { - "strip_prefix": "perl-darwin-arm64", - "sha256": "285769f3c50c339fb59a3987b216ae3c5c573b95babe6875a1ef56fb178433da", - "urls": [ - "https://github.com/skaji/relocatable-perl/releases/download/5.36.0.1/perl-darwin-arm64.tar.xz" - ] - } - }, - "perl_darwin_amd64": { - "repoRuleId": "@@rules_perl+//perl:repo.bzl%perl_download", - "attributes": { - "strip_prefix": "perl-darwin-amd64", - "sha256": "63bc5ee36f5394d71c50cca6cafdd333ee58f9eaa40bca63c85f9bd06f2c1fd6", - "urls": [ - "https://github.com/skaji/relocatable-perl/releases/download/5.36.0.1/perl-darwin-amd64.tar.xz" - ] - } - }, - "perl_linux_amd64": { - "repoRuleId": "@@rules_perl+//perl:repo.bzl%perl_download", - "attributes": { - "strip_prefix": "perl-linux-amd64", - "sha256": "3bdffa9d7a3f97c0207314637b260ba5115b1d0829f97e3e2e301191a4d4d076", - "urls": [ - "https://github.com/skaji/relocatable-perl/releases/download/5.36.0.1/perl-linux-amd64.tar.xz" - ] - } - }, - "perl_linux_arm64": { - "repoRuleId": "@@rules_perl+//perl:repo.bzl%perl_download", - "attributes": { - "strip_prefix": "perl-linux-arm64", - "sha256": "6fa4ece99e790ecbc2861f6ecb7b52694c01c2eeb215b4370f16a3b12d952117", - "urls": [ - "https://github.com/skaji/relocatable-perl/releases/download/5.36.0.1/perl-linux-arm64.tar.xz" - ] - } - }, - "perl_windows_x86_64": { - "repoRuleId": "@@rules_perl+//perl:repo.bzl%perl_download", - "attributes": { - "strip_prefix": "", - "sha256": "aeb973da474f14210d3e1a1f942dcf779e2ae7e71e4c535e6c53ebabe632cc98", - "urls": [ - "https://mirror.bazel.build/strawberryperl.com/download/5.32.1.1/strawberry-perl-5.32.1.1-64bit.zip", - "https://strawberryperl.com/download/5.32.1.1/strawberry-perl-5.32.1.1-64bit.zip" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_perl+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_perl+", - "rules_perl", - "rules_perl+" - ] - ] - } - }, "@@rules_python+//python/extensions:config.bzl%config": { "general": { "bzlTransitiveDigest": "EcMcbtKZvYmd5Mi1Fpg4EeBBztLHEE5tjO5tLDBYDuU=", @@ -1617,380 +1260,6 @@ }, "facts": { "@@rules_go+//go:extensions.bzl%go_sdk": { - "1.20.2": { - "darwin_amd64": [ - "go1.20.2.darwin-amd64.tar.gz", - "c93b8ced9517d07e1cd4c362c6e2d5242cb139e29b417a328fbf19aded08764c" - ], - "darwin_arm64": [ - "go1.20.2.darwin-arm64.tar.gz", - "7343c87f19e79c0063532e82e1c4d6f42175a32d99f7a4d15e658e88bf97f885" - ], - "freebsd_386": [ - "go1.20.2.freebsd-386.tar.gz", - "14f9be2004e042b3a64d0facb0c020756a9084a5c7333e33b0752b393b6016ea" - ], - "freebsd_amd64": [ - "go1.20.2.freebsd-amd64.tar.gz", - "b41b67b4f1b56797a7cecf6ee7f47fcf4f93960b2788a3683c07dd009d30b2a4" - ], - "linux_386": [ - "go1.20.2.linux-386.tar.gz", - "ee240ed33ae57504c41f04c12236aeaa17fbeb6ea9fcd096cd9dc7a89d10d4db" - ], - "linux_amd64": [ - "go1.20.2.linux-amd64.tar.gz", - "4eaea32f59cde4dc635fbc42161031d13e1c780b87097f4b4234cfce671f1768" - ], - "linux_arm64": [ - "go1.20.2.linux-arm64.tar.gz", - "78d632915bb75e9a6356a47a42625fd1a785c83a64a643fedd8f61e31b1b3bef" - ], - "linux_armv6l": [ - "go1.20.2.linux-armv6l.tar.gz", - "d79d56bafd6b52b8d8cbe3f8e967caaac5383a23d7a4fa9ac0e89778cd16a076" - ], - "linux_ppc64le": [ - "go1.20.2.linux-ppc64le.tar.gz", - "850564ddb760cb703db63bf20182dc4407abd2ff090a95fa66d6634d172fd095" - ], - "linux_s390x": [ - "go1.20.2.linux-s390x.tar.gz", - "8da24c5c4205fe8115f594237e5db7bcb1d23df67bc1fa9a999954b1976896e8" - ], - "windows_386": [ - "go1.20.2.windows-386.zip", - "31838b291117495bbb93683603e98d5118bfabd2eb318b4d07540bfd524bab86" - ], - "windows_amd64": [ - "go1.20.2.windows-amd64.zip", - "fe439f0e438f7555a7f5f7194ddb6f4a07b0de1fa414385d19f2aeb26d9f43db" - ], - "windows_arm64": [ - "go1.20.2.windows-arm64.zip", - "ac5010c8b8b22849228a8dea698d58b9c7be2195d30c6d778cce0f709858fa64" - ] - }, - "1.22.0": { - "aix_ppc64": [ - "go1.22.0.aix-ppc64.tar.gz", - "190e105fc4133a8b5bb1492f368fa89aa4b729270441120714be7ee82e871ebc" - ], - "darwin_amd64": [ - "go1.22.0.darwin-amd64.tar.gz", - "ebca81df938d2d1047cc992be6c6c759543cf309d401b86af38a6aed3d4090f4" - ], - "darwin_arm64": [ - "go1.22.0.darwin-arm64.tar.gz", - "bf8e388b09134164717cd52d3285a4ab3b68691b80515212da0e9f56f518fb1e" - ], - "dragonfly_amd64": [ - "go1.22.0.dragonfly-amd64.tar.gz", - "357ab446200effa26c73ccaf3e8551426428950bf2490859fb296a09e53228b1" - ], - "freebsd_386": [ - "go1.22.0.freebsd-386.tar.gz", - "b8065da37783e8b9e7086365a54d74537e832c92311b61101a66989ab2458d8e" - ], - "freebsd_amd64": [ - "go1.22.0.freebsd-amd64.tar.gz", - "50f421c7f217083ac94aab1e09400cb9c2fea7d337679ec11f1638a11460da30" - ], - "freebsd_arm64": [ - "go1.22.0.freebsd-arm64.tar.gz", - "e23385e5c640787fa02cd58f2301ea09e162c4d99f8ca9fa6d52766f428a933d" - ], - "freebsd_armv6l": [ - "go1.22.0.freebsd-arm.tar.gz", - "c9c8b305f90903536f4981bad9f029828c2483b3216ca1783777344fbe603f2d" - ], - "freebsd_riscv64": [ - "go1.22.0.freebsd-riscv64.tar.gz", - "c8f94d1de6024546194d58e7b9370dc7ea06176aad94a675b0062c25c40cb645" - ], - "illumos_amd64": [ - "go1.22.0.illumos-amd64.tar.gz", - "d6792f11ad6ee5fc42d2fe51e1f1683471aa2ee4f20e3ad1be22a4afdbd38e7f" - ], - "linux_386": [ - "go1.22.0.linux-386.tar.gz", - "1e209c4abde069067ac9afb341c8003db6a210f8173c77777f02d3a524313da3" - ], - "linux_amd64": [ - "go1.22.0.linux-amd64.tar.gz", - "f6c8a87aa03b92c4b0bf3d558e28ea03006eb29db78917daec5cfb6ec1046265" - ], - "linux_arm64": [ - "go1.22.0.linux-arm64.tar.gz", - "6a63fef0e050146f275bf02a0896badfe77c11b6f05499bb647e7bd613a45a10" - ], - "linux_armv6l": [ - "go1.22.0.linux-armv6l.tar.gz", - "0525f92f79df7ed5877147bce7b955f159f3962711b69faac66bc7121d36dcc4" - ], - "linux_loong64": [ - "go1.22.0.linux-loong64.tar.gz", - "b4b1d425cc113608452a32768469b6e34e538fd072bde9f508a75c8dbbdb843f" - ], - "linux_mips": [ - "go1.22.0.linux-mips.tar.gz", - "ec0f9194df750c78492c02e4c70ffe6f3864f2511e47c894415320db752719f8" - ], - "linux_mips64": [ - "go1.22.0.linux-mips64.tar.gz", - "47e938d215e4968ab42afb7307792e5e64184a717e8f176d0de7c411af96d63d" - ], - "linux_mips64le": [ - "go1.22.0.linux-mips64le.tar.gz", - "c162a1a516b8bc8839fc0c0283ad90f6b511e5aca3da4939ed4800f124b9e72c" - ], - "linux_mipsle": [ - "go1.22.0.linux-mipsle.tar.gz", - "6ce3e9a06e3a8ca0312dc1f85046b6914c19852eba5393c5cbf26acf698f8977" - ], - "linux_ppc64": [ - "go1.22.0.linux-ppc64.tar.gz", - "5ae23bf460731eb078f5700b01a50a303308b9f7174a2994892e28bf061f7c85" - ], - "linux_ppc64le": [ - "go1.22.0.linux-ppc64le.tar.gz", - "0e57f421df9449066f00155ce98a5be93744b3d81b00ee4c2c9b511be2a31d93" - ], - "linux_riscv64": [ - "go1.22.0.linux-riscv64.tar.gz", - "afe9cedcdbd6fdff27c57efd30aa5ce0f666f471fed5fa96cd4fb38d6b577086" - ], - "linux_s390x": [ - "go1.22.0.linux-s390x.tar.gz", - "2e546a3583ba7bd3988f8f476245698f6a93dfa9fe206a8ca8f85c1ceecb2446" - ], - "netbsd_386": [ - "go1.22.0.netbsd-386.tar.gz", - "9b7e2dbd43a169bff18bf533a6c2f462eebe04126ab01c138d0d669c046e1658" - ], - "netbsd_amd64": [ - "go1.22.0.netbsd-amd64.tar.gz", - "b11995c271d2256dfe85cf54882ca3655e18c49c4d7db0502bff9977767894e7" - ], - "netbsd_arm64": [ - "go1.22.0.netbsd-arm64.tar.gz", - "499918ccfedde78264d194244d251bf41d95cf606cc0adad13b36b8103cb974f" - ], - "netbsd_armv6l": [ - "go1.22.0.netbsd-arm.tar.gz", - "b57a3aa9c862300ec0ee8609ce5e0f430f132044f351677fd34711a504081872" - ], - "openbsd_386": [ - "go1.22.0.openbsd-386.tar.gz", - "379e0829104c18a139d78b33378e6bd3ab2e0792f6c43b4c38e7f20d8d77b99d" - ], - "openbsd_amd64": [ - "go1.22.0.openbsd-amd64.tar.gz", - "ceb0c97ffc3bfaf74e1df843cb8571d7fc3173a08432f0f42112495df6a31520" - ], - "openbsd_arm64": [ - "go1.22.0.openbsd-arm64.tar.gz", - "358801cab7122ab50d7d92727644f26d818e9e973403f09e85c2e935a625db75" - ], - "openbsd_armv6l": [ - "go1.22.0.openbsd-arm.tar.gz", - "8af5aea3df539bc95ed412c0a176fe84baced70ea1dd29f4aa82d0e9ce27fd9f" - ], - "plan9_386": [ - "go1.22.0.plan9-386.tar.gz", - "fa42c545c9025c45ca9af176dc13a0f4af0cc26bacff6fcb35bb4a170ac538e8" - ], - "plan9_amd64": [ - "go1.22.0.plan9-amd64.tar.gz", - "d8cf64f37a1dfd8e190c5a303c43ab2d49324868f098d88a3106072d137a5a0b" - ], - "plan9_armv6l": [ - "go1.22.0.plan9-arm.tar.gz", - "86fd6165f0cbb47ad551094f74b3e5a6c5e09de858d8b99de72d978d41be6e2a" - ], - "solaris_amd64": [ - "go1.22.0.solaris-amd64.tar.gz", - "a6c12651768d3a74f16104502b4b7bef513ea6b646d99990a28d934c261d1689" - ], - "windows_386": [ - "go1.22.0.windows-386.zip", - "553d44928509965cbda02a45b35ab01cf8b925534bc526a34e2d9dc7794b57e8" - ], - "windows_amd64": [ - "go1.22.0.windows-amd64.zip", - "78b3158fe3aa358e0b6c9f26ecd338f9a11441e88bc434ae2e9f0ca2b0cc4dd3" - ], - "windows_arm64": [ - "go1.22.0.windows-arm64.zip", - "31a61e41d06a3bb2189a303f5f3e777ca4b454eff439f0a67bc2b166330021f4" - ], - "windows_armv6l": [ - "go1.22.0.windows-arm.zip", - "495c7dfaea4e2bf48643662bb622e4ce6378d6d9840015238ad4b8792b99ddbf" - ] - }, - "1.22.4": { - "aix_ppc64": [ - "go1.22.4.aix-ppc64.tar.gz", - "b9647fa9fc83a0cc5d4f092a19eaeaecf45f063a5aa7d4962fde65aeb7ae6ce1" - ], - "darwin_amd64": [ - "go1.22.4.darwin-amd64.tar.gz", - "c95967f50aa4ace34af0c236cbdb49a9a3e80ee2ad09d85775cb4462a5c19ed3" - ], - "darwin_arm64": [ - "go1.22.4.darwin-arm64.tar.gz", - "242b78dc4c8f3d5435d28a0d2cec9b4c1aa999b601fb8aa59fb4e5a1364bf827" - ], - "dragonfly_amd64": [ - "go1.22.4.dragonfly-amd64.tar.gz", - "f2fbb51af4719d3616efb482d6ed2b96579b474156f85a7ddc6f126764feec4b" - ], - "freebsd_386": [ - "go1.22.4.freebsd-386.tar.gz", - "7c54884bb9f274884651d41e61d1bc12738863ad1497e97ea19ad0e9aa6bf7b5" - ], - "freebsd_amd64": [ - "go1.22.4.freebsd-amd64.tar.gz", - "88d44500e1701dd35797619774d6dd51bf60f45a8338b0a82ddc018e4e63fb78" - ], - "freebsd_arm64": [ - "go1.22.4.freebsd-arm64.tar.gz", - "726dc093cf020277be45debf03c3b02b43c2efb3e2a5d4fba8f52579d65327dc" - ], - "freebsd_armv6l": [ - "go1.22.4.freebsd-arm.tar.gz", - "3d9efe47db142a22679aba46b1772e3900b0d87ae13bd2b3bc80dbf2ac0b2cd6" - ], - "freebsd_riscv64": [ - "go1.22.4.freebsd-riscv64.tar.gz", - "5f6b67e5e32f1d6ccb2d4dcb44934a5e2e870a877ba7443d86ec43cfc28afa71" - ], - "illumos_amd64": [ - "go1.22.4.illumos-amd64.tar.gz", - "d56ecc2f85b6418a21ef83879594d0c42ab4f65391a676bb12254870e6690d63" - ], - "linux_386": [ - "go1.22.4.linux-386.tar.gz", - "47a2a8d249a91eb8605c33bceec63aedda0441a43eac47b4721e3975ff916cec" - ], - "linux_amd64": [ - "go1.22.4.linux-amd64.tar.gz", - "ba79d4526102575196273416239cca418a651e049c2b099f3159db85e7bade7d" - ], - "linux_arm64": [ - "go1.22.4.linux-arm64.tar.gz", - "a8e177c354d2e4a1b61020aca3562e27ea3e8f8247eca3170e3fa1e0c2f9e771" - ], - "linux_armv6l": [ - "go1.22.4.linux-armv6l.tar.gz", - "e2b143fbacbc9cbd448e9ef41ac3981f0488ce849af1cf37e2341d09670661de" - ], - "linux_loong64": [ - "go1.22.4.linux-loong64.tar.gz", - "e2ff9436e4b34bf6926b06d97916e26d67a909a2effec17967245900f0816f1d" - ], - "linux_mips": [ - "go1.22.4.linux-mips.tar.gz", - "73f0dcc60458c4770593b05a7bc01cc0d31fc98f948c0c2334812c7a1f2fc3f1" - ], - "linux_mips64": [ - "go1.22.4.linux-mips64.tar.gz", - "417af97fc2630a647052375768be4c38adcc5af946352ea5b28613ea81ca5d45" - ], - "linux_mips64le": [ - "go1.22.4.linux-mips64le.tar.gz", - "7486e2d7dd8c98eb44df815ace35a7fe7f30b7c02326e3741bd934077508139b" - ], - "linux_mipsle": [ - "go1.22.4.linux-mipsle.tar.gz", - "69479c8aad301e459a8365b40cad1074a0dbba5defb9291669f94809c4c4be6e" - ], - "linux_ppc64": [ - "go1.22.4.linux-ppc64.tar.gz", - "dd238847e65bc3e2745caca475a5db6522a2fcf85cf6c38fc36a06642b19efd7" - ], - "linux_ppc64le": [ - "go1.22.4.linux-ppc64le.tar.gz", - "a3e5834657ef92523f570f798fed42f1f87bc18222a16815ec76b84169649ec4" - ], - "linux_riscv64": [ - "go1.22.4.linux-riscv64.tar.gz", - "56a827ff7dc6245bcd7a1e9288dffaa1d8b0fd7468562264c1523daf3b4f1b4a" - ], - "linux_s390x": [ - "go1.22.4.linux-s390x.tar.gz", - "7590c3e278e2dc6040aae0a39da3ca1eb2e3921673a7304cc34d588c45889eec" - ], - "netbsd_386": [ - "go1.22.4.netbsd-386.tar.gz", - "ddd2eebe34471a2502de6c5dad04ab27c9fc80cbde7a9ad5b3c66ecec4504e1d" - ], - "netbsd_amd64": [ - "go1.22.4.netbsd-amd64.tar.gz", - "33af79f6f935f6fbacc5d23876450b3567b79348fc065beef8e64081127dd234" - ], - "netbsd_arm64": [ - "go1.22.4.netbsd-arm64.tar.gz", - "c9a2971dec9f6d320c6f2b049b2353c6d0a2d35e87b8a4b2d78a2f0d62545f8e" - ], - "netbsd_armv6l": [ - "go1.22.4.netbsd-arm.tar.gz", - "fa3550ebd5375a70b3bcd342b5a71f4bd271dcbbfaf4eabefa2144ab5d8924b6" - ], - "openbsd_386": [ - "go1.22.4.openbsd-386.tar.gz", - "d21af022331bfdc2b5b161d616c3a1a4573d33cf7a30416ee509a8f3641deb47" - ], - "openbsd_amd64": [ - "go1.22.4.openbsd-amd64.tar.gz", - "72c0094c43f7e5722ec49c2a3e9dfa7a1123ac43a5f3a63eecf3e3795d3ff0ae" - ], - "openbsd_arm64": [ - "go1.22.4.openbsd-arm64.tar.gz", - "a7ab8d4e0b02bf06ed144ba42c61c0e93ee00f2b433415dfd4ad4b6e79f31650" - ], - "openbsd_armv6l": [ - "go1.22.4.openbsd-arm.tar.gz", - "1096831ea3c5ea3ca57d14251d9eda3786889531eb40d7d6775dcaa324d4b065" - ], - "openbsd_ppc64": [ - "go1.22.4.openbsd-ppc64.tar.gz", - "9716327c8a628358798898dc5148c49dbbeb5196bf2cbf088e550721a6e4f60b" - ], - "plan9_386": [ - "go1.22.4.plan9-386.tar.gz", - "a8dd4503c95c32a502a616ab78870a19889c9325fe9bd31eb16dd69346e4bfa8" - ], - "plan9_amd64": [ - "go1.22.4.plan9-amd64.tar.gz", - "5423a25808d76fe5aca8607a2e5ac5673abf45446b168cb5e9d8519ee9fe39a1" - ], - "plan9_armv6l": [ - "go1.22.4.plan9-arm.tar.gz", - "6af939ad583f5c85c09c53728ab7d38c3cc2b39167562d6c18a07c5c6608b370" - ], - "solaris_amd64": [ - "go1.22.4.solaris-amd64.tar.gz", - "e8cabe69c03085725afdb32a6f9998191a3e55a747b270d835fd05000d56abba" - ], - "windows_386": [ - "go1.22.4.windows-386.zip", - "aca4e2c37278a10f1c70dd0df142f7d66b50334fcee48978d409202d308d6d25" - ], - "windows_amd64": [ - "go1.22.4.windows-amd64.zip", - "26321c4d945a0035d8a5bc4a1965b0df401ff8ceac66ce2daadabf9030419a98" - ], - "windows_arm64": [ - "go1.22.4.windows-arm64.zip", - "8a2daa9ea28cbdafddc6171aefed384f4e5b6e714fb52116fe9ed25a132f37ed" - ], - "windows_armv6l": [ - "go1.22.4.windows-arm.zip", - "5fcd0671a49cecf39b41021621ee1b6e7aa1370f37122b72e80d4fd4185833b6" - ] - }, "1.23.1": { "aix_ppc64": [ "go1.23.1.aix-ppc64.tar.gz", @@ -2157,168 +1426,6 @@ "64ad0954d2c33f556fb1018d62de091254aa6e3a94f1c8a8b16af0d3701d194e" ] }, - "1.24.0": { - "aix_ppc64": [ - "go1.24.0.aix-ppc64.tar.gz", - "5d04588154d5923bd8e26b76111806340ec55c41af1b05623ea744fcb3d6bc22" - ], - "darwin_amd64": [ - "go1.24.0.darwin-amd64.tar.gz", - "7af054e5088b68c24b3d6e135e5ca8d91bbd5a05cb7f7f0187367b3e6e9e05ee" - ], - "darwin_arm64": [ - "go1.24.0.darwin-arm64.tar.gz", - "fd9cfb5dd6c75a347cfc641a253f0db1cebaca16b0dd37965351c6184ba595e4" - ], - "dragonfly_amd64": [ - "go1.24.0.dragonfly-amd64.tar.gz", - "d0dc34ad86aea746abe245994c68a9e1ad8f46ba8c4af901cd5861a4dd4c21df" - ], - "freebsd_386": [ - "go1.24.0.freebsd-386.tar.gz", - "4ee02b1f3812aff4da79c79464ee4038ca61ad74b3a9619850f30435f81c2536" - ], - "freebsd_amd64": [ - "go1.24.0.freebsd-amd64.tar.gz", - "838191001f9324da904dece35a586a3156d548687db87ac9461aa3d38fc88b09" - ], - "freebsd_arm": [ - "go1.24.0.freebsd-arm.tar.gz", - "ce6ad4e84a40a8a1d848b7e31b0cddfd1cee8f7959e7dc358a8fa8b5566ea718" - ], - "freebsd_arm64": [ - "go1.24.0.freebsd-arm64.tar.gz", - "511f7b0cac4c4ed1066d324072ce223b906ad6b2a85f2e1c5d260eb7d08b5901" - ], - "freebsd_riscv64": [ - "go1.24.0.freebsd-riscv64.tar.gz", - "a1e4072630dc589a2975ef51317b52c7d8599bf6f389fc59033c01e0a0fa705a" - ], - "illumos_amd64": [ - "go1.24.0.illumos-amd64.tar.gz", - "7593e9dcee9f07c3df6d099f7d259f5734a6c0dccc5f28962f18e7f501c9bb21" - ], - "linux_386": [ - "go1.24.0.linux-386.tar.gz", - "90521453a59c6ce20364d2dc7c38532949b033b602ba12d782caeb90af1b0624" - ], - "linux_amd64": [ - "go1.24.0.linux-amd64.tar.gz", - "dea9ca38a0b852a74e81c26134671af7c0fbe65d81b0dc1c5bfe22cf7d4c8858" - ], - "linux_arm64": [ - "go1.24.0.linux-arm64.tar.gz", - "c3fa6d16ffa261091a5617145553c71d21435ce547e44cc6dfb7470865527cc7" - ], - "linux_armv6l": [ - "go1.24.0.linux-armv6l.tar.gz", - "695dc54fa14cd3124fa6900d7b5ae39eeac23f7a4ecea81656070160fac2c54a" - ], - "linux_loong64": [ - "go1.24.0.linux-loong64.tar.gz", - "a201e4c9b7e6d29ed64c43296ed88e81a66f82f2093ce45b766d2c526941396f" - ], - "linux_mips": [ - "go1.24.0.linux-mips.tar.gz", - "f3ac039aae78ad0bfb08106406c2e62eaf763dd82ebaf0ecd539adadd1d729a6" - ], - "linux_mips64": [ - "go1.24.0.linux-mips64.tar.gz", - "f2e6456d45e024831b1da8d88b1bb6392cca9500c1b00841f525d76c9e9553e0" - ], - "linux_mips64le": [ - "go1.24.0.linux-mips64le.tar.gz", - "b847893ff119389c939adc2b8516b6500204b7cb49d5e19b25e1c2091d2c74c6" - ], - "linux_mipsle": [ - "go1.24.0.linux-mipsle.tar.gz", - "bd4aed27d02746c237c3921e97029ac6b6fe687a67436b8f52ff1f698d330bd9" - ], - "linux_ppc64": [ - "go1.24.0.linux-ppc64.tar.gz", - "007123c9b06c41729a4bb3f166f4df7196adf4e33c2d2ab0e7e990175f0ce1d4" - ], - "linux_ppc64le": [ - "go1.24.0.linux-ppc64le.tar.gz", - "a871a43de7d26c91dd90cb6e0adacb214c9e35ee2188c617c91c08c017efe81a" - ], - "linux_riscv64": [ - "go1.24.0.linux-riscv64.tar.gz", - "620dcf48c6297519aad6c81f8e344926dc0ab09a2a79f1e306964aece95a553d" - ], - "linux_s390x": [ - "go1.24.0.linux-s390x.tar.gz", - "544d78b077c6b54bf78958c4a8285abec2d21f668fb007261c77418cd2edbb46" - ], - "netbsd_386": [ - "go1.24.0.netbsd-386.tar.gz", - "8b143a7edefbaa2a0b0246c9df2df1bac9fbed909d8615a375c08da7744e697d" - ], - "netbsd_amd64": [ - "go1.24.0.netbsd-amd64.tar.gz", - "67150a6dd7bdb9c4e88d77f46ee8c4dc99d5e71deca4912d8c2c85f7a16d0262" - ], - "netbsd_arm": [ - "go1.24.0.netbsd-arm.tar.gz", - "446b2539f11218fd6f6f6e3dd90b20ae55a06afe129885eeb3df51eb344eb0f6" - ], - "netbsd_arm64": [ - "go1.24.0.netbsd-arm64.tar.gz", - "370115b6ff7d30b29431223de348eb11ab65e3c92627532d97fd55f63f94e7a8" - ], - "openbsd_386": [ - "go1.24.0.openbsd-386.tar.gz", - "cbda5f15f06ed9630f122a53542d9de13d149643633c74f1dcb45e79649b788a" - ], - "openbsd_amd64": [ - "go1.24.0.openbsd-amd64.tar.gz", - "926f601d0e655ab1e8d7f357fd82542e5cf206c38c4e2f9fccf0706987d38836" - ], - "openbsd_arm": [ - "go1.24.0.openbsd-arm.tar.gz", - "8a54892f8c933c541fff144a825d0fdc41bae14b0832aab703cb75eb4cb64f2c" - ], - "openbsd_arm64": [ - "go1.24.0.openbsd-arm64.tar.gz", - "ef7fddcef0a22c7900c178b7687cf5aa25c2a9d46a3cc330b77a6de6e6c2396b" - ], - "openbsd_ppc64": [ - "go1.24.0.openbsd-ppc64.tar.gz", - "b3b5e2e2b53489ded2c2c21900ddcbbdb7991632bb5b42f05f125d71675e0b76" - ], - "openbsd_riscv64": [ - "go1.24.0.openbsd-riscv64.tar.gz", - "fbcb1dbf1269b4079dc4fd0b15f3274b9d635f1a7e319c3fc1a907b03280348e" - ], - "plan9_386": [ - "go1.24.0.plan9-386.tar.gz", - "33b4221e1c174a16e3f661deab6c60838ac4ae6cb869a4da1d1115773ceed88b" - ], - "plan9_amd64": [ - "go1.24.0.plan9-amd64.tar.gz", - "111a89014019cdbd69c2978de9b3e201f77e35183c8ab3606fba339d38f28549" - ], - "plan9_arm": [ - "go1.24.0.plan9-arm.tar.gz", - "8da3d3997049f40ebe0cd336a9bb9e4bfa4832df3c90a32f07383371d6d74849" - ], - "solaris_amd64": [ - "go1.24.0.solaris-amd64.tar.gz", - "b6069da21dc95ccdbd047675b584e5480ffc3eba35f9e7c8b0e7b317aaf01e2c" - ], - "windows_386": [ - "go1.24.0.windows-386.zip", - "b53c28a4c2863ec50ab4a1dbebe818ef6177f86773b6f43475d40a5d9aa4ec9e" - ], - "windows_amd64": [ - "go1.24.0.windows-amd64.zip", - "96b7280979205813759ee6947be7e3bb497da85c482711116c00522e3bb41ff1" - ], - "windows_arm64": [ - "go1.24.0.windows-arm64.zip", - "53f73450fb66075d16be9f206e9177bd972b528168271918c4747903b5596c3d" - ] - }, "1.25.0": { "aix_ppc64": [ "go1.25.0.aix-ppc64.tar.gz", diff --git a/Makefile b/Makefile index de647a5e..a9a3f719 100644 --- a/Makefile +++ b/Makefile @@ -8,56 +8,47 @@ release_source_archive: .github/workflows/pack_release_archive.sh archives/release.tar.gz -.PHONY: release_deploy_jar -release_deploy_jar: - bazel \ - build \ - //cli:bazel-diff_deploy.jar \ - -c opt - # Builds the same artifact CI publishes, named the same way: # bazel-bin/release/bazel-diff-rust--[.exe]. -.PHONY: release_rust_binary -release_rust_binary: +.PHONY: release_binary +release_binary: bazel \ build \ - //release:bazel-diff-rust \ + //release:bazel-diff \ --config=release # The published Linux binaries, which are not host-native: they are statically # linked against musl so they run on any distribution, and cross-compile from a # glibc Linux host or an Apple Silicon Mac. Same output path and asset name. -.PHONY: release_rust_binary_linux -release_rust_binary_linux: +.PHONY: release_binary_linux +release_binary_linux: bazel \ build \ - //release:bazel-diff-rust \ + //release:bazel-diff \ --config=release-musl -.PHONY: release_rust_binary_linux_arm64 -release_rust_binary_linux_arm64: +.PHONY: release_binary_linux_arm64 +release_binary_linux_arm64: bazel \ build \ - //release:bazel-diff-rust \ + //release:bazel-diff \ --config=release-musl-arm64 -.PHONY: build_rust -build_rust: - bazel build //:bazel-diff-rust -c opt +.PHONY: build +build: + bazel build //:bazel-diff -c opt -# Both go through Bazel so they use the same formatters CI gates on. `cargo fmt -# --all` is not equivalent: it only sees the root crate, missing tools/coverage, -# and it uses whatever rustfmt is on PATH rather than the pinned one. +# Goes through Bazel so it uses the same rustfmt CI gates on. `cargo fmt --all` +# is not equivalent: it only sees the root crate, missing tools/coverage, and it +# uses whatever rustfmt is on PATH rather than the pinned one. .PHONY: format format: - bazel run //cli/format - bazel run //cli/format:rustfmt + bazel run //tools/format:rustfmt # Regenerates the per-case e2e test targets from the e2e sources. Run it after -# adding, renaming or removing a `@Test` method under -# cli/src/test/kotlin/com/bazel_diff/e2e/ or a `#[test]` fn under tests/e2e/, -# and commit the result -- `e2e-split-regen` in ci.yaml fails the build if the -# checked-in split is stale. See tools/e2e/README.md. +# adding, renaming or removing a `#[test]` fn under tests/e2e/, and commit the +# result -- `e2e-split-regen` in ci.yaml fails the build if the checked-in +# split is stale. See tools/e2e/README.md. .PHONY: regen-e2e regen-e2e: bazel run //tools/e2e:regen @@ -73,7 +64,7 @@ generate-readme: .PHONY: coverage coverage: - bazel coverage --combined_report=lcov //cli/... //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/... + bazel coverage --combined_report=lcov //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/... bazel run //tools:coverage-check -- bazel-out/_coverage/_coverage_report.dat bazel run //tools:coverage-check -- --include tools/go/ --threshold 90 bazel-out/_coverage/_coverage_report.dat @@ -88,42 +79,6 @@ coverage-test: .PHONY: coverage-html coverage-html: - bazel coverage --combined_report=lcov //cli/... //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/... + bazel coverage --combined_report=lcov //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/... bazel run //tools:coverage-check -- bazel-out/_coverage/_coverage_report.dat --html coverage-html @echo "Open coverage-html/index.html in a browser to inspect." - -.PHONY: coverage_rust -coverage_rust: - bazel coverage //src:cli_tests //src:rust_tests - -.PHONY: benchmark -benchmark: - @test -n "$(WORKSPACE)" || (echo "usage: make benchmark WORKSPACE=/path/to/bazel [BAZEL=/path/to/bazelisk] [HYPERFINE=/path/to/hyperfine] [STREAMED_PROTO=/path/to/targets.pb] [INCLUDE_BAZEL=1] [ITERATIONS=10] [WARMUP=3] [RSS_RUNS=5] [JSON=benchmark.json]" >&2; exit 2) - $(or $(BAZEL),bazel) run -c opt //tools:benchmark -- \ - --workspace "$(WORKSPACE)" \ - --bazel "$(or $(BAZEL),bazel)" \ - --hyperfine "$(or $(HYPERFINE),hyperfine)" \ - --iterations "$(or $(ITERATIONS),10)" \ - --warmup "$(or $(WARMUP),3)" \ - --rss-runs "$(or $(RSS_RUNS),5)" \ - $(if $(STREAMED_PROTO),--streamed-proto "$(STREAMED_PROTO)",) \ - $(if $(INCLUDE_BAZEL),--include-bazel,) \ - $(if $(JSON),--json "$(JSON)",) - -# Hermetic Kotlin-vs-Rust performance gate. Unlike `make benchmark` this needs no -# workspace, no Bazel server and no Hyperfine: it generates its own fixtures and fails -# (exit 1) if Rust is not faster than Kotlin on every workload. JSON=... must be an -# absolute path -- `bazel run` executes from the runfiles tree, not the repo root. -.PHONY: perf-gate -perf-gate: - $(or $(BAZEL),bazel) run -c opt //tools:perf-gate -- \ - --rounds "$(or $(ROUNDS),5)" \ - --warmup-rounds "$(or $(WARMUP),1)" \ - --scale "$(or $(SCALE),1)" \ - $(if $(WORKLOAD),--workload "$(WORKLOAD)",) \ - $(if $(RSS_RUNS),--rss-runs "$(RSS_RUNS)",) \ - $(if $(JSON),--json "$(JSON)",) - -.PHONY: perf-gate-test -perf-gate-test: - $(or $(BAZEL),bazel) test //tools:perf_gate_test diff --git a/README.md b/README.md index 402352e3..09c61414 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ `bazel-diff` offers several key advantages over rolling your own target diffing solution -1. `bazel-diff` is designed for very large Bazel projects. We use Java Protobuf's `parseDelimitedFrom` method alongside Bazel Query's `streamed_proto` output option. These two together allow you to parse Gigabyte or larger protobuf messages. We have tested it with projects containing tens of thousands of targets. +1. `bazel-diff` is designed for very large Bazel projects. We stream Bazel Query's `streamed_proto` output one length-delimited message at a time instead of materialising the whole result, which allows you to parse Gigabyte or larger protobuf messages. We have tested it with projects containing tens of thousands of targets. 2. We avoid usage of large command line query lists when interacting with Bazel, [issue here](https://github.com/bazelbuild/bazel/issues/8609). When you interact with Bazel with thousands of query parameters you can reach an upper maximum limit, seeing this error `bash: /usr/local/bin/bazel: Argument list too long`. `bazel-diff` is smart enough to avoid these errors. 3. `bazel-diff` has been tested with file renames, deletions, and modifications. Works on `bzl` files, `WORKSPACE` files, `BUILD` files and regular files @@ -26,7 +26,9 @@ This approach was inspired by the [following BazelConf talk](https://www.youtube * Git * Bazel 3.3.0 or higher -* Java 8 JDK or higher (Bazel requires this) + +`bazel-diff` itself is a single statically linked binary with no runtime +dependencies. ## Getting Started @@ -304,412 +306,195 @@ Notes and current limitations: `bazel-diff` Command ```terminal -Usage: bazel-diff [-hvV] [COMMAND] -Writes to a file the impacted targets between two Bazel graph JSON files - -h, --help Show this help message and exit. - -v, --verbose Display query string, missing files and elapsed time - -V, --version Print version information and exit. +Writes impacted targets between two Bazel graph hash files + +Usage: bazel-diff [OPTIONS] + Commands: - generate-hashes Writes to a file the SHA256 hashes for each Bazel - Target in the provided workspace. - get-impacted-targets Command-line utility to analyze the state of the bazel - build graph - warmup Record-side entrypoint for Firecracker snapshots: runs - generate-hashes for the base revision, writes base - hashes + fingerprint to known paths, and exits 0 only - once the Bazel server is warm (the host's 'safe to - snapshot' signal). - fingerprint Computes the snapshot cache key over the inputs that - affect the build graph (bazel version, MODULE.bazel. - lock, .bazelrc, bazel-diff version, flag set) and - writes it as JSON. Used to decide whether a - Firecracker snapshot is safe to consume. - serve Runs bazel-diff as a long-running HTTP query service - that returns the impacted targets between two git - revisions, caching generated hashes per commit SHA. + generate-hashes Write canonical hashes for Bazel targets in a workspace + get-impacted-targets Compare two hash files and report impacted targets + warmup Warm Bazel and write snapshot hashes and fingerprint metadata + fingerprint Compute the snapshot/cache fingerprint for the current workspace + serve Run the HTTP impacted-target query service + help Print this message or the help of the given subcommand(s) + +Options: + -v, --verbose + -h, --help Print help + -V, --version Print version ``` ### `generate-hashes` command ```terminal -Usage: bazel-diff generate-hashes [-hkvV] [--[no-]excludeExternalTargets] [-- - [no-]includeTargetType] [--[no-]useCquery] - [-b=] - [--contentHashPath=] - [--cqueryExpression=] - [-d=] - [--excludeTargetsQuery=] - [--fineGrainedHashExternalReposFile=] - [-m=] [-s=] - -w= - [--alwaysAffectedTags=]... - [-co=]... - [--cqueryCommandOptions= - ]... - [--fineGrainedHashExternalRepos=]... - [--ignoredRuleHashingAttributes=]... - [-so=]... - [-tt=[,...]]... - -Writes to a file the SHA256 hashes for each Bazel Target in the provided -workspace. - The filepath to write the resulting JSON of - dictionary target => SHA-256 values. If not - specified, the JSON will be written to STDOUT. - --alwaysAffectedTags= - Comma separated list of Bazel target tags (e.g. - `external`). Any target whose `tags` attribute - contains one of these values is always reported as - impacted: a per-invocation sentinel is mixed into - its hash so a diff of two `generate-hashes` runs - always marks it changed. Use this for non-hermetic - targets that read undeclared workspace state at - execution time (e.g. repo-scanning linters such as - buildifier/gofmt/eslint tests) which would - otherwise hash as unchanged and be wrongly skipped - by target determination. This is the Bazel target - `external` *tag* and is unrelated to - --excludeExternalTargets / - --fineGrainedHashExternalRepos, which concern - external *repositories*. - -b, --bazelPath= - Path to Bazel binary. If not specified, the Bazel - binary available in PATH will be used. - -co, --bazelCommandOptions= - Additional space separated Bazel command options used - when invoking `bazel query` - --contentHashPath= - Path to content hash json file. It's a map which maps - a workspace-relative file path (and - `external//` for fine-grained - external repo files) to its content hash. Files in - this map will skip content hashing and use provided - value - --cqueryCommandOptions= - Additional space separated Bazel command options used - when invoking `bazel cquery`. This flag is has no - effect if `--useCquery`is false. - --cqueryExpression= - 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. - -d, --depEdgesFile= - Path to the file where dependency edges are written - to. If not specified, the dependency edges will not - be written to a file. Needed for computing build - graph distance metrics. See bazel-diff docs for - more details about build graph distance metrics. - --[no-]excludeExternalTargets - If true, exclude external targets (do not query - //external:all-targets). When Bzlmod is enabled - (detected via bazel mod graph), external targets - are excluded automatically. Set this when using - Bazel with --enable_workspace=false in other - configurations. Defaults to false. - --excludeTargetsQuery= - A Bazel query expression whose matched targets are - excluded from the generated hashes via the `except` - operator. Applied to the main target universe for - both `query` and `cquery`. Use this to drop targets - you never want reported as impacted, e.g. - `manual`-tagged targets: --excludeTargetsQuery='attr - ("tags", "[\[ ]manual[,\]]", //...)'. Excluded - targets are absent from hashing, so a kept target - that depended on one no longer tracks changes to it. - --fineGrainedHashExternalRepos= - Comma separate list of external repos in which - fine-grained hashes are computed for the targets. - By default, external repos are treated as an opaque - blob. If an external repo is specified here, - bazel-diff instead computes the hash for individual - targets. For example, one wants to specify `maven` - here if they user rules_jvm_external so that - individual third party dependency change won't - invalidate all targets in the mono repo. - --fineGrainedHashExternalReposFile= - A text file containing a newline separated list of - external repos. Similar to - --fineGrainedHashExternalRepos but helps you avoid - exceeding max arg length. Mutually exclusive with - --fineGrainedHashExternalRepos. - -h, --help Show this help message and exit. - --ignoredRuleHashingAttributes= - Attributes that should be ignored when hashing rule - targets. - --[no-]includeTargetType - Whether include target type in the generated JSON or - not. - If false, the generate JSON schema is: {"": - ""} - If true, the generate JSON schema is: {"": - "#" } - -k, --[no-]keep_going This flag controls if `bazel query` will be executed - with the `--keep_going` flag or not. Enabling this - flag lets `bazel query` tolerate failures in your - Bazel graph, but may silently drop targets that - fail to resolve and produce non-deterministic - hashes. Disabling it catches configuration issues - by failing loudly. Defaults to `false` - -m, --modified-filepaths= - Experimental: A text file containing a newline - separated list of filepaths (relative to the - workspace) these filepaths should represent the - modified files between the specified revisions and - will be used to scope what files are hashed during - hash generation. - -s, --seed-filepaths= - A text file containing a newline separated list of - filepaths. Each file in this list will be read and - its content will be used as a SHA256 seed when - determining affected targets in the build graph. - Invalidating any of these files will effectively - mark all targets as affected. - -so, --bazelStartupOptions= - Additional space separated Bazel client startup - options used when invoking Bazel - -tt, --targetType=[,...] - The types of targets to filter. Use comma (,) to - separate multiple values, e.g. - '--targetType=SourceFile,Rule,GeneratedFile'. - --[no-]useCquery If true, use cquery instead of query when generating - dependency graphs. Using cquery would yield more - accurate build graph at the cost of slower query - execution. When this is set, one usually also wants - to set `--cqueryCommandOptions` to specify a - targeting platform. Note that this flag only works - with Bazel 6.2.0 or above because lower versions - does not support `--query_file` flag. - -v, --verbose Display query string, missing files and elapsed time - -V, --version Print version information and exit. - -w, --workspacePath= - Path to Bazel workspace directory. +Write canonical hashes for Bazel targets in a workspace + +Usage: bazel-diff generate-hashes [OPTIONS] --workspacePath [OUTPUT_PATH] + +Arguments: + [OUTPUT_PATH] + +Options: + -v, --verbose + + -w, --workspacePath + Path to the Bazel workspace + -b, --bazelPath + Path to the Bazel or Bazelisk executable [default: bazel] + -s, --seed-filepaths + File containing workspace-relative paths whose contents seed every target hash + --bazelStartupOptions + Additional space-separated Bazel startup options + --bazelCommandOptions + Additional space-separated `bazel query` options + --cqueryCommandOptions + Additional space-separated `bazel cquery` options + --fineGrainedHashExternalRepos + + --fineGrainedHashExternalReposFile + + --useCquery[=] + [default: false] [possible values: true, false] + --cqueryExpression + + -k, --keep_going[=] + [default: false] [possible values: true, false] + --ignoredRuleHashingAttributes + + --excludeExternalTargets[=] + [default: false] [possible values: true, false] + --excludeTargetsQuery + + --alwaysAffectedTags + + --contentHashPath + + --includeTargetType[=] + [default: false] [possible values: true, false] + --targetType + + -d, --depEdgesFile + + -m, --modified-filepaths + + -h, --help + Print help + -V, --version + Print version ``` ### `get-impacted-targets` command ```terminal -Missing required options: '--startingHashes=', '--finalHashes=', '--workspacePath=' -Usage: bazel-diff get-impacted-targets [-v] [--[no-]excludeExternalTargets] [-- - [no-]noBazelrc] [-b=] - [-d=] - -fh= - [-o=] - -sh= - -w= - [-so=]... - [-tt=[,...]]... -Command-line utility to analyze the state of the bazel build graph - -b, --bazelPath= - Path to Bazel binary. If not specified, the Bazel - binary available in PATH will be used. - -d, --depEdgesFile= - Path to the file where dependency edges are. If - specified, build graph distance metrics will be - computed from the given hash data. - --[no-]excludeExternalTargets - If true, drop labels starting with '//external:' from - the impacted-targets output. These synthetic labels - are produced for bzlmod-managed external repos so - generate-hashes can detect dep changes, but they are - not buildable in bzlmod-only mode (Bazel 8.6.0+ with - --enable_workspace=false) and will fail downstream - `bazel build`. See https://github. - com/Tinder/bazel-diff/issues/326. When unset, - defaults to true if Bzlmod is detected (via `bazel - mod graph`), false otherwise. - -fh, --finalHashes= - The path to the JSON file of target hashes for the - final revision. Run 'generate-hashes' to get this - value. - --[no-]noBazelrc Don't use .bazelrc - -o, --output= - Filepath to write the impacted Bazel targets to. If - using depEdgesFile: formatted in json, otherwise: - newline separated. If not specified, the output will - be written to STDOUT. - -sh, --startingHashes= - The path to the JSON file of target hashes for the - initial revision. Run 'generate-hashes' to get this - value. - -so, --bazelStartupOptions= - Additional space separated Bazel client startup - options used when invoking Bazel - -tt, --targetType=[,...] - The types of targets to filter. Use comma (,) to - separate multiple values, e.g. - '--targetType=SourceFile,Rule,GeneratedFile'. - -v, --verbose Display query string, missing files and elapsed time - -w, --workspacePath= - Path to Bazel workspace directory. Required for module - change detection. +Compare two hash files and report impacted targets + +Usage: bazel-diff get-impacted-targets [OPTIONS] --startingHashes --finalHashes --workspacePath + +Options: + --startingHashes + + -v, --verbose + + --finalHashes + + -d, --depEdgesFile + + --targetType + + -o, --output + + -w, --workspacePath + + -b, --bazelPath + [default: bazel] + --bazelStartupOptions + + --noBazelrc[=] + [default: false] [possible values: true, false] + --excludeExternalTargets[=] + [possible values: true, false] + -h, --help + Print help + -V, --version + Print version ``` ### `serve` command ```terminal -Usage: bazel-diff serve [-hkvV] [--[no-]excludeExternalTargets] - [--no-initial-fetch] [--[no-]s3ForcePathStyle] [--[no-] - trackDeps] [--[no-]useCquery] [-b=] - --cacheDir= [--cacheMaxAge=] - [--cacheMaxEntries=] - [--cacheMaxSize=] - [--cachePruneInterval=] - [--cqueryExpression=] - [--excludeTargetsQuery=] - [--fineGrainedHashExternalReposFile=] [--gitPath=] [--port=] - [--portFile=] - [--requestTimeout=] - [-s=] [--s3Bucket=] - [--s3Endpoint=] [--s3Prefix=] - [--s3Region=] -w= - [-co=]... - [--cqueryCommandOptions=]... - [--fineGrainedHashExternalRepos=]... - [--ignoredRuleHashingAttributes=]... [-so=]... - [--warmupRevision=]... -Runs bazel-diff as a long-running HTTP query service that returns the impacted -targets between two git revisions, caching generated hashes per commit SHA. - -b, --bazelPath= - Path to Bazel binary. If not specified, the Bazel - binary available in PATH will be used. - --cacheDir= Directory where generated hashes are cached per - commit SHA. Persists across restarts. - --cacheMaxAge= - Evict cached hashes not read or written within this - window, so the cache does not grow without bound - over time. Duration like 7d, 36h, 90m (units - d/h/m/s). Unset means no age limit. Enforced by a - background sweeper (see --cachePruneInterval). - --cacheMaxEntries= - Keep at most this many cached commit-SHA entries, - evicting the least-recently-used first. Unset - means no count limit. - --cacheMaxSize= - Keep the cache's total on-disk size at or below - this, evicting the least-recently-used entries - first. Size like 10GB, 500MB, or a bare byte - count (base 1024). Unset means no size limit. - --cachePruneInterval= - How often the background sweeper enforces the - --cacheMax* limits. Duration like 1h, 30m. - Defaults to 1h. No effect unless a --cacheMax* - limit is set. - -co, --bazelCommandOptions= - Additional space separated Bazel command options - used when invoking `bazel query` - --cqueryCommandOptions= - Additional space separated Bazel command options - used when invoking `bazel cquery`. No effect - unless --useCquery is set. - --cqueryExpression= - Custom cquery expression to use instead of the - default. No effect unless --useCquery. - --[no-]excludeExternalTargets - If true, exclude external targets (do not query - //external:all-targets). - --excludeTargetsQuery= - A Bazel query expression whose matched targets are - excluded from the served hashes via the `except` - operator, e.g. `manual`-tagged targets: - --excludeTargetsQuery='attr("tags", "[\[ ]manual[, - \]]", //...)'. - --fineGrainedHashExternalRepos= - Comma separated list of external repos for which - fine-grained hashes are computed. - --fineGrainedHashExternalReposFile= - A text file with a newline separated list of - external repos. Mutually exclusive with - --fineGrainedHashExternalRepos. - --gitPath= Path to the git binary used for fetch/checkout - operations. Defaults to 'git' on the PATH. - -h, --help Show this help message and exit. - --ignoredRuleHashingAttributes= - Attributes that should be ignored when hashing rule - targets. - -k, --[no-]keep_going Run `bazel query` with --keep_going. Defaults to - false. - --no-initial-fetch Skip the initial 'git fetch' before reporting - healthy. Useful for local/offline testing. - --port= Port to listen on. Defaults to 8080. Pass 0 to bind - any free port, and --portFile to find out which - one. - --portFile= Write the bound port to this file once the server - is listening. The point is '--port 0 --portFile - ': a caller that picks a port itself has to - open a socket to find a free one and close it - again before this process can bind, and anything - else on the machine can take the port in between. - Binding 0 and reporting back has no such window. - The file appears only after the bind succeeds, so - waiting for it is also waiting for the listener. - --requestTimeout= - Maximum seconds an /impacted_targets - (_with_distances) or /dependency_edges request - may run before the server abandons it and - responds 504. 0 (the default) means no timeout. - This bounds the request the client waits on; an - in-flight bazel query may keep running in the - background and still populate the per-SHA cache. - -s, --seed-filepaths= - A text file with a newline separated list of - filepaths used as a SHA256 seed for all targets. - --s3Bucket= S3 bucket used as a shared hash cache behind the - local --cacheDir tier. Generated hashes are - published to the bucket and local cache misses - fall back to it, so replicas behind a load - balancer share one cache. Credentials and region - come from the AWS default provider chains (env - vars, profile, IRSA, IMDS). Unset (the default) - means local-disk caching only. - --s3Endpoint= - Custom S3 endpoint URL for S3-compatible stores - (MinIO, LocalStack). Usually combined with - --s3ForcePathStyle. - --[no-]s3ForcePathStyle - Use path-style S3 addressing (bucket in the URL - path, not the hostname), required by most - S3-compatible stores. Defaults to false. - --s3Prefix= Key prefix for cache objects in --s3Bucket, e.g. - 'bazel-diff/my-repo'. Defaults to no prefix - (objects at the bucket root). - --s3Region= AWS region of --s3Bucket. Defaults to the SDK's - default region chain (env vars, profile, IMDS). - -so, --bazelStartupOptions= - Additional space separated Bazel client startup - options used when invoking Bazel - --[no-]trackDeps Track dependency edges and persist them per commit - SHA so build-graph distance metrics can be served - via /impacted_targets_with_distances and the - generate-hashes graph via /dependency_edges. - Increases cache size and memory. Defaults to - false. - --[no-]useCquery If true, use cquery instead of query when - generating dependency graphs. - -v, --verbose Display query string, missing files and elapsed time - -V, --version Print version information and exit. - -w, --workspacePath= - Path to the Bazel workspace git clone the service - checks out and queries. - --warmupRevision= - Comma separated git revisions (branch/tag/SHA) - whose hashes are generated and cached at startup, - before the server reports healthy, so the first - real request is warm and the Bazel analysis - server is primed. Best-effort: a revision that - fails to warm is logged and the server still - becomes ready (serving it cold on demand). - Increases time-to-healthy, so size - deploy/health-check timeouts accordingly. +Run the HTTP impacted-target query service + +Usage: bazel-diff serve [OPTIONS] --workspacePath --cacheDir + +Options: + -v, --verbose + + -w, --workspacePath + Path to the Bazel workspace + -b, --bazelPath + Path to the Bazel or Bazelisk executable [default: bazel] + -s, --seed-filepaths + File containing workspace-relative paths whose contents seed every target hash + --bazelStartupOptions + Additional space-separated Bazel startup options + --bazelCommandOptions + Additional space-separated `bazel query` options + --cqueryCommandOptions + Additional space-separated `bazel cquery` options + --fineGrainedHashExternalRepos + + --fineGrainedHashExternalReposFile + + --useCquery[=] + [default: false] [possible values: true, false] + --cqueryExpression + + -k, --keep_going[=] + [default: false] [possible values: true, false] + --ignoredRuleHashingAttributes + + --excludeExternalTargets[=] + [default: false] [possible values: true, false] + --excludeTargetsQuery + + --alwaysAffectedTags + + --gitPath + [default: git] + --port + [default: 8080] + --requestTimeout + [default: 0] + --cacheDir + + --trackDeps[=] + [default: false] [possible values: true, false] + --no-initial-fetch + + --warmupRevision + + --cacheMaxAge + + --cacheMaxEntries + + --cacheMaxSize + + --cachePruneInterval + [default: 1h] + --s3Bucket + + --s3Prefix + [default: ""] + --s3Region + + --s3Endpoint + + --s3ForcePathStyle + + -h, --help + Print help + -V, --version + Print version ``` @@ -723,61 +508,52 @@ content of the file are converted into a SHA256 value. ## Installing -### Integrate into your project (recommended) +### Prebuilt binaries (recommended) -First, add the following snippet to your project: +Every GitHub release ships self-contained CLIs. The Linux binaries are statically +linked against musl -- no glibc requirement, so they run on any distribution, +including Alpine and images older than the machine that built them. -#### Bzlmod snippet +```terminal +# Linux amd64 +curl -Lo bazel-diff https://github.com/Tinder/bazel-diff/releases/latest/download/bazel-diff-rust-linux-amd64 +chmod +x bazel-diff -```bazel -bazel_dep(name = "bazel-diff", version = "46.0.0") -``` +# Linux arm64 +curl -Lo bazel-diff https://github.com/Tinder/bazel-diff/releases/latest/download/bazel-diff-rust-linux-arm64 +chmod +x bazel-diff -You can now run the tool with: +# macOS arm64 +curl -Lo bazel-diff https://github.com/Tinder/bazel-diff/releases/latest/download/bazel-diff-rust-macos-arm64 +chmod +x bazel-diff -```terminal -bazel run @bazel-diff//cli:bazel-diff +./bazel-diff --help ``` -#### WORKSPACE snippet +Windows amd64: download +`bazel-diff-rust-windows-amd64.exe` from the +[latest release](https://github.com/Tinder/bazel-diff/releases/latest). -```bazel -http_jar = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_jar") -http_jar( - name = "bazel-diff", - urls = [ - "https://github.com/Tinder/bazel-diff/releases/download/7.0.0/bazel-diff_deploy.jar" - ], - sha256 = "0b9e32f9c20e570846b083743fe967ae54d13e2a1f7364983e0a7792979442be", -) -``` +The `bazel-diff-rust-` prefix in the asset names predates the removal of the +JVM implementation and is kept so existing download scripts keep working. -Second, add in your root `BUILD.bazel` file: +### Integrate into your project -```bazel -load("@rules_java//java:defs.bzl", "java_binary") +Add the module to your `MODULE.bazel`: -java_binary( - name = "bazel-diff", - main_class = "com.bazel_diff.Main", - runtime_deps = ["@bazel-diff//jar"], -) +```bazel +bazel_dep(name = "bazel-diff", version = "46.0.0") ``` -That's it! You can now run the tool with: +You can now run the tool with: ```terminal -bazel run //:bazel-diff +bazel run @bazel-diff//:bazel-diff -- --help ``` -> Note, in releases prior to 2.0.0 the value for the `main_class` attribute is just `BazelDiff` - -### Run Via JAR Release - -```terminal -curl -Lo bazel-diff.jar https://github.com/Tinder/bazel-diff/releases/latest/download/bazel-diff_deploy.jar -java -jar bazel-diff.jar -h -``` +This builds the CLI from source with the Rust toolchain that `rules_rust` +downloads for you; no host Rust or Java installation is needed. `bazel-diff` +is bzlmod-only: it has no `WORKSPACE` integration. ### Build from Source @@ -786,7 +562,7 @@ After cloning down the repo, you are good to go, Bazel will handle the rest To run the project ```terminal -bazel run :bazel-diff -- bazel-diff -h +bazel run //:bazel-diff -- --help ``` #### Debugging (when running from source) @@ -794,55 +570,29 @@ bazel run :bazel-diff -- bazel-diff -h To run `bazel-diff` with debug logging, run your commands with the `verbose` config like so: ```terminal -bazel run :bazel-diff --config=verbose -- bazel-diff -h -``` - -### Build your own deployable JAR - -```terminal -bazel build //cli:bazel-diff_deploy.jar -java -jar bazel-bin/cli/bazel-diff_deploy.jar # This JAR can be run anywhere +bazel run //:bazel-diff --config=verbose -- --help ``` -### Build from source in your Bazel Project - -Add the following to your `WORKSPACE` file to add the external repositories, replacing the `RELEASE_ARCHIVE_URL` with the archive url of the bazel-diff release you wish to depend on: - -```bazel -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -http_archive( - name = "bazel-diff", - urls = [ - "RELEASE_ARCHIVE_URL", - ], - sha256 = "UPDATE_ME", - strip_prefix = "UPDATE_ME" -) - -load("@bazel-diff//:repositories.bzl", "bazel_diff_dependencies") - -bazel_diff_dependencies() - -load("@rules_jvm_external//:defs.bzl", "maven_install") -load("@bazel-diff//:artifacts.bzl", "BAZEL_DIFF_MAVEN_ARTIFACTS") - -maven_install( - name = "bazel_diff_maven", - artifacts = BAZEL_DIFF_MAVEN_ARTIFACTS, - repositories = [ - "http://uk.maven.org/maven2", - "https://jcenter.bintray.com/", - ], -) -``` +### Build your own release binary -Now you can simply run `bazel-diff` from your project: +Release assets are produced by Bazel alone -- CI runs nothing but the commands +below and uploads whatever lands in `bazel-bin/release/`, so `//release:bazel-diff` +names the binary for the platform it was built for +(`bazel-diff-rust--`, plus `.exe` on Windows): ```terminal -bazel run @bazel-diff//cli:bazel-diff -- bazel-diff -h +make release_binary # bazel build //release:bazel-diff --config=release +make release_binary_linux # ... --config=release-musl +make release_binary_linux_arm64 # ... --config=release-musl-arm64 ``` +`--config=release-musl` and `--config=release-musl-arm64` target +`//platforms:linux_x86_64_musl` and `//platforms:linux_aarch64_musl`, which select a musl Rust +std and a musl C toolchain, so the Linux assets are statically linked instead of inheriting the +build runner's glibc as a version floor. They are cross-compiles: the same commands produce +`bazel-diff-rust-linux-amd64` and `bazel-diff-rust-linux-arm64` on a glibc Linux host and on an +Apple Silicon Mac. + ## Contributors @@ -940,87 +690,29 @@ To run the tests simply run bazel test //... ``` -## Experimental Rust candidate - -This branch includes a proposed Rust implementation at `//:bazel-diff-rust`. The existing Kotlin -implementation remains the default `//:bazel-diff` target and the released JAR. - -```terminal -bazel run //:bazel-diff-rust -- --help -``` - -GitHub Releases also ship prebuilt binaries: - -```terminal -# Linux amd64 (statically linked against musl -- no glibc requirement, so it -# runs on any distribution, including Alpine and images older than the runner) -curl -Lo bazel-diff-rust https://github.com/Tinder/bazel-diff/releases/latest/download/bazel-diff-rust-linux-amd64 -chmod +x bazel-diff-rust - -# Linux arm64 (same musl static linking) -curl -Lo bazel-diff-rust https://github.com/Tinder/bazel-diff/releases/latest/download/bazel-diff-rust-linux-arm64 -chmod +x bazel-diff-rust - -# macOS arm64 -curl -Lo bazel-diff-rust https://github.com/Tinder/bazel-diff/releases/latest/download/bazel-diff-rust-macos-arm64 -chmod +x bazel-diff-rust -``` - -Windows amd64: download -`bazel-diff-rust-windows-amd64.exe` from the -[latest release](https://github.com/Tinder/bazel-diff/releases/latest). - -Those assets are produced by Bazel alone -- CI runs nothing but the commands below and uploads -whatever lands in `bazel-bin/release/`, so `//release:bazel-diff-rust` names the binary for the -platform it was built for (`bazel-diff-rust--`, plus `.exe` on Windows): - -```terminal -make release_rust_binary # bazel build //release:bazel-diff-rust --config=release -make release_rust_binary_linux # ... --config=release-musl -make release_rust_binary_linux_arm64 # ... --config=release-musl-arm64 -``` - -`--config=release-musl` and `--config=release-musl-arm64` target -`//platforms:linux_x86_64_musl` and `//platforms:linux_aarch64_musl`, which select a musl Rust -std and a musl C toolchain, so the Linux assets are statically linked instead of inheriting the -build runner's glibc as a version floor. They are cross-compiles: the same commands produce -`bazel-diff-rust-linux-amd64` and `bazel-diff-rust-linux-arm64` on a glibc Linux host and on an -Apple Silicon Mac. - -### Performance gate - -The Rust candidate is expected to be faster than Kotlin, and CI enforces it. `make perf-gate` -runs both binaries over generated workloads -- a synthetic `streamed_proto` graph plus hash-file -pairs, with no real workspace, Bazel server or Hyperfine involved -- and exits non-zero unless -Rust wins on median wall time, on start-up-adjusted wall time, and in every paired round. - -```terminal -make perf-gate -make perf-gate SCALE=4 ROUNDS=9 JSON=/tmp/perf-gate.json -``` - -Outputs are compared before timings are reported, so a "speedup" can never come from the two -implementations doing different work. See -[`docs/kotlin-rust-perf-gate.md`](docs/kotlin-rust-perf-gate.md) for the protocol, the workload -list and what to do when the gate fails. For measuring the two implementations against a real -repository instead, see [`docs/kotlin-vs-rust-benchmark.md`](docs/kotlin-vs-rust-benchmark.md). +The unit tests live next to the code under `src/` (`//:rust_tests`). The +behavioral suite under `tests/e2e/` (`//tests:e2e_test`) drives the real binary +against real Bazel workspaces, one Bazel test target per case; see +[`tools/e2e/README.md`](tools/e2e/README.md) for how that split is maintained. +`//:rust_clippy_check` and `//:rust_format_check` are the lint gates, and +`make format` (`bazel run //tools/format:rustfmt`) is how you satisfy the +latter. ## Code coverage -CI enforces a minimum **90% line coverage** on production sources. Kotlin -(`cli/src/main/...`), Go (`tools/go/...`), and the experimental Rust -implementation (`src/...`) are gated **independently** at 90% each, so thin -coverage in one language can't hide behind well-covered code in another. To -run the same checks locally: +CI enforces a minimum **90% line coverage** on production sources. Rust +(`src/...`) and Go (`tools/go/...`) are gated **independently** at 90% each, so +thin coverage in one language can't hide behind well-covered code in another. +To run the same checks locally: ```terminal make coverage ``` This invokes -`bazel coverage --combined_report=lcov //cli/... //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/...` +`bazel coverage --combined_report=lcov //src:cli_tests //src:rust_tests //tools:coverage_check_test //tools/coverage/... //tools/go/...` and then runs `//tools:coverage-check` twice against the resulting LCOV report — once for -the Kotlin main sources and once scoped to `tools/go/` (`--include tools/go/`). The check is +the Rust sources under `src/` and once scoped to `tools/go/` (`--include tools/go/`). The check is a Python `py_binary` ([`tools/coverage_check.py`](tools/coverage_check.py)) that prints a per-file table sorted by coverage (worst first), the overall percentage, and exits non-zero if the scoped coverage is below the threshold. @@ -1053,14 +745,11 @@ coverage_enforced_test( ``` The default minimum is 90%. Go (`//tools/go/sample:sample_test`), the Rust -LCOV merger (`//tools/coverage:lcov_merger_test`), the experimental Rust -implementation (`//src:cli_tests` and `//src:rust_tests`), and the -primary-owner Kotlin/JVM tests under `//cli` all carry minimums. When a +LCOV merger (`//tools/coverage:lcov_merger_test`) and the CLI itself +(`//src:cli_tests` and `//src:rust_tests`) all carry minimums. When a target's merged report falls below its minimum, the coverage run fails that target and the test log contains a per-file breakdown. See [`tools/coverage/README.md`](tools/coverage/README.md) for details. -The Kotlin-to-Rust applicability and parity decisions are tracked in -[`docs/kotlin-rust-test-parity.md`](docs/kotlin-rust-test-parity.md). For an interactive HTML report (annotated source with covered/uncovered lines highlighted), use `make coverage-html`. This requires the `lcov` package @@ -1075,7 +764,7 @@ To experiment with a different threshold (e.g. while ratcheting up), set COVERAGE_THRESHOLD=80 make coverage ``` -The CI matrix runs the same check on every Linux/macOS test job, so a PR cannot +The CI `coverage` matrix runs the same check on Linux and macOS, so a PR cannot land if it drops main-source line coverage below the threshold. ## Versioning diff --git a/WORKSPACE b/WORKSPACE deleted file mode 100644 index 70f90b92..00000000 --- a/WORKSPACE +++ /dev/null @@ -1,80 +0,0 @@ -workspace(name = "bazel_diff") - -load("//:repositories.bzl", "bazel_diff_dependencies") - -bazel_diff_dependencies() - -load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") - -bazel_skylib_workspace() - -load("@bazel_features//:deps.bzl", "bazel_features_deps") - -bazel_features_deps() - -load("@rules_python//python:repositories.bzl", "py_repositories") - -py_repositories() - -load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") - -protobuf_deps() - -load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") - -rules_proto_dependencies() - -load("@rules_proto//proto:toolchains.bzl", "rules_proto_toolchains") - -rules_proto_toolchains() - -load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains") - -rules_java_dependencies() - -rules_java_toolchains() - -load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") - -rules_jvm_external_deps() - -load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") - -rules_jvm_external_setup() - -load("@rules_jvm_external//:defs.bzl", "maven_install") -load("@rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories") -load("//:artifacts.bzl", "BAZEL_DIFF_MAVEN_ARTIFACTS") - -kotlin_repositories() - -load("@rules_kotlin//kotlin:core.bzl", "kt_register_toolchains") - -kt_register_toolchains() - -maven_install( - name = "bazel_diff_maven", - artifacts = BAZEL_DIFF_MAVEN_ARTIFACTS, - maven_install_json = "//:maven_install.json", - repositories = [ - "https://repo1.maven.org/maven2/", - ], -) - -load("@bazel_diff_maven//:defs.bzl", "pinned_maven_install") - -pinned_maven_install() - -load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies") - -aspect_bazel_lib_dependencies() - -load( - "@aspect_rules_lint//format:repositories.bzl", - "fetch_ktfmt", - "rules_lint_dependencies", -) - -rules_lint_dependencies() - -fetch_ktfmt() diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod deleted file mode 100644 index 389269a6..00000000 --- a/WORKSPACE.bzlmod +++ /dev/null @@ -1 +0,0 @@ -workspace(name = "bazel_diff") diff --git a/artifacts.bzl b/artifacts.bzl deleted file mode 100644 index c241dba6..00000000 --- a/artifacts.bzl +++ /dev/null @@ -1,14 +0,0 @@ -""" Artifacts used to build bazel-diff """ - -BAZEL_DIFF_MAVEN_ARTIFACTS = [ - "com.google.code.gson:gson:jar:2.14.0", - "com.google.guava:guava:33.6.0-jre", - "com.willowtreeapps.assertk:assertk-jvm:0.28.1", - "info.picocli:picocli:jar:4.7.7", - "io.insert-koin:koin-core-jvm:4.2.2", - "io.insert-koin:koin-test-junit4:4.2.2", - "junit:junit:4.13.2", - "org.apache.commons:commons-pool2:2.13.1", - "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0", - "org.mockito.kotlin:mockito-kotlin:6.3.0", -] diff --git a/bazel-diff-example.ps1 b/bazel-diff-example.ps1 index b08efdaa..c05e9ad3 100644 --- a/bazel-diff-example.ps1 +++ b/bazel-diff-example.ps1 @@ -66,11 +66,6 @@ function Run-BazelDiff { param( [string[]]$Arguments ) - # Shut down the Bazel server first so its persistent workers exit. On Windows a - # live Kotlin worker keeps rules_kotlin's build.exe/compiler.jar locked, and the - # external repo refetch fails with "Permission denied" whenever the two revisions - # being diffed pin different rules_kotlin versions. - & $BazelPath shutdown & $BazelPath run @BazelExtraOptions //:bazel-diff -- @Arguments return $LASTEXITCODE } diff --git a/cli/BUILD b/cli/BUILD deleted file mode 100644 index a8ae5bb6..00000000 --- a/cli/BUILD +++ /dev/null @@ -1,500 +0,0 @@ -load("@rules_java//java:defs.bzl", "java_binary") -load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") -load("//tools/coverage:defs.bzl", "coverage_minimum_env") -load("//tools/e2e:defs.bzl", "kt_jvm_e2e_tests") -load("//tools/e2e:kotlin_e2e_cases.bzl", "KOTLIN_E2E_SUITES") - -config_setting( - name = "enable_debug", - values = { - "compilation_mode": "dbg", - }, -) - -genrule( - name = "version_file", - outs = ["src/main/resources/cli/version"], - cmd = "echo '{version}' > $@".format(version = module_version() if module_version() else "unknown"), - stamp = 1, -) - -java_binary( - name = "bazel-diff", - jvm_flags = select({ - ":enable_debug": ["-DDEBUG=true"], - "//conditions:default": [], - }), - main_class = "com.bazel_diff.Main", - visibility = ["//visibility:public"], - runtime_deps = [":cli-lib"], -) - -kt_jvm_library( - name = "cli-lib", - srcs = glob(["src/main/kotlin/**/*.kt"]), - resources = [":version_file"], - # sts is runtime-only: the SDK's DefaultCredentialsProvider loads it reflectively to - # assume a pod IAM role via web identity (IRSA) for the serve S3 cache backend. - runtime_deps = [ - "@bazel_diff_maven//:software_amazon_awssdk_sts", - ], - deps = [ - "//proto:analysis_v2_java_proto", - "//proto:build_java_proto", - "@bazel_diff_maven//:com_google_code_gson_gson", - "@bazel_diff_maven//:com_google_guava_guava", - "@bazel_diff_maven//:info_picocli_picocli", - "@bazel_diff_maven//:io_insert_koin_koin_core_jvm", - "@bazel_diff_maven//:org_apache_commons_commons_pool2", - "@bazel_diff_maven//:org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm", - "@bazel_diff_maven//:software_amazon_awssdk_regions", - "@bazel_diff_maven//:software_amazon_awssdk_s3", - "@bazel_diff_maven//:software_amazon_awssdk_sdk_core", - "@bazel_diff_maven//:software_amazon_awssdk_url_connection_client", - ], -) - -# Every primary-owner kt_jvm_test declares a per-target line-coverage minimum -# (default 90%), enforced by //tools/coverage:lcov_merger during -# `bazel coverage` only (plain `bazel test` is unaffected). Jacoco instruments -# the whole of :cli-lib for every test, so each check is scoped -# (coverage_include) to the source file(s) the test is responsible for. -# Partial/regression tests and E2ETest omit enforcement; the repo-wide -# coverage gate still covers the combined report. -kt_jvm_test( - name = "BuildGraphHasherTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt"], - ), - jvm_flags = [ - "-Dnet.bytebuddy.experimental=true", - ], - test_class = "com.bazel_diff.hash.BuildGraphHasherTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "TargetHashTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/hash/TargetHash.kt"], - ), - test_class = "com.bazel_diff.hash.TargetHashTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "RuleHasherTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/hash/RuleHasher.kt"], - ), - test_class = "com.bazel_diff.hash.RuleHasherTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "RuleHasherAlwaysAffectedTagsTest", - test_class = "com.bazel_diff.hash.RuleHasherAlwaysAffectedTagsTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "SourceFileHasherTest", - data = [ - ":src/test/kotlin/com/bazel_diff/hash/fixture/foo.ts", - ], - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/hash/SourceFileHasher.kt"], - ), - test_class = "com.bazel_diff.hash.SourceFileHasherTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "CalculateImpactedTargetsInteractorTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/interactor/CalculateImpactedTargetsInteractor.kt"], - ), - test_class = "com.bazel_diff.interactor.CalculateImpactedTargetsInteractorTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "CalculateImpactedTargetsInteractorIssue335Test", - test_class = "com.bazel_diff.interactor.CalculateImpactedTargetsInteractorIssue335Test", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "CalculateImpactedTargetsInteractorModuleQueryTest", - test_class = "com.bazel_diff.interactor.CalculateImpactedTargetsInteractorModuleQueryTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "NormalisingPathConverterTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/converter/NormalisingPathConverter.kt"], - ), - test_class = "com.bazel_diff.cli.converter.NormalisingPathConverterTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "OptionsConverterTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/converter/OptionsConverter.kt"], - ), - test_class = "com.bazel_diff.cli.converter.OptionsConverterTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "DurationConverterTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/converter/DurationConverter.kt"], - ), - test_class = "com.bazel_diff.cli.converter.DurationConverterTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "ByteSizeConverterTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/converter/ByteSizeConverter.kt"], - ), - test_class = "com.bazel_diff.cli.converter.ByteSizeConverterTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "DeserialiseHashesInteractorTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt"], - ), - test_class = "com.bazel_diff.interactor.DeserialiseHashesInteractorTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "FingerprintInteractorTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/interactor/FingerprintInteractor.kt"], - ), - test_class = "com.bazel_diff.interactor.FingerprintInteractorTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "FingerprintGathererTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/FingerprintGatherer.kt"], - ), - test_class = "com.bazel_diff.cli.FingerprintGathererTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "FingerprintCommandTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/FingerprintCommand.kt"], - ), - test_class = "com.bazel_diff.cli.FingerprintCommandTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "WarmupCommandTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/WarmupCommand.kt"], - ), - test_class = "com.bazel_diff.cli.WarmupCommandTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "BazelRuleTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/bazel/BazelRule.kt"], - ), - test_class = "com.bazel_diff.bazel.BazelRuleTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "BazelClientTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt"], - ), - test_class = "com.bazel_diff.bazel.BazelClientTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "BazelTargetTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/bazel/BazelTarget.kt"], - ), - test_class = "com.bazel_diff.bazel.BazelTargetTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "BazelTargetTypeTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/bazel/BazelTargetType.kt"], - ), - test_class = "com.bazel_diff.bazel.BazelTargetTypeTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "BazelDiffTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/BazelDiff.kt"], - ), - test_class = "com.bazel_diff.cli.BazelDiffTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "VersionProviderTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/VersionProvider.kt"], - ), - test_class = "com.bazel_diff.cli.VersionProviderTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "StderrLoggerTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/log/StderrLogger.kt"], - ), - test_class = "com.bazel_diff.log.StderrLoggerTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "BazelModServiceTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/bazel/BazelModService.kt"], - ), - test_class = "com.bazel_diff.bazel.BazelModServiceTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "ModuleGraphParserTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/bazel/ModuleGraphParser.kt"], - ), - test_class = "com.bazel_diff.bazel.ModuleGraphParserTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "StderrPollutionRegressionTest", - test_class = "com.bazel_diff.bazel.StderrPollutionRegressionTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "ProcessStdinHangRegressionTest", - test_class = "com.bazel_diff.process.ProcessStdinHangRegressionTest", - runtime_deps = [":cli-test-lib"], -) - -# The JUnit e2e suite, one Bazel target per `@Test` method. It used to be a -# single target with timeout = "eternal", because the class as a whole ran -# ~1150s on CI's macos-latest x bazel 8.x cell; split, each case gets its own -# 300s budget, its own cache entry and its own log, and the cases run in -# parallel instead of back to back. -# -# KOTLIN_E2E_SUITES is generated from the sources by `make regen-e2e`, so adding -# a `@Test` method to any class under cli/src/test/kotlin/com/bazel_diff/e2e/ -# creates its target with no edit here. //tools/e2e:regen_check fails CI when -# the two drift. -# -# No per-target coverage minimum: E2E exercises the real binary across the whole -# main tree, but the repo-wide coverage gate (and primary-owner unit tests) own -# the 90% floor. Enforcement here would duplicate that gate. -kt_jvm_e2e_tests( - data = [":workspaces"], - suites = KOTLIN_E2E_SUITES, - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "ContentHashProviderTest", - data = [ - ":src/test/kotlin/com/bazel_diff/io/fixture/correct.json", - ":src/test/kotlin/com/bazel_diff/io/fixture/wrong.json", - ], - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/io/ContentHashProvider.kt"], - ), - test_class = "com.bazel_diff.io.ContentHashProviderTest", - runtime_deps = [ - ":cli-test-lib", - ], -) - -kt_jvm_test( - name = "ServeCommandTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/cli/ServeCommand.kt"], - ), - test_class = "com.bazel_diff.cli.ServeCommandTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "LocalDiskHashCacheStorageTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/HashCacheStorage.kt"], - ), - test_class = "com.bazel_diff.server.LocalDiskHashCacheStorageTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "S3HashCacheStorageTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/S3HashCacheStorage.kt"], - ), - jvm_flags = [ - "-Dnet.bytebuddy.experimental=true", - ], - test_class = "com.bazel_diff.server.S3HashCacheStorageTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "TieredHashCacheStorageTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/TieredHashCacheStorage.kt"], - ), - test_class = "com.bazel_diff.server.TieredHashCacheStorageTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "CachePrunerTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/CachePruner.kt"], - ), - test_class = "com.bazel_diff.server.CachePrunerTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "MetricsServiceTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/MetricsService.kt"], - ), - test_class = "com.bazel_diff.server.MetricsServiceTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "GitClientTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/GitClient.kt"], - ), - test_class = "com.bazel_diff.server.GitClientTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "HashServiceTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/HashService.kt"], - ), - jvm_flags = [ - "-Dnet.bytebuddy.experimental=true", - ], - test_class = "com.bazel_diff.server.HashServiceTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "ImpactedTargetsServiceTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/ImpactedTargetsService.kt"], - ), - jvm_flags = [ - "-Dnet.bytebuddy.experimental=true", - ], - test_class = "com.bazel_diff.server.ImpactedTargetsServiceTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "BazelDiffServerTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/BazelDiffServer.kt"], - ), - test_class = "com.bazel_diff.server.BazelDiffServerTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_test( - name = "QueryProfilerTest", - env = coverage_minimum_env( - coverage_include = ["cli/src/main/kotlin/com/bazel_diff/server/QueryProfiler.kt"], - ), - test_class = "com.bazel_diff.server.QueryProfilerTest", - runtime_deps = [":cli-test-lib"], -) - -kt_jvm_library( - name = "cli-test-lib", - testonly = True, - srcs = glob(["src/test/kotlin/**/*.kt"]), - resources = glob(["src/test/resources/**/*"]), - deps = [ - ":cli-lib", - "@bazel_diff_maven//:com_willowtreeapps_assertk_assertk_jvm", - "@bazel_diff_maven//:io_insert_koin_koin_test_junit4", - "@bazel_diff_maven//:io_insert_koin_koin_test_jvm", - "@bazel_diff_maven//:junit_junit", - "@bazel_diff_maven//:org_mockito_kotlin_mockito_kotlin", - "@bazel_diff_maven//:software_amazon_awssdk_s3", - "@bazel_diff_maven//:software_amazon_awssdk_sdk_core", - ], -) - -# The Kotlin e2e sources //tools/e2e:regen_check re-derives the generated case -# list from. Globbed, not enumerated: a new e2e class is meant to need no edit -# here either. -filegroup( - name = "e2e_test_sources", - srcs = glob(["src/test/kotlin/com/bazel_diff/e2e/**/*.kt"]), - visibility = ["//tools/e2e:__pkg__"], -) - -filegroup( - name = "workspaces", - srcs = [ - "src/test/resources/workspaces", - ], -) - -# //tests:e2e_test reads both fixture trees straight off disk (it is not a JVM -# test, so it cannot pull the zips and goldens off a classpath the way -# //cli:E2ETest does) and resolves them relative to the runfiles root. -filegroup( - name = "e2e_resources", - srcs = [ - "src/test/resources/fixture", - "src/test/resources/workspaces", - ], - visibility = ["//tests:__pkg__"], -) - -java_binary( - name = "ktfmt", - main_class = "com.facebook.ktfmt.cli.Main", - visibility = ["//cli/format:__pkg__"], - runtime_deps = ["@ktfmt//jar"], -) diff --git a/cli/src/main/kotlin/com/bazel_diff/Main.kt b/cli/src/main/kotlin/com/bazel_diff/Main.kt deleted file mode 100644 index 2ed37ad6..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/Main.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.bazel_diff - -import com.bazel_diff.cli.BazelDiff -import kotlin.system.exitProcess -import picocli.CommandLine - -class Main { - companion object { - @JvmStatic - fun main(args: Array) { - val exitCode = CommandLine(BazelDiff()).execute(*args) - exitProcess(exitCode) - } - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt deleted file mode 100644 index d35ef2ee..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt +++ /dev/null @@ -1,140 +0,0 @@ -package com.bazel_diff.bazel - -import com.bazel_diff.log.Logger -import java.util.Calendar -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -class BazelClient( - private val useCquery: Boolean, - private val cqueryExpression: String?, - private val fineGrainedHashExternalRepos: Set, - private val excludeExternalTargets: Boolean, - private val excludeTargetsQuery: String?, -) : KoinComponent { - private val logger: Logger by inject() - private val queryService: BazelQueryService by inject() - private val bazelModService: BazelModService by inject() - - /** - * Latched once a query proves `//external` is unavailable in this workspace (bzlmod-only mode - * where the `bazel mod graph` probe in [BazelModService] returned a false negative). Subsequent - * [queryAllTargets] calls -- e.g. later revisions served by a long-running `serve` process -- - * then skip `//external:all-targets` up front rather than failing and retrying every time. - */ - @Volatile private var externalPackageUnavailable = false - - /** - * Wraps [query] so that every target matching the user-supplied [excludeTargetsQuery] is removed - * from the result set via Bazel's `except` operator. Returns [query] unchanged when no exclude - * query is configured. This is how callers drop, e.g., `manual`-tagged targets from the graph - * (issue #392): `--excludeTargetsQuery='attr("tags", "[\[ ]manual[,\]]", //...)'`. Excluded - * targets are simply absent from hashing; any kept target that depended on one logs an "Unable to - * calculate digest" warning and hashes without that input, which is the intended behavior for - * leaf targets like `manual`-tagged tests/binaries. - */ - private fun withExcludeFilter(query: String): String = - excludeTargetsQuery?.takeIf { it.isNotBlank() }?.let { "($query) except ($it)" } ?: query - - suspend fun queryAllTargets(): List { - val queryEpoch = Calendar.getInstance().getTimeInMillis() - - // Skip //external:all-targets when explicitly excluded, or when an earlier query already proved - // //external is unavailable here (see [externalPackageUnavailable]). - // - // Bzlmod being enabled does not retire //external: a workspace can run both, and //external - // still holds the only version-bearing hash for every WORKSPACE repo. Ask for it and let the - // ExternalPackageUnavailableException path below drop it when it really is gone. - val repoTargetsQuery = - if (excludeExternalTargets || externalPackageUnavailable) { - emptyList() - } else { - listOf("//external:all-targets") - } - - // When Bzlmod is enabled and Bazel 8.6.0+ is available, query bzlmod-managed external repo - // definitions via `bazel mod show_repo --output=streamed_proto`. This creates synthetic - // //external:* targets from Build.Repository protos, enabling fine-grained hashing of - // bzlmod dependencies. - val bzlmodRepoTargets = - if (bazelModService.isBzlmodEnabled && queryService.canUseBzlmodShowRepo) { - logger.i { "Querying bzlmod-managed external repos via mod show_repo" } - queryService.queryBzlmodRepos() - } else { - emptyList() - } - - val targets = - if (useCquery) { - // Explicitly listing external repos here sometimes causes issues mentioned at - // https://bazel.build/query/cquery#recursive-target-patterns. Hence, we query all - // dependencies with `deps` - // instead. However, we still need to append all "//external:*" targets because - // fine-grained hash - // computation depends on hashing of source files in external repos as well, which is - // limited to repos - // explicitly mentioned in `fineGrainedHashExternalRepos` flag. Therefore, for any repos - // not mentioned there - // we are still relying on the repo-generation target under `//external` to compute the - // hash. - // - // In addition, we must include all source dependencies in this query in order for them to - // show up in - // `configuredRuleInput`. Hence, one must not filter them out with `kind(rule, deps(..))`. - val expression = withExcludeFilter(cqueryExpression ?: "deps(//...:all-targets)") - val mainTargets = queryService.query(expression, useCquery = true) - val repoTargets = - if (repoTargetsQuery.isNotEmpty()) { - try { - queryService.query(repoTargetsQuery.joinToString(" + ") { "'$it'" }) - } catch (e: ExternalPackageUnavailableException) { - // //external isn't queryable here despite the Bzlmod probe saying otherwise; the - // main deps() query already succeeded, so just drop the external repo targets. - noteExternalPackageUnavailable() - emptyList() - } - } else { - emptyList() - } - (mainTargets + repoTargets).distinctBy { it.name } - } else { - val buildTargetsQuery = - listOf("//...:all-targets") + - fineGrainedHashExternalRepos.map { "$it//...:all-targets" } - try { - queryService.query( - withExcludeFilter( - (repoTargetsQuery + buildTargetsQuery).joinToString(" + ") { "'$it'" })) - } catch (e: ExternalPackageUnavailableException) { - // The combined query failed because //external is unavailable. If we hadn't asked for - // it, the error came from elsewhere and must not be masked; otherwise retry without it. - if (repoTargetsQuery.isEmpty()) throw e - noteExternalPackageUnavailable() - queryService.query(withExcludeFilter(buildTargetsQuery.joinToString(" + ") { "'$it'" })) - } - } - val allTargets = (targets + bzlmodRepoTargets).distinctBy { it.name } - val queryDuration = Calendar.getInstance().getTimeInMillis() - queryEpoch - logger.i { "All targets queried in $queryDuration" } - return allTargets - } - - /** - * Latches [externalPackageUnavailable] the first time a query proves `//external` is unqueryable, - * emitting a one-time explanation. This means the Bzlmod probe (`bazel mod graph`) reported that - * Bzlmod was off when it is actually on -- common in restricted environments where `bazel mod - * graph` cannot resolve the module graph. Dropping `//external:all-targets` is correct here: when - * the package genuinely isn't available, there are no WORKSPACE-defined external targets to hash. - */ - private fun noteExternalPackageUnavailable() { - if (!externalPackageUnavailable) { - externalPackageUnavailable = true - logger.w { - "//external is not available in this workspace (WORKSPACE deprecated / bzlmod-only), but " + - "Bzlmod auto-detection did not catch it; dropping //external:all-targets from the " + - "query and continuing. Pass --excludeExternalTargets to skip it up front and silence " + - "this." - } - } - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelModService.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelModService.kt deleted file mode 100644 index 22198653..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelModService.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.bazel_diff.bazel - -import com.bazel_diff.log.Logger -import com.bazel_diff.process.Redirect -import com.bazel_diff.process.process -import java.nio.file.Path -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -/** - * Service that runs `bazel mod` to detect whether Bzlmod is enabled in the workspace. Used to - * decide whether to query //external:all-targets (disabled when Bzlmod is active). - */ -class BazelModService( - private val workingDirectory: Path, - private val bazelPath: Path, - private val startupOptions: List, - private val noBazelrc: Boolean, -) : KoinComponent { - private val logger: Logger by inject() - - /** - * True if Bzlmod is enabled (e.g. `bazel mod graph` succeeds). When true, //external is not - * available. - */ - val isBzlmodEnabled: Boolean by lazy { runBlocking { checkBzlmodEnabled() } } - - /** - * Returns the module dependency graph as a string for hashing purposes. This captures all module - * dependencies and their versions, allowing bazel-diff to detect when MODULE.bazel changes (e.g., - * when a module version is updated). - * - * @return The output of `bazel mod graph` if bzlmod is enabled, or null if disabled/error. - */ - @OptIn(ExperimentalCoroutinesApi::class) - suspend fun getModuleGraph(): String? { - if (!isBzlmodEnabled) { - return null - } - - val cmd = - mutableListOf().apply { - add(bazelPath.toString()) - if (noBazelrc) { - add("--bazelrc=/dev/null") - } - addAll(startupOptions) - add("mod") - add("graph") - } - logger.i { "Executing Bazel mod graph for hashing: ${cmd.joinToString()}" } - val result = - process( - *cmd.toTypedArray(), - stdout = Redirect.CAPTURE, - stderr = Redirect.SILENT, - workingDirectory = workingDirectory.toFile(), - destroyForcibly = true, - ) - - return if (result.resultCode == 0) { - result.output.joinToString("\n").trim() - } else { - logger.w { "Failed to get module graph" } - null - } - } - - /** - * Returns the module dependency graph in JSON format for precise change detection. - * - * @return The JSON output of `bazel mod graph --output=json` if bzlmod is enabled, or null if - * disabled/error. - */ - @OptIn(ExperimentalCoroutinesApi::class) - suspend fun getModuleGraphJson(): String? { - if (!isBzlmodEnabled) { - return null - } - - val cmd = - mutableListOf().apply { - add(bazelPath.toString()) - if (noBazelrc) { - add("--bazelrc=/dev/null") - } - addAll(startupOptions) - add("mod") - add("graph") - add("--output=json") - } - logger.i { "Executing Bazel mod graph JSON: ${cmd.joinToString()}" } - val result = - process( - *cmd.toTypedArray(), - stdout = Redirect.CAPTURE, - stderr = Redirect.SILENT, - workingDirectory = workingDirectory.toFile(), - destroyForcibly = true, - ) - - return if (result.resultCode == 0) { - result.output.joinToString("\n").trim() - } else { - logger.w { "Failed to get module graph JSON" } - null - } - } - - @OptIn(ExperimentalCoroutinesApi::class) - private suspend fun checkBzlmodEnabled(): Boolean { - val cmd = - mutableListOf().apply { - add(bazelPath.toString()) - if (noBazelrc) { - add("--bazelrc=/dev/null") - } - addAll(startupOptions) - add("mod") - add("graph") - } - logger.i { "Executing Bazel mod graph: ${cmd.joinToString()}" } - val result = - process( - *cmd.toTypedArray(), - stdout = Redirect.CAPTURE, - stderr = Redirect.CAPTURE, - workingDirectory = workingDirectory.toFile(), - destroyForcibly = true, - ) - return result.resultCode == 0 - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt deleted file mode 100644 index 89c49bc7..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt +++ /dev/null @@ -1,651 +0,0 @@ -package com.bazel_diff.bazel - -import com.bazel_diff.log.Logger -import com.bazel_diff.process.Redirect -import com.bazel_diff.process.process -import com.google.devtools.build.lib.analysis.AnalysisProtosV2 -import com.google.devtools.build.lib.query2.proto.proto2api.Build -import java.io.File -import java.nio.file.Files -import java.nio.file.Path -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.runBlocking -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -private val versionComparator = - compareBy> { it.first }.thenBy { it.second }.thenBy { it.third } - -/** - * Thrown by [BazelQueryService.query] when a `bazel query` fails specifically because the - * `//external` package is unavailable. Bazel reports `no such package 'external'` once the - * WORKSPACE file is deprecated (bzlmod-only mode -- e.g. Bazel 8/9 with - * `--enable_workspace=false`). - * - * [BazelClient] catches this to transparently drop `//external:all-targets` and retry, self-healing - * the case where bzlmod auto-detection via `bazel mod graph` returned a false negative (e.g. a - * restricted deploy environment where `bazel mod graph` cannot resolve the module graph, so the - * `//external`-availability prediction disagrees with what Bazel actually does). - */ -class ExternalPackageUnavailableException(message: String) : RuntimeException(message) - -class BazelQueryService( - private val workingDirectory: Path, - private val bazelPath: Path, - private val startupOptions: List, - private val commandOptions: List, - private val cqueryOptions: List, - private val keepGoing: Boolean, - private val noBazelrc: Boolean, -) : KoinComponent { - private val logger: Logger by inject() - private val modService: BazelModService by inject() - private val version: Triple by lazy { runBlocking { determineBazelVersion() } } - - @OptIn(ExperimentalCoroutinesApi::class) - private suspend fun determineBazelVersion(): Triple { - val cmd = arrayOf(bazelPath.toString(), "version") - logger.i { "Executing Bazel version command: ${cmd.joinToString()}" } - val result = - process( - *cmd, - stdout = Redirect.CAPTURE, - workingDirectory = workingDirectory.toFile(), - stderr = Redirect.PRINT, - destroyForcibly = true, - ) - - if (result.resultCode != 0) { - throw RuntimeException("Bazel version command failed, exit code ${result.resultCode}") - } - - // "bazel version" outputs "Build label: X.Y.Z" on one of the lines; accept that or legacy - // "bazel X.Y.Z". - val versionString = - result.output - .firstOrNull { it.startsWith("Build label: ") } - ?.removePrefix("Build label: ") - ?.trim() - ?: result.output.firstOrNull { it.startsWith("bazel ") }?.removePrefix("bazel ")?.trim() - ?: throw RuntimeException( - "Bazel version command returned unexpected output: ${result.output}") - // Trim off any prerelease suffixes (e.g., 8.6.0-rc1 or 8.6.0rc1). - val version = - versionString - .split('-')[0] - .split('.') - .map { it.takeWhile { c -> c.isDigit() }.toInt() } - .toTypedArray() - return Triple(version[0], version[1], version[2]) - } - - // Use streamed_proto output for cquery if available. This is more efficient than the proto - // output. - // https://github.com/bazelbuild/bazel/commit/607d0f7335f95aa0ee236ba3c18ce2a232370cdb - private val canUseStreamedProtoWithCquery - get() = versionComparator.compare(version, Triple(7, 0, 0)) >= 0 - - // Use an output file for (c)query if supported. This avoids excessively large stdout, which is - // sent out on the BES. - // https://github.com/bazelbuild/bazel/commit/514e9052f2c603c53126fbd9436bdd3ad3a1b0c7 - private val canUseOutputFile - get() = versionComparator.compare(version, Triple(8, 2, 0)) >= 0 - - // Bazel 8.6.0+ / 9.0.1+ supports `bazel mod show_repo --output=streamed_proto`, which - // outputs Build.Repository protos for bzlmod-managed external repos. - // https://github.com/bazelbuild/bazel/pull/28010 - val canUseBzlmodShowRepo - get() = - versionComparator.compare(version, Triple(8, 6, 0)) >= 0 && - // 9.0.0 does not have the feature; it landed in 9.0.1. - version != Triple(9, 0, 0) - - suspend fun query(query: String, useCquery: Boolean = false): List { - // Unfortunately, there is still no direct way to tell if a target is compatible or not with the - // proto output - // by itself. So we do an extra cquery with the trick at - // https://bazel.build/extending/platforms#cquery-incompatible-target-detection to first find - // all compatible - // targets. - val compatibleTargetSet = - if (useCquery) { - val compatibleFile = runQuery(query, useCquery = true, outputCompatibleTargets = true) - val set = compatibleFile.useLines { it.filter { it.isNotBlank() }.toSet() } - compatibleFile.delete() - set - } else { - emptySet() - } - val outputFile = runQuery(query, useCquery) - - val targets = - outputFile.inputStream().buffered().use { proto -> - if (useCquery) { - if (canUseStreamedProtoWithCquery) { - mutableListOf() - .apply { - while (true) { - val result = - AnalysisProtosV2.CqueryResult.parseDelimitedFrom(proto) ?: break - // EOF - add(result) - } - } - .flatMap { it.resultsList } - } else { - AnalysisProtosV2.CqueryResult.parseFrom(proto).resultsList - } - .mapNotNull { toBazelTarget(it.target) } - .filter { it.name in compatibleTargetSet } - } else { - mutableListOf() - .apply { - while (true) { - val target = Build.Target.parseDelimitedFrom(proto) ?: break - // EOF - add(target) - } - } - .mapNotNull { toBazelTarget(it) } - } - } - - outputFile.delete() - return targets - } - - @OptIn(ExperimentalCoroutinesApi::class) - private suspend fun runQuery( - query: String, - useCquery: Boolean, - outputCompatibleTargets: Boolean = false - ): File { - val queryFile = Files.createTempFile(null, ".txt").toFile() - val outputFile = Files.createTempFile(null, ".bin").toFile() - var cqueryStarlarkFile: File? = null - - queryFile.writeText(query) - - val allowedExitCodes = mutableListOf(0) - - val cmd: MutableList = - ArrayList().apply { - add(bazelPath.toString()) - if (noBazelrc) { - add("--bazelrc=/dev/null") - } - addAll(startupOptions) - if (useCquery) { - add("cquery") - if (!outputCompatibleTargets) { - // There is no need to query the transitions when querying for compatible targets. - add("--transitions=lite") - } - } else { - add("query") - } - add("--output") - if (useCquery) { - if (outputCompatibleTargets) { - add("starlark") - add("--starlark:file") - val starlarkFile = Files.createTempFile(null, ".cquery").toFile() - cqueryStarlarkFile = starlarkFile - starlarkFile.writeText( - """ - def format(target): - if providers(target) == None: - return "" - if "IncompatiblePlatformProvider" not in providers(target): - target_repr = repr(target) - if "() - val result = - process( - *cmd.toTypedArray(), - stdout = if (canUseOutputFile) Redirect.SILENT else Redirect.ToFile(outputFile), - workingDirectory = workingDirectory.toFile(), - stderr = - Redirect.Consume { flow -> - flow.collect { line -> - System.err.println(line) - stderrLines.add(line) - } - }, - destroyForcibly = true, - ) - - queryFile.delete() - cqueryStarlarkFile?.delete() - - if (!allowedExitCodes.contains(result.resultCode)) { - outputFile.delete() - val stderrText = stderrLines.joinToString("\n") - logger.w { "Bazel query failed with exit code ${result.resultCode}" } - if (indicatesExternalPackageUnavailable(stderrText)) { - throw ExternalPackageUnavailableException( - "Bazel query failed because the //external package is unavailable " + - "(WORKSPACE deprecated / bzlmod-only), exit code ${result.resultCode}") - } - throw RuntimeException( - "Bazel query failed, exit code ${result.resultCode}, allowed exit codes: ${allowedExitCodes.joinToString()}") - } - return outputFile - } - - /** - * True if [stderr] from a failed `bazel query` indicates the `//external` package is unavailable - * because the WORKSPACE file is deprecated (bzlmod-only mode). Matches the stable fragments of - * Bazel's error message: `no such package 'external'` / `//external package is not available`. - */ - private fun indicatesExternalPackageUnavailable(stderr: String): Boolean = - stderr.contains("no such package 'external'") || - stderr.contains("//external package is not available") - - /** - * Queries bzlmod-managed external repo definitions using `bazel mod show_repo`. Requires Bazel - * 8.6.0+ or 9.0.1+ which supports `--output=streamed_proto` for this command. - * - * The approach: - * 1. Run `bazel mod dump_repo_mapping ""` to discover the root module's apparent→canonical repo - * name mapping (e.g., "bazel_diff_maven" → "rules_jvm_external++maven+maven"). - * 2. Run `bazel mod show_repo @@... --output=streamed_proto` to get Repository proto - * definitions for each repo (works for both module repos and extension-generated repos). - * 3. Create synthetic `//external:` targets for each repo. This matches how - * `transformRuleInput` in BazelRule.kt collapses `@apparent_name//...` deps to - * `//external:apparent_name`, so the hashing pipeline can detect changes. - */ - @OptIn(ExperimentalCoroutinesApi::class) - suspend fun queryBzlmodRepos(): List { - check(canUseBzlmodShowRepo) { "queryBzlmodRepos requires Bazel 8.6.0+ or 9.0.1+" } - - // Step 1: Get the root module's apparent → canonical repo mapping. - val repoMapping = discoverRepoMapping() - if (repoMapping.isEmpty()) { - logger.w { "No repo mappings discovered, skipping mod show_repo" } - return emptyList() - } - logger.i { "Discovered ${repoMapping.size} repo mappings" } - - // Build reverse map: canonical → list of apparent names - val canonicalToApparent = mutableMapOf>() - for ((apparent, canonical) in repoMapping) { - canonicalToApparent.getOrPut(canonical) { mutableListOf() }.add(apparent) - } - - // Step 2: Fetch repo definitions via `mod show_repo @@... --output=streamed_proto`. - // - // Ask only for repos `bazel mod` can resolve. A workspace running Bzlmod alongside WORKSPACE - // maps its WORKSPACE repos into the root repo mapping too, and show_repo fails the whole batch - // on the first one it does not know -- leaving every Bzlmod repo unhashed. A Bzlmod canonical - // name carries the module separator ('+', or '~' before Bazel 7.1); //external covers the rest. - val canonicalNames = - canonicalToApparent.keys.filter { it.contains('+') || it.contains('~') }.map { "@@$it" } - if (canonicalNames.isEmpty()) { - logger.w { "No bzlmod-canonical repos in the repo mapping, skipping mod show_repo" } - return emptyList() - } - val outputFile = Files.createTempFile(null, ".bin").toFile() - - val cmd: MutableList = - ArrayList().apply { - add(bazelPath.toString()) - if (noBazelrc) { - add("--bazelrc=/dev/null") - } - addAll(startupOptions) - add("mod") - add("show_repo") - addAll(canonicalNames) - add("--output=streamed_proto") - } - - logger.i { "Querying bzlmod repos: ${cmd.joinToString()}" } - val result = - process( - *cmd.toTypedArray(), - stdout = Redirect.ToFile(outputFile), - workingDirectory = workingDirectory.toFile(), - stderr = Redirect.PRINT, - destroyForcibly = true, - ) - - if (result.resultCode != 0) { - outputFile.delete() - logger.w { - "bazel mod show_repo failed (exit code ${result.resultCode}), skipping bzlmod repos" - } - return emptyList() - } - - // Step 3: Parse Build.Repository messages and create synthetic targets for each apparent name. - val repos = - outputFile.inputStream().buffered().use { proto -> - mutableListOf().apply { - while (true) { - val repo = Build.Repository.parseDelimitedFrom(proto) ?: break - add(repo) - } - } - } - outputFile.delete() - - // Discover the bzlmod module-graph edges so we can encode the dep relationships between - // synthetic //external:* targets. Without this, a target that depends on @outer//... only - // sees //external:outer's *metadata* hash and never picks up content changes in @outer's - // own bzlmod deps (e.g. @inner). With these edges in place, RuleHasher follows the chain - // //:consumer -> //external:outer -> //external:inner during digest computation, so a - // change inside @inner propagates all the way to the main-repo consumer without the user - // having to enumerate every wrapping repo in --fineGrainedHashExternalRepos. See - // https://github.com/Tinder/bazel-diff/issues/184 (transitive build-time chain) and - // https://github.com/Tinder/bazel-diff/issues/197 (alias-wrap chain). - val moduleGraphJson = modService.getModuleGraphJson() - val moduleDepEdges = - if (moduleGraphJson != null) { - val parser = ModuleGraphParser() - // `bazel mod graph` can return cycles (e.g. rules_go <-> gazelle via the latter's - // dev_dependency). Emitting both directions as rule_inputs on the synthetic - // //external:* targets triggers RuleHasher.CircularDependencyException, so break - // cycles into a deterministic DAG before deriving dep edges. - parser.breakCycles(parser.parseModuleGraphDepEdges(moduleGraphJson)) - } else { - emptyMap() - } - // `bazel mod show_repo` does not populate Repository.module_key in current Bazel, so - // bridge from a module's `name` (always present in `bazel mod graph` output) to that - // repo's `canonical_name` by stripping any trailing `+` suffix produced by - // bzlmod's canonical-name scheme. This is best-effort: it works for the no-version-conflict - // case (canonical = "+" or "+"). Module-extension repos do not appear - // in `bazel mod graph` at all, so they get no synthetic dep edges -- their contents are - // captured via repo metadata + the per-repo content hash below. - val moduleNameToCanonical = mutableMapOf() - for (repo in repos) { - val canonical = repo.canonicalName - val moduleName = canonical.substringBefore('+').ifEmpty { canonical } - // Only register a name -> canonical edge if the canonical "looks like a module repo" - // (single `+`, no extension separator). Skip extension-generated repos like - // "rules_jvm_external++maven+maven". - if (canonical.count { it == '+' } == 1) { - moduleNameToCanonical[moduleName] = canonical - } - } - val canonicalToRootApparent: Map> = - canonicalToApparent.mapValues { it.value.toList() } - - val targets = mutableListOf() - for (repo in repos) { - // Derive this repo's bzlmod module name from its canonical name and look up its direct - // deps in the module graph. Translate each dep's module name -> its canonical name -> - // root-visible apparent name; that's what `BazelRule.transformRuleInput` collapses - // non-fine-grained `@//...` rule_inputs to, so adding `//external:` - // as a rule_input here is what wires up the dep chain. - val moduleName = - repo.canonicalName.takeIf { it.count { c -> c == '+' } == 1 }?.substringBefore('+') - val depApparentNames = - if (moduleName != null) { - moduleDepEdges[moduleName] - .orEmpty() - .mapNotNull { moduleNameToCanonical[it] } - .flatMap { canonicalToRootApparent[it].orEmpty() } - } else { - emptyList() - } - val apparentNames = canonicalToApparent[repo.canonicalName] - if (apparentNames != null) { - for (apparentName in apparentNames) { - targets.add(repositoryToTarget(repo, apparentName, depApparentNames)) - } - } else { - // Fallback: use canonical name if no apparent name mapping exists - targets.add(repositoryToTarget(repo, repo.canonicalName, depApparentNames)) - } - } - - logger.i { "Parsed ${repos.size} bzlmod repos → ${targets.size} synthetic targets" } - return targets - } - - /** - * Converts a Build.Repository proto into a synthetic BazelTarget.Rule named - * `//external:`. This mirrors how WORKSPACE repos appear as `//external:*` targets, - * and matches the names produced by `transformRuleInput` in BazelRule.kt. - * - * For each bzlmod dep of this repo (as discovered from `bazel mod graph`) a corresponding - * `//external:` is added to the rule's `rule_input` list, so [RuleHasher] - * follows the dep chain when computing the digest. For repos backed by a `local_repository` rule - * (which is what `local_path_override` lowers to), the contents of the local directory are also - * rolled into a synthetic `_bazel_diff_content_hash` attribute so file content changes inside the - * repo flip the synthetic target's hash. - */ - private fun repositoryToTarget( - repo: Build.Repository, - targetName: String, - depApparentNames: List - ): BazelTarget.Rule { - val ruleClass = repo.repoRuleName.ifEmpty { "bzlmod_repo" } - - val attributes = repo.attributeList.toMutableList() - val contentHash = computeLocalRepoContentHash(repo) - if (contentHash != null) { - attributes.add( - Build.Attribute.newBuilder() - .setName("_bazel_diff_content_hash") - .setType(Build.Attribute.Discriminator.STRING) - .setStringValue(contentHash) - .build()) - } - - val ruleBuilder = - Build.Rule.newBuilder() - .setName("//external:$targetName") - .setRuleClass(ruleClass) - .addAllAttribute(attributes) - for (dep in depApparentNames.toSortedSet()) { - if (dep != targetName) ruleBuilder.addRuleInput("//external:$dep") - } - - val target = - Build.Target.newBuilder() - .setType(Build.Target.Discriminator.RULE) - .setRule(ruleBuilder) - .build() - return BazelTarget.Rule(target) - } - - /** - * Returns a stable hex sha256 over the files inside a `local_repository`-backed repo on disk, or - * null if the repo is not local-backed or the directory cannot be read. - * - * `local_path_override(module_name = "X", path = "...")` in MODULE.bazel lowers to a - * `local_repository` rule, whose `path` attribute is relative to the workspace root. Hashing that - * directory makes file content edits surface in the synthetic //external:X target's digest, which - * fixes the "external repo file change is invisible" half of - * [#184](https://github.com/Tinder/bazel-diff/issues/184) / - * [#197](https://github.com/Tinder/bazel-diff/issues/197). - */ - private fun computeLocalRepoContentHash(repo: Build.Repository): String? { - if (repo.repoRuleName != "local_repository") return null - val pathAttr = - repo.attributeList.find { - it.name == "path" && it.type == Build.Attribute.Discriminator.STRING - } ?: return null - val pathStr = - pathAttr.stringValue.ifEmpty { - return null - } - val rawPath = java.nio.file.Paths.get(pathStr) - val repoDir = - (if (rawPath.isAbsolute) rawPath.toFile() else workingDirectory.resolve(rawPath).toFile()) - if (!repoDir.exists() || !repoDir.isDirectory) return null - - return try { - val digest = java.security.MessageDigest.getInstance("SHA-256") - repoDir - .walkTopDown() - .filter { it.isFile } - // Skip MODULE.bazel.lock: bazel auto-regenerates it on every invocation in ways - // that don't reflect a real source change (it depends on resolution state). Letting - // it flip the content hash makes generate-hashes non-deterministic across runs. - .filter { it.name != "MODULE.bazel.lock" } - .map { Pair(it.relativeTo(repoDir).invariantSeparatorsPath, it) } - .sortedBy { it.first } - .forEach { (relPath, file) -> - digest.update(relPath.toByteArray(Charsets.UTF_8)) - digest.update(0x00) - digest.update(file.readBytes()) - digest.update(0x00) - } - digest.digest().joinToString("") { "%02x".format(it) } - } catch (e: Exception) { - logger.w { "Failed to content-hash local repo at $repoDir: ${e.message}" } - null - } - } - - /** - * Discovers the root module's apparent→canonical repo name mapping by running `bazel mod - * dump_repo_mapping ""`. Returns a map of apparent name → canonical name. Filters out internal - * repos (bazel_tools, _builtins, local_config_*) that aren't relevant for dependency hashing. - */ - @OptIn(ExperimentalCoroutinesApi::class) - private suspend fun discoverRepoMapping(): Map { - val cmd: MutableList = - ArrayList().apply { - add(bazelPath.toString()) - if (noBazelrc) { - add("--bazelrc=/dev/null") - } - addAll(startupOptions) - add("mod") - add("dump_repo_mapping") - // Empty string = root module's repo mapping - add("") - } - - logger.i { "Discovering repo mapping: ${cmd.joinToString()}" } - val result = - process( - *cmd.toTypedArray(), - stdout = Redirect.CAPTURE, - workingDirectory = workingDirectory.toFile(), - stderr = Redirect.PRINT, - destroyForcibly = true, - ) - - if (result.resultCode != 0) { - logger.w { "bazel mod dump_repo_mapping failed (exit code ${result.resultCode})" } - return emptyMap() - } - - return try { - val mapping = mutableMapOf() - for (line in result.output) { - val trimmed = line.trim() - if (trimmed.isEmpty()) continue - val json = com.google.gson.JsonParser.parseString(trimmed).asJsonObject - for ((apparent, canonicalElem) in json.entrySet()) { - val canonical = canonicalElem.asString - // Skip internal/infrastructure repos not relevant for dependency hashing. - if (apparent.isEmpty() || - canonical.isEmpty() || - canonical.startsWith("bazel_tools") || - canonical.startsWith("_builtins") || - canonical.startsWith("local_config_") || - canonical.startsWith("rules_java_builtin") || - apparent == "bazel_tools" || - apparent == "local_config_platform") - continue - mapping[apparent] = canonical - } - } - mapping - } catch (e: Exception) { - logger.w { "Failed to parse dump_repo_mapping output: ${e.message}" } - emptyMap() - } - } - - private fun toBazelTarget(target: Build.Target): BazelTarget? { - return when (target.type) { - Build.Target.Discriminator.RULE -> BazelTarget.Rule(target) - Build.Target.Discriminator.SOURCE_FILE -> BazelTarget.SourceFile(target) - Build.Target.Discriminator.GENERATED_FILE -> BazelTarget.GeneratedFile(target) - Build.Target.Discriminator.PACKAGE_GROUP -> packageGroupToTarget(target) - else -> { - logger.w { "Unsupported target type in the build graph: ${target.type.name}" } - null - } - } - } - - /** - * Lowers a PACKAGE_GROUP query target into a synthetic RULE target so it flows through the - * regular rule-hashing pipeline instead of being dropped (issue #441): the group's `packages` - * allow-list becomes a hashed attribute, and its `includes` become rule_inputs so an edit to a - * nested package_group propagates to every group that includes it. [RuleHasher] separately - * follows rules' `visibility` attributes to these targets, which is what carries a group edit to - * the rules the group gates -- and from there, transitively, to their dependents. - */ - private fun packageGroupToTarget(target: Build.Target): BazelTarget.Rule { - val packageGroup = target.packageGroup - val rule = - Build.Rule.newBuilder() - .setName(packageGroup.name) - .setRuleClass("package_group") - .addAttribute( - Build.Attribute.newBuilder() - .setName("packages") - .setType(Build.Attribute.Discriminator.STRING_LIST) - .addAllStringListValue(packageGroup.containedPackageList)) - .addAllRuleInput(packageGroup.includedPackageGroupList) - return BazelTarget.Rule( - Build.Target.newBuilder().setType(Build.Target.Discriminator.RULE).setRule(rule).build()) - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelRule.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelRule.kt deleted file mode 100644 index d4f57602..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelRule.kt +++ /dev/null @@ -1,169 +0,0 @@ -package com.bazel_diff.bazel - -import com.bazel_diff.hash.safePutBytes -import com.bazel_diff.hash.sha256 -import com.google.devtools.build.lib.query2.proto.proto2api.Build - -// Ignore generator_location when computing a target's hash since it is likely to change and does -// not -// affect a target's generated actions. Internally, Bazel also does this when computing a target's -// hash: -// https://github.com/bazelbuild/bazel/blob/6971b016f1e258e3bb567a0f9fe7a88ad565d8f2/src/main/java/com/google/devtools/build/lib/query2/query/output/SyntheticAttributeHashCalculator.java#L78-L81 -private val DEFAULT_IGNORED_ATTRS = arrayOf("generator_location") - -// Separator used to fold a cquery dep-edge's `configurationChecksum` into a rule-input string. -// Bazel target/package names are restricted to `[A-Za-z0-9/._+=,@~-]*` plus `:` between package -// and target, so `|` is invalid in a label and is a safe in-band encoding that survives the -// existing `List` return type of `ruleInputList()`. `RuleHasher` splits on this character -// to recover the bare label for `allRulesMap` / `sourceDigests` lookups and dep tracking; the -// full encoded string is what gets mixed into the transitive hash, so two configured graphs that -// share dep labels but differ in per-edge configuration produce distinct rule digests. See #359. -const val CONFIGURED_RULE_INPUT_SEPARATOR: Char = '|' - -fun encodeConfiguredRuleInput(input: Build.ConfiguredRuleInput): String { - // Fall back to a bare label when the checksum is empty -- this matches the pre-#359 behaviour - // for the known Bazel quirk where cquery's `transitions=lite` output sometimes omits the - // configurationChecksum for an edge, and avoids appending a dangling separator. - val checksum = input.configurationChecksum - return if (checksum.isNullOrEmpty()) { - input.label - } else { - "${input.label}${CONFIGURED_RULE_INPUT_SEPARATOR}${checksum}" - } -} - -fun decodeConfiguredRuleInputLabel(encoded: String): String { - // No separator present -> the input is a bare label (non-cquery callers, empty-checksum - // fallback, or //external:* synthetic inputs), so this is a no-op. - return encoded.substringBefore(CONFIGURED_RULE_INPUT_SEPARATOR) -} - -class BazelRule(private val rule: Build.Rule) { - fun digest(ignoredAttrs: Set): ByteArray { - return sha256 { - safePutBytes(rule.ruleClassBytes.toByteArray()) - safePutBytes(rule.nameBytes.toByteArray()) - safePutBytes(rule.skylarkEnvironmentHashCodeBytes.toByteArray()) - // Hash attributes in a canonical (name-sorted) order so a rule's digest is invariant to the - // order Bazel happens to emit them in. Attribute names are unique within a rule (the proto - // is generated from the rule's attribute map), so a name sort fully canonicalizes the set. - // Mirrors target-determinator's `sortedAttributesForHashing` (commit d4b6125). - for (attribute in rule.attributeList.sortedBy { it.name }) { - if (!DEFAULT_IGNORED_ATTRS.contains(attribute.name) && - !ignoredAttrs.contains(attribute.name)) - safePutBytes(attribute.toByteArray()) - } - } - } - - fun ruleInputList(useCquery: Boolean, fineGrainedHashExternalRepos: Set): List { - // Transformed //external:* synthetic inputs so that targets depending on external repos pick - // up hash changes from //external:* synthetic targets (e.g. from bzlmod mod show_repo or - // WORKSPACE //external:all-targets). - val externalSyntheticInputs = - rule.ruleInputList - .map { ruleInput: String -> - transformRuleInput(fineGrainedHashExternalRepos, ruleInput) - } - .filter { it.startsWith("//external:") } - - val inputs = - if (useCquery) { - // configuredRuleInputList already covers the non-fine-grained deps (with per-edge - // configuration folded in), so only the //external:* synthetic inputs are added. - rule.configuredRuleInputList.map { encodeConfiguredRuleInput(it) } + - externalSyntheticInputs - } else { - rule.ruleInputList + externalSyntheticInputs - } - - // Canonicalize: dedupe and sort so a target's digest is invariant to the order Bazel happens - // to emit (configured) rule inputs in. This matters most under --useCquery, where the same dep - // label can surface across multiple configurations and cquery does not guarantee a stable - // emission order -- without canonicalization an unchanged target could hash differently - // between two otherwise-identical graphs. RuleHasher mixes these bytes into the digest in list - // order, so a deterministic order is what keeps the hash stable. Mirrors - // target-determinator's `canonicalizeRuleInputs` (commit d4b6125). - return inputs.distinct().sorted() - } - - val name: String = rule.name - - /** - * True for `package_group` targets. `bazel query` reports them under their own PACKAGE_GROUP - * discriminator; [BazelQueryService] lowers them to synthetic rules with this rule class so they - * participate in hashing (issue #441). - */ - val isPackageGroup: Boolean - get() = rule.ruleClass == "package_group" - - /** - * The package_group labels referenced by this rule's `visibility` attribute. A visibility value - * is either a package_group label or one of the special forms -- `//visibility:public`, - * `//visibility:private`, or a package spec (`//foo:__pkg__` / `//foo:__subpackages__`) -- which - * name no target of their own and are filtered out here. - * - * Visibility labels are "nodep" labels: `bazel query` never lists them in `rule_input`, so - * [ruleInputList] cannot surface them and [com.bazel_diff.hash.RuleHasher] follows them - * explicitly instead (issue #441). - */ - fun visibilityPackageGroupLabels(): List = - rule.attributeList - .firstOrNull { it.name == "visibility" } - ?.stringListValueList - .orEmpty() - .filter { - !it.startsWith("//visibility:") && - !it.endsWith(":__pkg__") && - !it.endsWith(":__subpackages__") - } - .distinct() - .sorted() - - /** - * Macro instantiation frames (`"::: "`) naming the BUILD/`.bzl` files that - * produced this rule. Empty unless the query ran with `--proto:instantiation_stack`. - */ - val instantiationStack: List - get() = rule.instantiationStackList - - /** - * The values of this rule's `tags` attribute, or an empty set when the attribute is absent. Bazel - * emits user-declared tags as a `STRING_LIST` attribute named `tags` in the query proto. - * bazel-diff reads them to honour `--alwaysAffectedTags`: a target carrying one of those tags is - * treated as always impacted, because it may read undeclared workspace state at execution time - * (issue #401). This is the Bazel target *tag* `external`, which is unrelated to the external - * *repository* handling behind `--excludeExternalTargets` / `--fineGrainedHashExternalRepos`. - */ - fun tags(): Set = - rule.attributeList.firstOrNull { it.name == "tags" }?.stringListValueList?.toSet() - ?: emptySet() - - private fun transformRuleInput( - fineGrainedHashExternalRepos: Set, - ruleInput: String - ): String { - if (isNotMainRepo(ruleInput) && - ruleInput.startsWith("@") && - // Match on the full repo name (up to the `//` boundary), not a bare string - // prefix: with hub-and-spoke repos (e.g. rules_python's `@pip` hub and - // `@pip_` spokes), a bare prefix check makes every spoke label look - // like it belongs to the fine-grained hub, so it is never rewritten to its - // `//external:` seed and changes stop propagating. - fineGrainedHashExternalRepos.none { ruleInput == it || ruleInput.startsWith("$it//") }) { - val splitRule = ruleInput.split("//".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (splitRule.size == 2) { - var externalRule = splitRule[0] - externalRule = externalRule.replaceFirst("@+".toRegex(), "") - return String.format("//external:%s", externalRule) - } - } - return ruleInput - } - - private fun isNotMainRepo(ruleInput: String): Boolean { - return !ruleInput.startsWith("//") && - !ruleInput.startsWith("@//") && - !ruleInput.startsWith("@@//") - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelSourceFileTarget.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelSourceFileTarget.kt deleted file mode 100644 index f8fd863d..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelSourceFileTarget.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.bazel_diff.bazel - -class BazelSourceFileTarget( - val name: String, - val seed: ByteArray, -) diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelTarget.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelTarget.kt deleted file mode 100644 index efa9a38c..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelTarget.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.bazel_diff.bazel - -import com.google.devtools.build.lib.query2.proto.proto2api.Build - -sealed class BazelTarget(private val target: Build.Target) { - class SourceFile(target: Build.Target) : BazelTarget(target) { - init { - assert(target.hasSourceFile()) - } - - val sourceFileName: String = target.sourceFile.name - val subincludeList: List = target.sourceFile.subincludeList - override val name: String - get() = sourceFileName - } - - class Rule(target: Build.Target) : BazelTarget(target) { - init { - assert(target.hasRule()) - } - - val rule: BazelRule = BazelRule(target.rule) - override val name: String - get() = rule.name - } - - class GeneratedFile(target: Build.Target) : BazelTarget(target) { - init { - assert(target.hasGeneratedFile()) - } - - val generatedFileName: String = target.generatedFile.name - val generatingRuleName: String = target.generatedFile.generatingRule - override val name: String - get() = generatedFileName - } - - val type: BazelTargetType - get() = - when (target.type) { - Build.Target.Discriminator.RULE -> BazelTargetType.RULE - Build.Target.Discriminator.SOURCE_FILE -> BazelTargetType.SOURCE_FILE - Build.Target.Discriminator.GENERATED_FILE -> BazelTargetType.GENERATED_FILE - Build.Target.Discriminator.PACKAGE_GROUP -> BazelTargetType.PACKAGE_GROUP - Build.Target.Discriminator.ENVIRONMENT_GROUP -> BazelTargetType.ENVIRONMENT_GROUP - else -> BazelTargetType.UNKNOWN - } - - abstract val name: String -} diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelTargetType.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelTargetType.kt deleted file mode 100644 index 88a0e8d4..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelTargetType.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.bazel_diff.bazel - -enum class BazelTargetType { - RULE, - SOURCE_FILE, - GENERATED_FILE, - PACKAGE_GROUP, - ENVIRONMENT_GROUP, - UNKNOWN -} diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/ModuleGraphParser.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/ModuleGraphParser.kt deleted file mode 100644 index f5e64b04..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/ModuleGraphParser.kt +++ /dev/null @@ -1,309 +0,0 @@ -package com.bazel_diff.bazel - -import com.google.gson.JsonObject -import com.google.gson.JsonParser - -/** Data class representing a module in the dependency graph. */ -data class Module(val key: String, val name: String, val version: String, val apparentName: String) - -/** - * Parses and compares Bazel module graphs to detect changes. - * - * Instead of including the entire module graph in the hash seed (which causes all targets to rehash - * when MODULE.bazel changes), this class identifies which specific modules changed so we can query - * only the targets that depend on those modules. - */ -class ModuleGraphParser { - /** - * Parses the JSON output from `bazel mod graph --output=json`. - * - * Tolerates a non-JSON prefix (e.g. leaked stderr from bazel-diff 17.0.1..18.0.5, which captured - * stderr into moduleGraphJson via Process.kt's captureAll -> - * ProcessBuilder.redirectErrorStream(true)). - * - * @param json The JSON string from bazel mod graph - * @return A map of module keys to Module objects - */ - fun parseModuleGraph(json: String): Map { - val modules = mutableMapOf() - - try { - val root = - try { - JsonParser.parseString(json).asJsonObject - } catch (_: Exception) { - val start = json.indexOf('{') - if (start < 0) return emptyMap() - JsonParser.parseString(json.substring(start)).asJsonObject - } - extractModules(root, modules) - } catch (e: Exception) { - // If parsing fails, return empty map - return emptyMap() - } - - return modules - } - - private fun extractModules(obj: JsonObject, modules: MutableMap) { - val key = obj.get("key")?.asString - val name = obj.get("name")?.asString - val version = obj.get("version")?.asString - val apparentName = obj.get("apparentName")?.asString - - // Bazel's MODULE.bazel spec requires module names to be non-empty; reject - // empty `name` so the synthetic `` entry of an unnamed MODULE.bazel - // doesn't reach downstream canonical-repo matching. - if (key != null && !name.isNullOrEmpty() && version != null && apparentName != null) { - modules[key] = Module(key, name, version, apparentName) - } - - // Recursively extract from dependencies - obj.get("dependencies")?.asJsonArray?.forEach { dep -> - if (dep.isJsonObject) { - extractModules(dep.asJsonObject, modules) - } - } - } - - /** - * Parses the JSON from `bazel mod graph --output=json` and returns each module's direct - * `bazel_dep` neighbours as a `module_name -> [dep_module_name, ...]` map. - * - * Module names (the `name` field of the `module(name = ...)` declaration) are used as the key - * here because the alternative -- `module_key` -- is not always populated on the - * `Build.Repository` protos returned by `bazel mod show_repo`, which is what consumers want to - * look up against. Module names are universally present and sufficient to find a unique row in - * the graph for the common no-multi-version case. - * - * The same module may appear in multiple places in the JSON tree (`bazel mod graph` inlines each - * module once and references it via `unexpanded` afterwards). This method walks every - * `dependencies` array it sees, so even the `unexpanded` references contribute an edge. The - * resulting map is keyed by the parent's `module_name` and contains the union of all direct dep - * names observed across the tree. - * - * Returns an empty map on parse failure (same tolerance as [parseModuleGraph]). - */ - fun parseModuleGraphDepEdges(json: String): Map> { - val edges = mutableMapOf>() - try { - val root = - try { - JsonParser.parseString(json).asJsonObject - } catch (_: Exception) { - val start = json.indexOf('{') - if (start < 0) return emptyMap() - JsonParser.parseString(json.substring(start)).asJsonObject - } - extractDepEdges(root, edges) - } catch (_: Exception) { - return emptyMap() - } - return edges.mapValues { it.value.toList() } - } - - private fun extractDepEdges(obj: JsonObject, edges: MutableMap>) { - val name = obj.get("name")?.asString ?: return - val deps = obj.get("dependencies")?.asJsonArray ?: return - val collected = edges.getOrPut(name) { mutableSetOf() } - for (dep in deps) { - if (!dep.isJsonObject) continue - val depObj = dep.asJsonObject - val depName = depObj.get("name")?.asString ?: continue - collected.add(depName) - // Even if this child is `unexpanded`, recurse to pick up edges from its own expansion - // elsewhere in the tree. - extractDepEdges(depObj, edges) - } - } - - /** - * Returns a copy of [edges] with back-edges removed so the result is acyclic. - * - * `bazel mod graph` legitimately contains cycles: for example `rules_go` declares `bazel_dep(name - * = "gazelle", dev_dependency = True)` while `gazelle` declares `bazel_dep(name = "rules_go")`, - * so the dep graph has `rules_go <-> gazelle`. Feeding both edges into - * [BazelQueryService.queryBzlmodRepos] as `rule_input`s on the synthetic `//external:*` targets - * makes `RuleHasher` recurse infinitely and throw `CircularDependencyException`. We need a - * cycle-free dep DAG before emitting edges. - * - * The algorithm is a single DFS, visiting nodes in lexicographic order with their out-edges also - * sorted. An edge to a node currently on the DFS path is a back-edge (it would close a cycle) and - * is dropped; every other edge is kept. The result is therefore (a) acyclic and (b) deterministic - * across runs. - * - * Dropping the back-edge is conservative: a content change in the dropped-edge target still - * surfaces via its own synthetic `//external:*` target's hash (each repo gets one), so main-repo - * consumers that depend on either side of the cycle still see the change. We only lose the - * ability to propagate through the cycle itself, which is fine because all SCC members are - * co-dependent and a change in any of them already invalidates their own hashes directly. - */ - fun breakCycles(edges: Map>): Map> { - val result = mutableMapOf>() - val visited = mutableSetOf() - val onPath = mutableSetOf() - - fun dfs(node: String) { - if (node in visited) return - onPath.add(node) - val kept = mutableListOf() - for (target in edges[node].orEmpty().sorted()) { - if (target in onPath) continue // back-edge - kept.add(target) - dfs(target) - } - result[node] = kept - onPath.remove(node) - visited.add(node) - } - - for (node in edges.keys.sorted()) dfs(node) - return result - } - - /** - * Result of parsing the bzlmod dependency graph as edges keyed by `apparentName`. - * - * Distinct from [parseModuleGraphDepEdges] (which is keyed by `name` for use by - * [BazelQueryService.queryBzlmodRepos] when synthesising `//external:` rule_input edges): - * this view keys by `apparentName` because the consumer is `--fineGrainedHashExternalRepos`, - * whose tokens are apparent repo names (e.g. `@inner_repo`). - * - * @property edges Map from a module's `apparentName` to the set of `apparentName`s of its direct - * bzlmod dependencies. Populated for every module reached when walking the JSON tree. - * @property rootApparentNames `apparentName`s for the root module(s) (key ``). The root is - * the main repo and is always queried via `//...:all-targets`, so dependents-expansion should - * never add it to a fine-grained set. - */ - data class GraphEdges( - val edges: Map>, - val rootApparentNames: Set, - ) - - /** - * Parses `bazel mod graph --output=json` into apparent-name dependency edges. Tolerates a - * non-JSON prefix (e.g. leaked stderr) using the same recovery as [parseModuleGraph]. - * - * Each module in the JSON tree contributes an edge for every entry in its `dependencies` array. - * `unexpanded` dependency stubs (modules that the bzlmod resolver already described elsewhere in - * the graph) still contribute the edge but are not recursed into to avoid duplicate work. - */ - fun parseModuleGraphEdges(json: String): GraphEdges { - val edges = mutableMapOf>() - val rootApparentNames = mutableSetOf() - try { - val root = - try { - JsonParser.parseString(json).asJsonObject - } catch (_: Exception) { - val start = json.indexOf('{') - if (start < 0) return GraphEdges(emptyMap(), emptySet()) - JsonParser.parseString(json.substring(start)).asJsonObject - } - walkEdges(root, edges, rootApparentNames, isRoot = true) - } catch (_: Exception) { - return GraphEdges(emptyMap(), emptySet()) - } - return GraphEdges(edges.mapValues { it.value.toSet() }, rootApparentNames.toSet()) - } - - private fun walkEdges( - obj: JsonObject, - edges: MutableMap>, - rootApparentNames: MutableSet, - isRoot: Boolean, - ) { - val apparentName = obj.get("apparentName")?.asString ?: return - if (isRoot) rootApparentNames.add(apparentName) - val mySet = edges.getOrPut(apparentName) { mutableSetOf() } - val deps = obj.get("dependencies")?.asJsonArray ?: return - for (dep in deps) { - if (!dep.isJsonObject) continue - val depObj = dep.asJsonObject - val depApparent = depObj.get("apparentName")?.asString ?: continue - mySet.add(depApparent) - val unexpanded = depObj.get("unexpanded")?.asBoolean ?: false - if (!unexpanded) { - walkEdges(depObj, edges, rootApparentNames, isRoot = false) - } - } - } - - /** - * Computes the set of `apparentName`s that transitively depend on any module in [targets] by - * traversing [edges] in reverse. Excludes [rootApparentNames] from the result. - * - * Background: `--fineGrainedHashExternalRepos` opts a leaf module (e.g. `@inner_repo`) into - * per-target hashing. When another bzlmod module (`@middle_repo`) wraps it via alias or re-export - * and the main repo depends only on the wrapper, the wrapper's targets must also be queried for - * the hash chain to reach the leaf. This is the engine for that auto-expansion (issue #197). The - * set returned here is what the caller should add to the user-supplied set. - */ - fun findTransitiveDependents( - edges: Map>, - targets: Set, - rootApparentNames: Set, - ): Set { - if (edges.isEmpty() || targets.isEmpty()) return emptySet() - val reverse = mutableMapOf>() - for ((from, deps) in edges) { - for (dep in deps) { - reverse.getOrPut(dep) { mutableSetOf() }.add(from) - } - } - val out = mutableSetOf() - val visited = targets.toMutableSet() - val queue: ArrayDeque = ArrayDeque(targets) - while (queue.isNotEmpty()) { - val cur = queue.removeFirst() - val parents = reverse[cur] ?: continue - for (p in parents) { - if (p in visited) continue - visited.add(p) - if (p !in rootApparentNames) out.add(p) - queue.addLast(p) - } - } - return out - } - - /** - * Compares two module graphs and returns the keys of modules that changed. - * - * A module is considered changed if: - * - It exists in the new graph but not the old graph (added) - * - It exists in the old graph but not the new graph (removed) - * - It exists in both but has a different version - * - * @param oldGraph Module graph from the starting revision - * @param newGraph Module graph from the final revision - * @return Set of module keys that changed - */ - fun findChangedModules( - oldGraph: Map, - newGraph: Map - ): Set { - val changed = mutableSetOf() - - // Find added and version-changed modules - newGraph.forEach { (key, newModule) -> - val oldModule = oldGraph[key] - if (oldModule == null) { - // Module was added - changed.add(key) - } else if (oldModule.version != newModule.version) { - // Module version changed - changed.add(key) - } - } - - // Find removed modules - oldGraph.keys.forEach { key -> - if (!newGraph.containsKey(key)) { - changed.add(key) - } - } - - return changed - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/BazelDiff.kt b/cli/src/main/kotlin/com/bazel_diff/cli/BazelDiff.kt deleted file mode 100644 index b95b29d3..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/BazelDiff.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.bazel_diff.cli - -import picocli.CommandLine -import picocli.CommandLine.Model.CommandSpec -import picocli.CommandLine.Spec - -@CommandLine.Command( - name = "bazel-diff", - description = ["Writes to a file the impacted targets between two Bazel graph JSON files"], - subcommands = - [ - GenerateHashesCommand::class, - GetImpactedTargetsCommand::class, - WarmupCommand::class, - FingerprintCommand::class, - ServeCommand::class], - mixinStandardHelpOptions = true, - versionProvider = VersionProvider::class, -) -class BazelDiff : Runnable { - @CommandLine.Option( - names = ["-v", "--verbose"], - description = ["Display query string, missing files and elapsed time"], - scope = CommandLine.ScopeType.INHERIT, - ) - var verbose = false - - @CommandLine.Option( - names = ["--debug"], - hidden = true, - scope = CommandLine.ScopeType.INHERIT, - defaultValue = "\${sys:DEBUG:-false}") - var debug = false - - @Spec lateinit var spec: CommandSpec - - override fun run() { - throw CommandLine.ParameterException(spec.commandLine(), "Missing required subcommand") - } - - fun isVerbose(): Boolean { - return verbose || debug - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/FingerprintCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/FingerprintCommand.kt deleted file mode 100644 index cf7479b0..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/FingerprintCommand.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.bazel_diff.cli - -import com.bazel_diff.cli.converter.CommaSeparatedValueConverter -import com.bazel_diff.cli.converter.NormalisingPathConverter -import com.bazel_diff.cli.converter.OptionsConverter -import com.bazel_diff.interactor.FingerprintInteractor -import com.google.gson.GsonBuilder -import java.io.File -import java.nio.file.Path -import java.util.concurrent.Callable -import picocli.CommandLine - -/** - * Computes the Firecracker snapshot cache key ("fingerprint") for the current workspace + flag set - * and writes it as JSON. Used at record time to tag a snapshot and at consume time to validate a - * candidate snapshot before trusting it. See `docs/firecracker-snapshots.md` §5. - * - * Pure metadata: this command does not run `bazel query` and is cheap. The only external call is - * `bazel version`. - */ -@CommandLine.Command( - name = "fingerprint", - mixinStandardHelpOptions = true, - description = - [ - "Computes the snapshot cache key over the inputs that affect the build graph " + - "(bazel version, MODULE.bazel.lock, .bazelrc, bazel-diff version, flag set) and " + - "writes it as JSON. Used to decide whether a Firecracker snapshot is safe to consume."], - versionProvider = VersionProvider::class) -class FingerprintCommand : Callable { - @CommandLine.ParentCommand private lateinit var parent: BazelDiff - - @CommandLine.Option( - names = ["-w", "--workspacePath"], - description = ["Path to Bazel workspace directory."], - scope = CommandLine.ScopeType.INHERIT, - required = true, - converter = [NormalisingPathConverter::class]) - lateinit var workspacePath: Path - - @CommandLine.Option( - names = ["-b", "--bazelPath"], - description = - [ - "Path to Bazel binary. If not specified, the Bazel binary available in PATH will be used."], - scope = CommandLine.ScopeType.INHERIT, - defaultValue = "bazel", - ) - lateinit var bazelPath: Path - - // --- query-affecting flags (must match the corresponding generate-hashes flags) --- - - @CommandLine.Option( - names = ["-so", "--bazelStartupOptions"], - converter = [OptionsConverter::class], - description = - ["Bazel client startup options (must match the consuming generate-hashes run)."]) - var bazelStartupOptions: List = emptyList() - - @CommandLine.Option( - names = ["-co", "--bazelCommandOptions"], - converter = [OptionsConverter::class], - description = ["Bazel command options for `bazel query`."]) - var bazelCommandOptions: List = emptyList() - - @CommandLine.Option( - names = ["--cqueryCommandOptions"], - converter = [OptionsConverter::class], - description = ["Bazel command options for `bazel cquery`."]) - var cqueryCommandOptions: List = emptyList() - - @CommandLine.Option(names = ["--useCquery"], negatable = true) var useCquery = false - - @CommandLine.Option(names = ["--cqueryExpression"]) var cqueryExpression: String? = null - - @CommandLine.Option(names = ["--includeTargetType"], negatable = true) - var includeTargetType = false - - @CommandLine.Option(names = ["-tt", "--targetType"], split = ",") - var targetType: Set? = null - - @CommandLine.Option( - names = ["--fineGrainedHashExternalRepos"], converter = [CommaSeparatedValueConverter::class]) - var fineGrainedHashExternalRepos: Set = emptySet() - - @CommandLine.Option( - names = ["--ignoredRuleHashingAttributes"], converter = [CommaSeparatedValueConverter::class]) - var ignoredRuleHashingAttributes: Set = emptySet() - - @CommandLine.Option(names = ["--excludeExternalTargets"], negatable = true) - var excludeExternalTargets = false - - @CommandLine.Option( - names = ["-k", "--keep_going"], - negatable = true, - defaultValue = "false", - fallbackValue = "true") - var keepGoing = false - - @CommandLine.Option( - names = ["-o", "--output"], - description = ["Path to write the fingerprint JSON. Defaults to STDOUT."], - defaultValue = CommandLine.Parameters.NULL_VALUE) - var outputPath: File? = null - - override fun call(): Int { - val flags = - FingerprintGatherer.canonicalizeFlags( - bazelStartupOptions = bazelStartupOptions, - bazelCommandOptions = bazelCommandOptions, - cqueryCommandOptions = cqueryCommandOptions, - useCquery = useCquery, - cqueryExpression = cqueryExpression, - includeTargetType = includeTargetType, - targetType = targetType, - fineGrainedHashExternalRepos = fineGrainedHashExternalRepos, - ignoredRuleHashingAttributes = ignoredRuleHashingAttributes, - excludeExternalTargets = excludeExternalTargets, - keepGoing = keepGoing, - ) - val inputs = - FingerprintGatherer.gather( - workspacePath = workspacePath, - bazelPath = bazelPath, - bazelDiffVersion = VersionProvider().version.firstOrNull() ?: "unknown", - flags = flags, - ) - - val result = FingerprintInteractor().compute(inputs) - val json = renderFingerprintJson(result, inputs.flags) - - val out = outputPath - if (out != null && out.path != "-") { - out.writeText(json + "\n") - } else { - println(json) - } - return CommandLine.ExitCode.OK - } -} - -/** Renders the fingerprint result + flag set as pretty JSON. Shared with [WarmupCommand]. */ -internal fun renderFingerprintJson( - result: com.bazel_diff.interactor.FingerprintResult, - flags: Map, -): String { - val gson = GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create() - return gson.toJson( - linkedMapOf( - "fingerprint" to result.fingerprint, - "components" to result.components, - "flags" to flags.toSortedMap(), - )) -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/FingerprintGatherer.kt b/cli/src/main/kotlin/com/bazel_diff/cli/FingerprintGatherer.kt deleted file mode 100644 index fdfdf0a3..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/FingerprintGatherer.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.bazel_diff.cli - -import com.bazel_diff.interactor.FingerprintInputs -import java.nio.file.Files -import java.nio.file.Path -import java.util.concurrent.TimeUnit - -/** - * Gathers the environment-specific [FingerprintInputs] (reads `MODULE.bazel.lock`, `.bazelrc` + - * imports, runs `bazel version`, reads bazel-diff's version) and canonicalizes the query-affecting - * flag set. - * - * Shared by [FingerprintCommand] and [WarmupCommand] so the cache key is computed identically on - * the record and consume sides. The pure hashing lives in - * [com.bazel_diff.interactor.FingerprintInteractor]; this object is the IO around it. - */ -object FingerprintGatherer { - - /** Canonicalize the flag set into stable key -> value strings. Lists are joined, sets sorted. */ - fun canonicalizeFlags( - bazelStartupOptions: List, - bazelCommandOptions: List, - cqueryCommandOptions: List, - useCquery: Boolean, - cqueryExpression: String?, - includeTargetType: Boolean, - targetType: Set?, - fineGrainedHashExternalRepos: Set, - ignoredRuleHashingAttributes: Set, - excludeExternalTargets: Boolean, - keepGoing: Boolean, - ): Map { - fun list(xs: List) = xs.joinToString(" ") - fun set(xs: Set) = xs.toSortedSet().joinToString(",") - return linkedMapOf( - "bazelStartupOptions" to list(bazelStartupOptions), - "bazelCommandOptions" to list(bazelCommandOptions), - "cqueryCommandOptions" to list(cqueryCommandOptions), - "useCquery" to useCquery.toString(), - "cqueryExpression" to (cqueryExpression ?: ""), - "includeTargetType" to includeTargetType.toString(), - "targetType" to (targetType?.let { set(it) } ?: ""), - "fineGrainedHashExternalRepos" to set(fineGrainedHashExternalRepos), - "ignoredRuleHashingAttributes" to set(ignoredRuleHashingAttributes), - "excludeExternalTargets" to excludeExternalTargets.toString(), - "keepGoing" to keepGoing.toString(), - ) - } - - fun gather( - workspacePath: Path, - bazelPath: Path, - bazelDiffVersion: String, - flags: Map, - ): FingerprintInputs = - FingerprintInputs( - bazelDiffVersion = bazelDiffVersion, - bazelVersion = readBazelVersion(workspacePath, bazelPath), - moduleLockContent = readOptional(workspacePath.resolve("MODULE.bazel.lock")), - bazelrcContents = readBazelrcs(workspacePath), - flags = flags, - ) - - private fun readOptional(path: Path): ByteArray? = - if (Files.isRegularFile(path)) Files.readAllBytes(path) else null - - /** Read `.bazelrc` plus any files it `import`s / `try-import`s (best-effort, one level deep). */ - private fun readBazelrcs(workspacePath: Path): Map { - val result = LinkedHashMap() - val root = workspacePath.resolve(".bazelrc") - readOptional(root)?.let { bytes -> - result[".bazelrc"] = bytes - String(bytes).lineSequence().forEach { line -> - val trimmed = line.trim() - if (trimmed.startsWith("import ") || trimmed.startsWith("try-import ")) { - val raw = trimmed.substringAfter(' ').trim() - val resolved = raw.replace("%workspace%", workspacePath.toString()) - val p = Path.of(resolved) - val abs = if (p.isAbsolute) p else workspacePath.resolve(resolved) - readOptional(abs)?.let { imported -> - result[workspacePath.relativize(abs).toString()] = imported - } - } - } - } - return result - } - - private fun readBazelVersion(workspacePath: Path, bazelPath: Path): String { - return try { - val proc = - ProcessBuilder(bazelPath.toString(), "version") - .directory(workspacePath.toFile()) - .redirectErrorStream(false) - .start() - val output = proc.inputStream.bufferedReader().readText() - proc.waitFor(60, TimeUnit.SECONDS) - output - .lineSequence() - .firstOrNull { it.startsWith("Build label: ") } - ?.removePrefix("Build label: ") - ?.trim() - ?: output - .lineSequence() - .firstOrNull { it.startsWith("bazel ") } - ?.removePrefix("bazel ") - ?.trim() - ?: "unknown" - } catch (e: Exception) { - "unknown" - } - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt deleted file mode 100644 index cacd855c..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt +++ /dev/null @@ -1,276 +0,0 @@ -package com.bazel_diff.cli - -import com.bazel_diff.cli.converter.CommaSeparatedValueConverter -import com.bazel_diff.cli.converter.NormalisingPathConverter -import com.bazel_diff.cli.converter.OptionsConverter -import com.bazel_diff.di.hasherModule -import com.bazel_diff.di.loggingModule -import com.bazel_diff.di.serialisationModule -import com.bazel_diff.interactor.GenerateHashesInteractor -import java.io.File -import java.nio.file.Path -import java.util.concurrent.Callable -import org.koin.core.context.startKoin -import org.koin.core.context.stopKoin -import picocli.CommandLine - -@CommandLine.Command( - name = "generate-hashes", - mixinStandardHelpOptions = true, - description = - ["Writes to a file the SHA256 hashes for each Bazel Target in the provided workspace."], - versionProvider = VersionProvider::class) -open class GenerateHashesCommand : Callable { - @CommandLine.ParentCommand private lateinit var parent: BazelDiff - - @CommandLine.Option( - names = ["-w", "--workspacePath"], - description = ["Path to Bazel workspace directory."], - scope = CommandLine.ScopeType.INHERIT, - required = true, - converter = [NormalisingPathConverter::class]) - lateinit var workspacePath: Path - - @CommandLine.Option( - names = ["-b", "--bazelPath"], - description = - [ - "Path to Bazel binary. If not specified, the Bazel binary available in PATH will be used."], - scope = CommandLine.ScopeType.INHERIT, - defaultValue = "bazel", - ) - lateinit var bazelPath: Path - - @CommandLine.Option( - names = ["--contentHashPath"], - description = - [ - "Path to content hash json file. It's a map which maps a workspace-relative file path (and `external//` for fine-grained external repo files) to its content hash. Files in this map will skip content hashing and use provided value"], - scope = CommandLine.ScopeType.INHERIT, - required = false) - var contentHashPath: File? = null - - @CommandLine.Option( - names = ["-so", "--bazelStartupOptions"], - description = - ["Additional space separated Bazel client startup options used when invoking Bazel"], - scope = CommandLine.ScopeType.INHERIT, - converter = [OptionsConverter::class], - ) - var bazelStartupOptions: List = emptyList() - - @CommandLine.Option( - names = ["-co", "--bazelCommandOptions"], - description = - ["Additional space separated Bazel command options used when invoking `bazel query`"], - scope = CommandLine.ScopeType.INHERIT, - converter = [OptionsConverter::class], - ) - var bazelCommandOptions: List = emptyList() - - @CommandLine.Option( - names = ["--fineGrainedHashExternalRepos"], - description = - [ - "Comma separate list of external repos in which fine-grained hashes are computed for the targets. By default, external repos are treated as an opaque blob. If an external repo is specified here, bazel-diff instead computes the hash for individual targets. For example, one wants to specify `maven` here if they user rules_jvm_external so that individual third party dependency change won't invalidate all targets in the mono repo."], - scope = CommandLine.ScopeType.INHERIT, - converter = [CommaSeparatedValueConverter::class], - ) - var fineGrainedHashExternalRepos: Set = emptySet() - - @CommandLine.Option( - names = ["--fineGrainedHashExternalReposFile"], - description = - [ - "A text file containing a newline separated list of external repos. Similar to --fineGrainedHashExternalRepos but helps you avoid exceeding max arg length. Mutually exclusive with --fineGrainedHashExternalRepos."]) - var fineGrainedHashExternalReposFile: File? = null - - @CommandLine.Option( - names = ["--useCquery"], - negatable = true, - description = - [ - "If true, use cquery instead of query when generating dependency graphs. Using cquery would yield more accurate build graph at the cost of slower query execution. When this is set, one usually also wants to set `--cqueryCommandOptions` to specify a targeting platform. Note that this flag only works with Bazel 6.2.0 or above because lower versions does not support `--query_file` flag."], - scope = CommandLine.ScopeType.INHERIT) - var useCquery = false - - @CommandLine.Option( - names = ["--includeTargetType"], - negatable = true, - description = - [ - "Whether include target type in the generated JSON or not.\n" + - "If false, the generate JSON schema is: {\"\": \"\"}\n" + - "If true, the generate JSON schema is: {\"\": \"#\" }"], - scope = CommandLine.ScopeType.INHERIT) - var includeTargetType = false - - @CommandLine.Option( - names = ["-tt", "--targetType"], - split = ",", - scope = CommandLine.ScopeType.LOCAL, - description = - [ - "The types of targets to filter. Use comma (,) to separate multiple values, e.g. '--targetType=SourceFile,Rule,GeneratedFile'."]) - var targetType: Set? = null - - @CommandLine.Option( - names = ["--cqueryCommandOptions"], - description = - [ - "Additional space separated Bazel command options used when invoking `bazel cquery`. This flag is has no effect if `--useCquery`is false."], - scope = CommandLine.ScopeType.INHERIT, - converter = [OptionsConverter::class], - ) - var cqueryCommandOptions: List = emptyList() - - @CommandLine.Option( - names = ["--cqueryExpression"], - description = - [ - "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."], - scope = CommandLine.ScopeType.INHERIT) - var cqueryExpression: String? = null - - @CommandLine.Option( - names = ["-k", "--keep_going"], - negatable = true, - defaultValue = "false", - fallbackValue = "true", - description = - [ - "This flag controls if `bazel query` will be executed with the `--keep_going` flag or not. Enabling this flag lets `bazel query` tolerate failures in your Bazel graph, but may silently drop targets that fail to resolve and produce non-deterministic hashes. Disabling it catches configuration issues by failing loudly. Defaults to `false`"], - scope = CommandLine.ScopeType.INHERIT) - var keepGoing = false - - @CommandLine.Option( - names = ["-s", "--seed-filepaths"], - description = - [ - "A text file containing a newline separated list of filepaths. Each file in this list will be read and its content will be used as a SHA256 seed when determining affected targets in the build graph. Invalidating any of these files will effectively mark all targets as affected."]) - var seedFilepaths: File? = null - - @CommandLine.Parameters( - index = "0", - description = - [ - "The filepath to write the resulting JSON of dictionary target => SHA-256 values. If not specified, the JSON will be written to STDOUT."], - defaultValue = CommandLine.Parameters.NULL_VALUE) - var outputPath: File? = null - - @CommandLine.Option( - names = ["--ignoredRuleHashingAttributes"], - description = ["Attributes that should be ignored when hashing rule targets."], - scope = CommandLine.ScopeType.INHERIT, - converter = [CommaSeparatedValueConverter::class], - ) - var ignoredRuleHashingAttributes: Set = emptySet() - - @CommandLine.Option( - names = ["-d", "--depEdgesFile"], - description = - [ - "Path to the file where dependency edges are written to. If not specified, the dependency edges will not be written to a file. Needed for computing build graph distance metrics. See bazel-diff docs for more details about build graph distance metrics."], - scope = CommandLine.ScopeType.INHERIT, - defaultValue = CommandLine.Parameters.NULL_VALUE) - var depsMappingJSONPath: File? = null - - @CommandLine.Option( - names = ["-m", "--modified-filepaths"], - description = - [ - "Experimental: A text file containing a newline separated list of filepaths (relative to the workspace) these filepaths should represent the modified files between the specified revisions and will be used to scope what files are hashed during hash generation."]) - var modifiedFilepaths: File? = null - - @CommandLine.Option( - names = ["--excludeExternalTargets"], - negatable = true, - description = - [ - "If true, exclude external targets (do not query //external:all-targets). When Bzlmod is enabled (detected via bazel mod graph), external targets are excluded automatically. Set this when using Bazel with --enable_workspace=false in other configurations. Defaults to false."], - scope = CommandLine.ScopeType.INHERIT) - var excludeExternalTargets = false - - @CommandLine.Option( - names = ["--excludeTargetsQuery"], - description = - [ - "A Bazel query expression whose matched targets are excluded from the generated " + - "hashes via the `except` operator. Applied to the main target universe for both " + - "`query` and `cquery`. Use this to drop targets you never want reported as " + - "impacted, e.g. `manual`-tagged targets: " + - "--excludeTargetsQuery='attr(\"tags\", \"[\\[ ]manual[,\\]]\", //...)'. " + - "Excluded targets are absent from hashing, so a kept target that depended on one " + - "no longer tracks changes to it."], - scope = CommandLine.ScopeType.INHERIT) - var excludeTargetsQuery: String? = null - - @CommandLine.Option( - names = ["--alwaysAffectedTags"], - description = - [ - "Comma separated list of Bazel target tags (e.g. `external`). Any target whose `tags` attribute contains one of these values is always reported as impacted: a per-invocation sentinel is mixed into its hash so a diff of two `generate-hashes` runs always marks it changed. Use this for non-hermetic targets that read undeclared workspace state at execution time (e.g. repo-scanning linters such as buildifier/gofmt/eslint tests) which would otherwise hash as unchanged and be wrongly skipped by target determination. This is the Bazel target `external` *tag* and is unrelated to --excludeExternalTargets / --fineGrainedHashExternalRepos, which concern external *repositories*."], - scope = CommandLine.ScopeType.INHERIT, - converter = [CommaSeparatedValueConverter::class], - ) - var alwaysAffectedTags: Set = emptySet() - - @CommandLine.Spec lateinit var spec: CommandLine.Model.CommandSpec - - override fun call(): Int { - validate(contentHashPath = contentHashPath) - - // Stop any existing Koin instance before starting a new one (for E2E tests) - org.koin.core.context.GlobalContext.stopKoin() - startKoin { - modules( - hasherModule( - workspacePath, - bazelPath, - contentHashPath, - bazelStartupOptions, - bazelCommandOptions, - cqueryCommandOptions, - useCquery, - cqueryExpression, - keepGoing, - depsMappingJSONPath != null, - fineGrainedHashExternalRepos, - fineGrainedHashExternalReposFile, - excludeExternalTargets, - excludeTargetsQuery, - alwaysAffectedTags, - ), - loggingModule(parent.verbose), - serialisationModule(), - ) - } - - return try { - when (GenerateHashesInteractor() - .execute( - seedFilepaths, - outputPath, - depsMappingJSONPath, - ignoredRuleHashingAttributes, - targetType, - includeTargetType, - modifiedFilepaths)) { - true -> CommandLine.ExitCode.OK - false -> CommandLine.ExitCode.SOFTWARE - } - } finally { - stopKoin() - } - } - - private fun validate(contentHashPath: File?) { - contentHashPath?.let { - if (!it.canRead()) { - throw CommandLine.ParameterException( - spec.commandLine(), - "Incorrect contentHashFilePath: file doesn't exist or can't be read.") - } - } - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/GetImpactedTargetsCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/GetImpactedTargetsCommand.kt deleted file mode 100644 index 4f6197b8..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/GetImpactedTargetsCommand.kt +++ /dev/null @@ -1,219 +0,0 @@ -package com.bazel_diff.cli - -import com.bazel_diff.cli.converter.NormalisingPathConverter -import com.bazel_diff.cli.converter.OptionsConverter -import com.bazel_diff.di.loggingModule -import com.bazel_diff.di.serialisationModule -import com.bazel_diff.interactor.CalculateImpactedTargetsInteractor -import com.bazel_diff.interactor.DeserialiseHashesInteractor -import java.io.BufferedWriter -import java.io.File -import java.io.FileDescriptor -import java.io.FileWriter -import java.io.IOException -import java.nio.file.Path -import java.util.concurrent.Callable -import org.koin.core.context.startKoin -import org.koin.core.context.stopKoin -import picocli.CommandLine - -@CommandLine.Command( - name = "get-impacted-targets", - description = ["Command-line utility to analyze the state of the bazel build graph"], -) -class GetImpactedTargetsCommand : Callable { - @CommandLine.ParentCommand private lateinit var parent: BazelDiff - - @CommandLine.Option( - names = ["-sh", "--startingHashes"], - scope = CommandLine.ScopeType.LOCAL, - description = - [ - "The path to the JSON file of target hashes for the initial revision. Run 'generate-hashes' to get this value."], - required = true, - ) - lateinit var startingHashesJSONPath: File - - @CommandLine.Option( - names = ["-fh", "--finalHashes"], - scope = CommandLine.ScopeType.LOCAL, - description = - [ - "The path to the JSON file of target hashes for the final revision. Run 'generate-hashes' to get this value."], - required = true, - ) - lateinit var finalHashesJSONPath: File - - @CommandLine.Option( - names = ["-d", "--depEdgesFile"], - description = - [ - "Path to the file where dependency edges are. If specified, build graph distance metrics will be computed from the given hash data."], - scope = CommandLine.ScopeType.INHERIT, - defaultValue = CommandLine.Parameters.NULL_VALUE) - var depsMappingJSONPath: File? = null - - @CommandLine.Option( - names = ["-tt", "--targetType"], - split = ",", - scope = CommandLine.ScopeType.LOCAL, - description = - [ - "The types of targets to filter. Use comma (,) to separate multiple values, e.g. '--targetType=SourceFile,Rule,GeneratedFile'."]) - var targetType: Set? = null - - @CommandLine.Option( - names = ["-o", "--output"], - scope = CommandLine.ScopeType.LOCAL, - description = - [ - "Filepath to write the impacted Bazel targets to. If using depEdgesFile: formatted in json, otherwise: newline separated. If not specified, the output will be written to STDOUT."], - ) - var outputPath: File? = null - - @CommandLine.Option( - names = ["-w", "--workspacePath"], - description = ["Path to Bazel workspace directory. Required for module change detection."], - scope = CommandLine.ScopeType.LOCAL, - required = true, - converter = [NormalisingPathConverter::class]) - lateinit var workspacePath: Path - - @CommandLine.Option( - names = ["-b", "--bazelPath"], - description = - [ - "Path to Bazel binary. If not specified, the Bazel binary available in PATH will be used."], - scope = CommandLine.ScopeType.LOCAL, - defaultValue = CommandLine.Parameters.NULL_VALUE) - var bazelPath: Path? = null - - @CommandLine.Option( - names = ["-so", "--bazelStartupOptions"], - description = - ["Additional space separated Bazel client startup options used when invoking Bazel"], - scope = CommandLine.ScopeType.LOCAL, - converter = [OptionsConverter::class]) - var bazelStartupOptions: List = emptyList() - - @CommandLine.Option( - names = ["--noBazelrc"], - negatable = true, - description = ["Don't use .bazelrc"], - scope = CommandLine.ScopeType.LOCAL) - var noBazelrc = false - - @CommandLine.Option( - names = ["--excludeExternalTargets"], - negatable = true, - description = - [ - "If true, drop labels starting with '//external:' from the impacted-targets output. " + - "These synthetic labels are produced for bzlmod-managed external repos so " + - "generate-hashes can detect dep changes, but they are not buildable in " + - "bzlmod-only mode (Bazel 8.6.0+ with --enable_workspace=false) and will fail " + - "downstream `bazel build`. See https://github.com/Tinder/bazel-diff/issues/326. " + - "When unset, defaults to true if Bzlmod is detected (via `bazel mod graph`), " + - "false otherwise."], - scope = CommandLine.ScopeType.LOCAL, - defaultValue = CommandLine.Parameters.NULL_VALUE) - var excludeExternalTargets: Boolean? = null - - @CommandLine.Spec lateinit var spec: CommandLine.Model.CommandSpec - - override fun call(): Int { - // Stop any existing Koin instance before starting a new one (for E2E tests) - org.koin.core.context.GlobalContext.stopKoin() - - // Setup modules - include hasher module for module querying - val resolvedBazelPath = bazelPath ?: java.nio.file.Paths.get("bazel") - startKoin { - modules( - serialisationModule(), - loggingModule(parent.verbose), - com.bazel_diff.di.hasherModule( - workingDirectory = workspacePath, - bazelPath = resolvedBazelPath, - contentHashPath = null, - startupOptions = bazelStartupOptions, - commandOptions = emptyList(), - cqueryOptions = emptyList(), - useCquery = false, - cqueryExpression = null, - keepGoing = false, - trackDeps = false, - fineGrainedHashExternalRepos = emptySet(), - fineGrainedHashExternalReposFile = null, - excludeExternalTargets = false)) - } - - return try { - validate() - val deserialiser = DeserialiseHashesInteractor() - val fromData = deserialiser.executeTargetHashWithMetadata(startingHashesJSONPath) - val toData = deserialiser.executeTargetHashWithMetadata(finalHashesJSONPath) - - // If the user did not pass --[no-]excludeExternalTargets, default to true when bzlmod is - // enabled (synthetic //external:* labels are not buildable in bzlmod-only mode — #326), - // otherwise false to preserve WORKSPACE-mode behavior. - val resolvedExcludeExternalTargets = - excludeExternalTargets - ?: org.koin.java.KoinJavaComponent.get( - com.bazel_diff.bazel.BazelModService::class.java) - .isBzlmodEnabled - - val outputWriter = - BufferedWriter( - when (val path = outputPath) { - null -> FileWriter(FileDescriptor.out) - else -> FileWriter(path) - }) - - try { - if (depsMappingJSONPath != null) { - val depsMapping = deserialiser.deserializeDeps(depsMappingJSONPath!!) - CalculateImpactedTargetsInteractor() - .executeWithDistances( - fromData.hashes, - toData.hashes, - depsMapping, - outputWriter, - targetType, - fromData.moduleGraphJson, - toData.moduleGraphJson, - resolvedExcludeExternalTargets) - } else { - CalculateImpactedTargetsInteractor() - .execute( - fromData.hashes, - toData.hashes, - outputWriter, - targetType, - fromData.moduleGraphJson, - toData.moduleGraphJson, - resolvedExcludeExternalTargets) - } - CommandLine.ExitCode.OK - } catch (e: IOException) { - CommandLine.ExitCode.SOFTWARE - } - } finally { - stopKoin() - } - } - - private fun validate() { - if (!startingHashesJSONPath.canRead()) { - throw CommandLine.ParameterException( - spec.commandLine(), "Incorrect starting hashes: file doesn't exist or can't be read.") - } - if (!finalHashesJSONPath.canRead()) { - throw CommandLine.ParameterException( - spec.commandLine(), "Incorrect final hashes: file doesn't exist or can't be read.") - } - if (depsMappingJSONPath != null && !depsMappingJSONPath!!.canRead()) { - throw CommandLine.ParameterException( - spec.commandLine(), "Incorrect dep edges file: file doesn't exist or can't be read.") - } - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/ServeCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/ServeCommand.kt deleted file mode 100644 index 412fbdfd..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/ServeCommand.kt +++ /dev/null @@ -1,594 +0,0 @@ -package com.bazel_diff.cli - -import com.bazel_diff.cli.converter.ByteSizeConverter -import com.bazel_diff.cli.converter.CommaSeparatedValueConverter -import com.bazel_diff.cli.converter.DurationConverter -import com.bazel_diff.cli.converter.NormalisingPathConverter -import com.bazel_diff.cli.converter.OptionsConverter -import com.bazel_diff.di.hasherModule -import com.bazel_diff.di.loggingModule -import com.bazel_diff.di.serialisationModule -import com.bazel_diff.extensions.toHexString -import com.bazel_diff.hash.sha256 -import com.bazel_diff.server.BazelDiffServer -import com.bazel_diff.server.CachePruneLimits -import com.bazel_diff.server.CachePruner -import com.bazel_diff.server.GitClient -import com.bazel_diff.server.HashCacheStorage -import com.bazel_diff.server.HashProvider -import com.bazel_diff.server.HashService -import com.bazel_diff.server.ImpactedTargetsService -import com.bazel_diff.server.LocalDiskHashCacheStorage -import com.bazel_diff.server.MetricsService -import com.bazel_diff.server.ProcessGitClient -import com.bazel_diff.server.PrunableHashCacheStorage -import com.bazel_diff.server.S3HashCacheStorage -import com.bazel_diff.server.TieredHashCacheStorage -import java.io.File -import java.net.URI -import java.nio.file.Path -import java.time.Duration -import java.util.concurrent.Callable -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicBoolean -import org.koin.core.context.startKoin -import org.koin.core.context.stopKoin -import picocli.CommandLine - -/** - * Long-running HTTP/JSON query service that answers affectedness queries between two git revisions - * (RFC: issue #29). - * - * The service is scoped to a single workspace clone at [workspacePath]. On startup it performs an - * initial `git fetch` and only then reports healthy. For each request it resolves the `from`/`to` - * revisions, generates (and caches, keyed by SHA) the hashes for each, and reuses the same - * affectedness logic as `get-impacted-targets`. - * - * Query-affecting flags mirror `generate-hashes` so the hashes the service produces match what a - * cold CLI run would produce. - */ -@CommandLine.Command( - name = "serve", - mixinStandardHelpOptions = true, - description = - [ - "Runs bazel-diff as a long-running HTTP query service that returns the impacted targets " + - "between two git revisions, caching generated hashes per commit SHA."], - versionProvider = VersionProvider::class) -open class ServeCommand : Callable { - @CommandLine.ParentCommand private lateinit var parent: BazelDiff - - @CommandLine.Option( - names = ["-w", "--workspacePath"], - description = ["Path to the Bazel workspace git clone the service checks out and queries."], - required = true, - converter = [NormalisingPathConverter::class]) - lateinit var workspacePath: Path - - @CommandLine.Option( - names = ["-b", "--bazelPath"], - description = - [ - "Path to Bazel binary. If not specified, the Bazel binary available in PATH will be used."], - defaultValue = "bazel") - lateinit var bazelPath: Path - - @CommandLine.Option( - names = ["--gitPath"], - description = - [ - "Path to the git binary used for fetch/checkout operations. Defaults to 'git' on the PATH."], - defaultValue = "git") - var gitPath: String = "git" - - @CommandLine.Option( - names = ["--port"], - description = - [ - "Port to listen on. Defaults to 8080. Pass 0 to bind any free port, and " + - "--portFile to find out which one."], - defaultValue = "8080") - var port: Int = 8080 - - @CommandLine.Option( - names = ["--portFile"], - description = - [ - "Write the bound port to this file once the server is listening. The point is " + - "'--port 0 --portFile ': a caller that picks a port itself has to open a " + - "socket to find a free one and close it again before this process can bind, and " + - "anything else on the machine can take the port in between. Binding 0 and " + - "reporting back has no such window. The file appears only after the bind " + - "succeeds, so waiting for it is also waiting for the listener."]) - var portFile: Path? = null - - @CommandLine.Option( - names = ["--requestTimeout"], - description = - [ - "Maximum seconds an /impacted_targets(_with_distances) or /dependency_edges " + - "request may run before the " + - "server abandons it and responds 504. 0 (the default) means no timeout. This " + - "bounds the request the client waits on; an in-flight bazel query may keep " + - "running in the background and still populate the per-SHA cache."], - defaultValue = "0") - var requestTimeoutSeconds: Long = 0 - - @CommandLine.Option( - names = ["--cacheDir"], - description = - ["Directory where generated hashes are cached per commit SHA. Persists across restarts."], - required = true, - converter = [NormalisingPathConverter::class]) - lateinit var cacheDir: Path - - @CommandLine.Option( - names = ["--cacheMaxAge"], - description = - [ - "Evict cached hashes not read or written within this window, so the cache does not " + - "grow without bound over time. Duration like 7d, 36h, 90m (units d/h/m/s). Unset " + - "means no age limit. Enforced by a background sweeper (see --cachePruneInterval)."], - converter = [DurationConverter::class]) - var cacheMaxAge: Duration? = null - - @CommandLine.Option( - names = ["--cacheMaxEntries"], - description = - [ - "Keep at most this many cached commit-SHA entries, evicting the least-recently-used " + - "first. Unset means no count limit."]) - var cacheMaxEntries: Int? = null - - @CommandLine.Option( - names = ["--cacheMaxSize"], - description = - [ - "Keep the cache's total on-disk size at or below this, evicting the " + - "least-recently-used entries first. Size like 10GB, 500MB, or a bare byte count " + - "(base 1024). Unset means no size limit."], - converter = [ByteSizeConverter::class]) - var cacheMaxSize: Long? = null - - @CommandLine.Option( - names = ["--cachePruneInterval"], - description = - [ - "How often the background sweeper enforces the --cacheMax* limits. Duration like 1h, " + - "30m. Defaults to 1h. No effect unless a --cacheMax* limit is set."], - converter = [DurationConverter::class], - defaultValue = "1h") - var cachePruneInterval: Duration = Duration.ofHours(1) - - @CommandLine.Option( - names = ["--s3Bucket"], - description = - [ - "S3 bucket used as a shared hash cache behind the local --cacheDir tier. Generated " + - "hashes are published to the bucket and local cache misses fall back to it, so " + - "replicas behind a load balancer share one cache. Credentials and region come " + - "from the AWS default provider chains (env vars, profile, IRSA, IMDS). Unset " + - "(the default) means local-disk caching only."]) - var s3Bucket: String? = null - - @CommandLine.Option( - names = ["--s3Prefix"], - description = - [ - "Key prefix for cache objects in --s3Bucket, e.g. 'bazel-diff/my-repo'. Defaults to " + - "no prefix (objects at the bucket root)."], - defaultValue = "") - var s3Prefix: String = "" - - @CommandLine.Option( - names = ["--s3Region"], - description = - [ - "AWS region of --s3Bucket. Defaults to the SDK's default region chain (env vars, " + - "profile, IMDS)."]) - var s3Region: String? = null - - @CommandLine.Option( - names = ["--s3Endpoint"], - description = - [ - "Custom S3 endpoint URL for S3-compatible stores (MinIO, LocalStack). Usually " + - "combined with --s3ForcePathStyle."]) - var s3Endpoint: URI? = null - - @CommandLine.Option( - names = ["--s3ForcePathStyle"], - negatable = true, - description = - [ - "Use path-style S3 addressing (bucket in the URL path, not the hostname), required " + - "by most S3-compatible stores. Defaults to false."]) - var s3ForcePathStyle = false - - @CommandLine.Option( - names = ["--warmupRevision"], - description = - [ - "Comma separated git revisions (branch/tag/SHA) whose hashes are generated and cached " + - "at startup, before the server reports healthy, so the first real request is warm " + - "and the Bazel analysis server is primed. Best-effort: a revision that fails to " + - "warm is logged and the server still becomes ready (serving it cold on demand). " + - "Increases time-to-healthy, so size deploy/health-check timeouts accordingly."], - converter = [CommaSeparatedValueConverter::class]) - var warmupRevisions: Set = emptySet() - - @CommandLine.Option( - names = ["--no-initial-fetch"], - description = - [ - "Skip the initial 'git fetch' before reporting healthy. Useful for local/offline testing."]) - var noInitialFetch = false - - @CommandLine.Option( - names = ["-so", "--bazelStartupOptions"], - description = - ["Additional space separated Bazel client startup options used when invoking Bazel"], - converter = [OptionsConverter::class]) - var bazelStartupOptions: List = emptyList() - - @CommandLine.Option( - names = ["-co", "--bazelCommandOptions"], - description = - ["Additional space separated Bazel command options used when invoking `bazel query`"], - converter = [OptionsConverter::class]) - var bazelCommandOptions: List = emptyList() - - @CommandLine.Option( - names = ["--cqueryCommandOptions"], - description = - [ - "Additional space separated Bazel command options used when invoking `bazel cquery`. No effect unless --useCquery is set."], - converter = [OptionsConverter::class]) - var cqueryCommandOptions: List = emptyList() - - @CommandLine.Option( - names = ["--useCquery"], - negatable = true, - description = ["If true, use cquery instead of query when generating dependency graphs."]) - var useCquery = false - - @CommandLine.Option( - names = ["--cqueryExpression"], - description = - ["Custom cquery expression to use instead of the default. No effect unless --useCquery."]) - var cqueryExpression: String? = null - - @CommandLine.Option( - names = ["-k", "--keep_going"], - negatable = true, - defaultValue = "false", - fallbackValue = "true", - description = ["Run `bazel query` with --keep_going. Defaults to false."]) - var keepGoing = false - - @CommandLine.Option( - names = ["--fineGrainedHashExternalRepos"], - description = - ["Comma separated list of external repos for which fine-grained hashes are computed."], - converter = [CommaSeparatedValueConverter::class]) - var fineGrainedHashExternalRepos: Set = emptySet() - - @CommandLine.Option( - names = ["--fineGrainedHashExternalReposFile"], - description = - [ - "A text file with a newline separated list of external repos. Mutually exclusive with --fineGrainedHashExternalRepos."]) - var fineGrainedHashExternalReposFile: File? = null - - @CommandLine.Option( - names = ["--ignoredRuleHashingAttributes"], - description = ["Attributes that should be ignored when hashing rule targets."], - converter = [CommaSeparatedValueConverter::class]) - var ignoredRuleHashingAttributes: Set = emptySet() - - @CommandLine.Option( - names = ["-s", "--seed-filepaths"], - description = - [ - "A text file with a newline separated list of filepaths used as a SHA256 seed for all targets."]) - var seedFilepaths: File? = null - - @CommandLine.Option( - names = ["--excludeExternalTargets"], - negatable = true, - description = ["If true, exclude external targets (do not query //external:all-targets)."]) - var excludeExternalTargets = false - - @CommandLine.Option( - names = ["--excludeTargetsQuery"], - description = - [ - "A Bazel query expression whose matched targets are excluded from the served hashes " + - "via the `except` operator, e.g. `manual`-tagged targets: " + - "--excludeTargetsQuery='attr(\"tags\", \"[\\[ ]manual[,\\]]\", //...)'."]) - var excludeTargetsQuery: String? = null - - @CommandLine.Option( - names = ["--trackDeps"], - negatable = true, - description = - [ - "Track dependency edges and persist them per commit SHA so build-graph distance " + - "metrics can be served via /impacted_targets_with_distances and the " + - "generate-hashes graph via /dependency_edges. Increases cache " + - "size and memory. Defaults to false."]) - var trackDeps = false - - // The background cache sweeper, when a --cacheMax* limit is configured. Held so the shutdown path - // can stop it; null when pruning is disabled or the backend manages its own retention. - private var cachePruner: CachePruner? = null - - override fun call(): Int { - org.koin.core.context.GlobalContext.stopKoin() - startKoin { - modules( - hasherModule( - workspacePath, - bazelPath, - null, - bazelStartupOptions, - bazelCommandOptions, - cqueryCommandOptions, - useCquery, - cqueryExpression, - keepGoing, - trackDeps, - fineGrainedHashExternalRepos, - fineGrainedHashExternalReposFile, - excludeExternalTargets, - excludeTargetsQuery, - ), - loggingModule(parent.verbose), - serialisationModule(), - ) - } - - return try { - val server = buildAndStartServer(createGitClient(), createStorage()) - awaitShutdown(server) - CommandLine.ExitCode.OK - } finally { - stopKoin() - } - } - - /** - * Builds the cache storage stack: the local `--cacheDir` tier, backed by the shared [remote] tier - * when one is configured. [remote] is injectable for tests; the default builds the S3 tier from - * the `--s3*` flags (null -- local-only -- when `--s3Bucket` is unset). - */ - fun createStorage(remote: HashCacheStorage? = createS3Storage()): HashCacheStorage { - val local = LocalDiskHashCacheStorage(cacheDir) - return if (remote == null) local else TieredHashCacheStorage(local, remote) - } - - /** The S3 remote cache tier from the `--s3*` flags, or null when `--s3Bucket` is unset. */ - fun createS3Storage(): S3HashCacheStorage? = - s3Bucket?.let { bucket -> - S3HashCacheStorage( - S3HashCacheStorage.buildClient(s3Region, s3Endpoint, s3ForcePathStyle), - bucket, - s3Prefix) - } - - /** Human-readable location of the shared remote cache tier for `/metrics`; null when unset. */ - fun remoteCacheLocation(): String? = - s3Bucket?.let { "s3://$it/${S3HashCacheStorage.normalizePrefix(s3Prefix)}" } - - /** - * Builds the [GitClient]. Git fetch/checkout operations shell out to the `git` binary at - * [gitPath], so a `git` binary must be available on the host. Overridable in tests so [call] can - * avoid shelling out during the initial-fetch handshake. - */ - open fun createGitClient(): GitClient = ProcessGitClient(workspacePath, gitPath) - - /** - * Wires the services, starts the HTTP server, and performs the initial git fetch + readiness - * handshake. Returns the started [BazelDiffServer]. Requires only a logging/serialisation Koin - * context to be active (the bazel-backed singletons are injected lazily and not touched until an - * `/impacted_targets` request arrives), which keeps this independently unit-testable. - */ - fun buildAndStartServer(gitClient: GitClient, storage: HashCacheStorage): BazelDiffServer { - val hashService = - HashService( - gitClient, - storage, - computeConfigFingerprint(), - loadSeedFilepaths(), - ignoredRuleHashingAttributes, - trackDeps) - val impactedTargetsService = - ImpactedTargetsService(gitClient, hashService, depsTracked = trackDeps) - - val ready = AtomicBoolean(false) - val metrics = - MetricsService( - version = VersionProvider().version.firstOrNull() ?: "unknown", - startedAtMillis = System.currentTimeMillis(), - gitEngine = "subprocess", - trackDeps = trackDeps, - cacheDir = cacheDir.toString(), - remoteCache = remoteCacheLocation(), - storage = storage, - readiness = { ready.get() }, - ) - val server = - BazelDiffServer(port, impactedTargetsService, requestTimeoutSeconds, metrics) { - ready.get() - } - server.start() - writePortFile(server) - performInitialFetch(gitClient, hashService, ready, server) - // Start sweeping only after warmup, so the immediate first pass evaluates an already-warm - // cache. - cachePruner = buildCachePruner(storage)?.also { it.start() } - return server - } - - /** - * The cache-pruning limits requested via the `--cacheMax*` flags (all-null = pruning disabled). - */ - fun cachePruneLimits(): CachePruneLimits = - CachePruneLimits(maxAge = cacheMaxAge, maxEntries = cacheMaxEntries, maxBytes = cacheMaxSize) - - /** - * Builds the background [CachePruner] for [storage], or null when no `--cacheMax*` limit is set - * or the backend manages its own retention. Only a [PrunableHashCacheStorage] can be swept - * in-process -- a remote backend (e.g. S3) would instead rely on a bucket lifecycle policy, so a - * limit set against a non-prunable backend is reported and ignored rather than silently - * pretended-to. - */ - fun buildCachePruner(storage: HashCacheStorage): CachePruner? { - val limits = cachePruneLimits() - if (!limits.hasAny) return null - if (storage !is PrunableHashCacheStorage) { - System.err.println( - "[Warn] --cacheMax* is set but the cache backend does not support in-process pruning; " + - "ignoring (rely on the backend's own retention policy instead)") - return null - } - return CachePruner(storage, limits, cachePruneInterval) - } - - /** - * Performs the startup git fetch (unless [noInitialFetch]) and flips [ready] to true once the - * clone is known good. On a fetch failure the server is left running but un-ready ("lame duck") - * so health checks report `503` and a load balancer removes the instance rather than us - * attempting a risky in-place repair (RFC issue #29). - */ - fun performInitialFetch( - gitClient: GitClient, - hashProvider: HashProvider, - ready: AtomicBoolean, - server: BazelDiffServer - ) { - if (noInitialFetch) { - warmUpCache(gitClient, hashProvider) - ready.set(true) - return - } - try { - gitClient.fetch() - // Warm up before flipping ready so the load balancer only routes to this instance once its - // configured baseline revisions are cached and the Bazel server is primed. - warmUpCache(gitClient, hashProvider) - ready.set(true) - System.err.println("[Info] initial git fetch complete; serving on port ${server.boundPort()}") - } catch (e: Exception) { - System.err.println( - "[Error] initial git fetch failed; server is lame-ducked (health will report NOT_READY): ${e.message}") - } - } - - /** - * Publishes the bound port to `--portFile`, if one was given. - * - * Written to a sibling temporary file and moved into place, so a reader that sees the path at all - * sees a complete number. That matters because the file doubles as a readiness signal for the - * listener: callers poll for it rather than guessing when the bind happened. - */ - fun writePortFile(server: BazelDiffServer) { - val destination = portFile ?: return - destination.toAbsolutePath().parent?.let { java.nio.file.Files.createDirectories(it) } - val temporary = - java.nio.file.Files.createTempFile( - destination.toAbsolutePath().parent, ".${destination.fileName}", ".tmp") - java.nio.file.Files.writeString(temporary, server.boundPort().toString()) - java.nio.file.Files.move( - temporary, destination, java.nio.file.StandardCopyOption.REPLACE_EXISTING) - } - - /** - * Best-effort priming of the hash cache (and, as a side effect, the Bazel analysis server) for - * each `--warmupRevision` before readiness is reported. A revision that fails to warm (bad ref, - * transient bazel error) is logged and skipped rather than failing the deploy: warmup is an - * optimization, not a correctness requirement, so the server still becomes ready and serves that - * revision cold on demand. Never throws, so a bad `--warmupRevision` cannot lame-duck the - * instance. - */ - fun warmUpCache(gitClient: GitClient, hashProvider: HashProvider) { - for (rev in warmupRevisions) { - try { - val sha = gitClient.resolveSha(rev) - System.err.println("[Info] warming hash cache for revision '$rev' ($sha)") - hashProvider.getHashes(sha) - System.err.println("[Info] warmed hash cache for revision '$rev' ($sha)") - } catch (e: Exception) { - System.err.println( - "[Warn] warmup for revision '$rev' failed; server will serve it cold on demand: ${e.message}") - } - } - } - - /** Reads the optional seed filepaths file into a set of paths. */ - fun loadSeedFilepaths(): Set = - seedFilepaths?.readLines()?.filter { it.isNotBlank() }?.map { File(it).toPath() }?.toSet() - ?: emptySet() - - /** - * Blocks until a JVM shutdown signal (or thread interrupt), stopping the server cleanly. - * - * [registerShutdownHook] and [await] default to the real JVM shutdown-hook + latch wait. Tests - * override this method (see [call]) or pass hooks so both the hook path and the interrupt path - * can be covered without hanging on a long-lived server. - */ - open fun awaitShutdown( - server: BazelDiffServer, - registerShutdownHook: (Thread) -> Unit = Runtime.getRuntime()::addShutdownHook, - await: (CountDownLatch) -> Unit = { it.await() }, - ) { - val latch = CountDownLatch(1) - registerShutdownHook( - Thread { - cachePruner?.stop() - server.stop(1) - latch.countDown() - }) - try { - await(latch) - } catch (e: InterruptedException) { - // Treated as a shutdown signal: stop serving and restore the interrupt flag. - cachePruner?.stop() - server.stop(1) - Thread.currentThread().interrupt() - } - } - - /** - * Short digest over the query-affecting flags, mixed into every cache key so hashes generated - * under one configuration are never served to a server started with a different one. The - * workspace SHA already determines file contents (including seed/ignored-attr file contents at - * that commit), so only the flag values themselves need to be captured here. - */ - fun computeConfigFingerprint(): String { - val canonical = buildString { - append("useCquery=").append(useCquery).append('\n') - append("cqueryExpression=").append(cqueryExpression ?: "").append('\n') - append("keepGoing=").append(keepGoing).append('\n') - append("excludeExternalTargets=").append(excludeExternalTargets).append('\n') - append("excludeTargetsQuery=").append(excludeTargetsQuery ?: "").append('\n') - append("bazelCommandOptions=").append(bazelCommandOptions.joinToString(" ")).append('\n') - append("cqueryCommandOptions=").append(cqueryCommandOptions.joinToString(" ")).append('\n') - append("bazelStartupOptions=").append(bazelStartupOptions.joinToString(" ")).append('\n') - append("fineGrainedHashExternalRepos=") - .append(fineGrainedHashExternalRepos.sorted().joinToString(",")) - .append('\n') - append("fineGrainedHashExternalReposFile=") - .append(fineGrainedHashExternalReposFile?.path ?: "") - .append('\n') - append("ignoredRuleHashingAttributes=") - .append(ignoredRuleHashingAttributes.sorted().joinToString(",")) - .append('\n') - // Distinct cache keys for deps-tracked vs deps-less runs: a deps-tracking server must never - // serve a deps-less cache entry (its distance queries would have no edges to traverse). - append("trackDeps=").append(trackDeps).append('\n') - append("version=").append(VersionProvider().version.firstOrNull() ?: "unknown").append('\n') - } - return sha256 { putBytes(canonical.toByteArray()) }.toHexString().take(12) - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/VersionProvider.kt b/cli/src/main/kotlin/com/bazel_diff/cli/VersionProvider.kt deleted file mode 100644 index d5222e06..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/VersionProvider.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.bazel_diff.cli - -import java.io.BufferedReader -import java.io.InputStreamReader -import picocli.CommandLine.IVersionProvider - -class VersionProvider( - private val classLoader: ClassLoader = VersionProvider::class.java.classLoader -) : IVersionProvider { - override fun getVersion(): Array { - val inputStream = - classLoader.getResourceAsStream("cli/version") - ?: classLoader.getResourceAsStream("version") - ?: throw IllegalArgumentException( - "unknown version as version file not found in resources") - - val version = BufferedReader(InputStreamReader(inputStream)).use { it.readText().trim() } - return arrayOf(version) - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/WarmupCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/WarmupCommand.kt deleted file mode 100644 index 8ba4b994..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/WarmupCommand.kt +++ /dev/null @@ -1,98 +0,0 @@ -package com.bazel_diff.cli - -import com.bazel_diff.interactor.FingerprintInteractor -import java.io.File -import picocli.CommandLine - -/** - * The Firecracker **record-side entrypoint**. See `docs/firecracker-snapshots.md` §4.1. - * - * `warmup` is `generate-hashes` for the base revision plus two metadata side effects: - * 1. writes the base hashes to a known path (`--base-hashes`, default `/snap/base_hashes.json`), - * 2. writes the fingerprint file (`--fingerprint-output`, default `/snap/fingerprint.json`). - * - * Crucially, it exits `0` **only** once `bazel query` has completed and the server is warm and - * quiesced — the host watches for this clean exit as the "safe to snapshot" signal. - * - * It extends [GenerateHashesCommand] so it inherits the exact same query-affecting flags; warmup is - * deliberately *generate-hashes plus metadata*, never a divergent query path. That guarantees the - * base hashes baked into the snapshot are byte-identical to what a cold `generate-hashes` would - * produce, and that the fingerprint reflects the flags actually used. - */ -@CommandLine.Command( - name = "warmup", - mixinStandardHelpOptions = true, - description = - [ - "Record-side entrypoint for Firecracker snapshots: runs generate-hashes for the base " + - "revision, writes base hashes + fingerprint to known paths, and exits 0 only once " + - "the Bazel server is warm (the host's 'safe to snapshot' signal)."], - versionProvider = VersionProvider::class) -open class WarmupCommand : GenerateHashesCommand() { - - @CommandLine.Option( - names = ["--base-hashes"], - description = ["Path to write the base hashes JSON. Default: /snap/base_hashes.json"], - defaultValue = "/snap/base_hashes.json") - lateinit var baseHashesPath: File - - @CommandLine.Option( - names = ["--fingerprint-output"], - description = ["Path to write the fingerprint JSON. Default: /snap/fingerprint.json"], - defaultValue = "/snap/fingerprint.json") - lateinit var fingerprintOutputPath: File - - override fun call(): Int { - // Route generate-hashes' output to the known base-hashes path. - outputPath = baseHashesPath - baseHashesPath.parentFile?.mkdirs() - - val genResult = runGenerateHashes() - if (genResult != CommandLine.ExitCode.OK) { - // Do not write the fingerprint or signal "safe to snapshot" on a failed warmup. - return genResult - } - - writeFingerprint() - return CommandLine.ExitCode.OK - } - - /** - * Invokes [GenerateHashesCommand.call]. Overridable in tests so [call] can be exercised without a - * real `bazel query`. - */ - open fun runGenerateHashes(): Int = super.call() - - /** - * Computes the fingerprint over the current flag set + workspace and writes it to - * [fingerprintOutputPath]. Split out of [call] so it is unit-testable without the bazel-backed - * `generate-hashes` run that [call] performs via `super.call()`. - */ - fun writeFingerprint() { - val flags = - FingerprintGatherer.canonicalizeFlags( - bazelStartupOptions = bazelStartupOptions, - bazelCommandOptions = bazelCommandOptions, - cqueryCommandOptions = cqueryCommandOptions, - useCquery = useCquery, - cqueryExpression = cqueryExpression, - includeTargetType = includeTargetType, - targetType = targetType, - fineGrainedHashExternalRepos = fineGrainedHashExternalRepos, - ignoredRuleHashingAttributes = ignoredRuleHashingAttributes, - excludeExternalTargets = excludeExternalTargets, - keepGoing = keepGoing, - ) - val inputs = - FingerprintGatherer.gather( - workspacePath = workspacePath, - bazelPath = bazelPath, - bazelDiffVersion = VersionProvider().version.firstOrNull() ?: "unknown", - flags = flags, - ) - val result = FingerprintInteractor().compute(inputs) - - fingerprintOutputPath.parentFile?.mkdirs() - fingerprintOutputPath.writeText(renderFingerprintJson(result, inputs.flags) + "\n") - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/converter/ByteSizeConverter.kt b/cli/src/main/kotlin/com/bazel_diff/cli/converter/ByteSizeConverter.kt deleted file mode 100644 index c3049764..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/converter/ByteSizeConverter.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.bazel_diff.cli.converter - -import picocli.CommandLine.ITypeConverter -import picocli.CommandLine.TypeConversionException - -/** - * Parses a human-friendly byte size such as `10GB`, `500MB`, `2g`, `1kb`, or a bare `1048576` - * (bytes when no unit is given). Units are base 1024 and case-insensitive: `b`, `k`/`kb`, `m`/`mb`, - * `g`/`gb`, `t`/`tb`. Returns the size in bytes. - */ -class ByteSizeConverter : ITypeConverter { - override fun convert(value: String): Long { - val match = - PATTERN.matchEntire(value.trim().lowercase()) - ?: throw TypeConversionException( - "invalid size '$value' (expected e.g. 1048576, 500mb, 10gb)") - val amount = match.groupValues[1].toLong() - val multiplier = - when (match.groupValues[2]) { - "", - "b" -> 1L - "k", - "kb" -> KB - "m", - "mb" -> KB * KB - "g", - "gb" -> KB * KB * KB - "t", - "tb" -> KB * KB * KB * KB - else -> throw TypeConversionException("invalid size unit in '$value'") - } - return amount * multiplier - } - - private companion object { - const val KB = 1024L - val PATTERN = Regex("(\\d+)\\s*([a-z]*)") - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/converter/CommaSeparatedValueConverter.kt b/cli/src/main/kotlin/com/bazel_diff/cli/converter/CommaSeparatedValueConverter.kt deleted file mode 100644 index b81bff8b..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/converter/CommaSeparatedValueConverter.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.bazel_diff.cli.converter - -import picocli.CommandLine.ITypeConverter - -class CommaSeparatedValueConverter : ITypeConverter> { - override fun convert(value: String): Set { - return value.split(",").dropLastWhile { it.isEmpty() }.toSet() - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/converter/DurationConverter.kt b/cli/src/main/kotlin/com/bazel_diff/cli/converter/DurationConverter.kt deleted file mode 100644 index 6af20b77..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/converter/DurationConverter.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.bazel_diff.cli.converter - -import java.time.Duration -import picocli.CommandLine.ITypeConverter -import picocli.CommandLine.TypeConversionException - -/** - * Parses a human-friendly duration such as `7d`, `36h`, `90m`, `45s`, or a combination like - * `1d12h30m`. Recognised unit suffixes are `d` (days), `h` (hours), `m` (minutes) and `s` - * (seconds). The whole string must be made up of `` tokens; anything else is rejected - * so a typo fails fast rather than being silently truncated. - */ -class DurationConverter : ITypeConverter { - override fun convert(value: String): Duration { - val normalized = value.trim().lowercase() - val tokens = TOKEN.findAll(normalized).toList() - val consumed = tokens.sumOf { it.value.length } - if (tokens.isEmpty() || consumed != normalized.length) { - throw TypeConversionException( - "invalid duration '$value' (expected e.g. 7d, 36h, 90m, 45s, or 1d12h)") - } - var total = Duration.ZERO - for (token in tokens) { - val amount = token.groupValues[1].toLong() - total += - when (token.groupValues[2]) { - "d" -> Duration.ofDays(amount) - "h" -> Duration.ofHours(amount) - "m" -> Duration.ofMinutes(amount) - "s" -> Duration.ofSeconds(amount) - else -> throw TypeConversionException("invalid duration unit in '$value'") - } - } - return total - } - - private companion object { - val TOKEN = Regex("(\\d+)([dhms])") - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/converter/NormalisingPathConverter.kt b/cli/src/main/kotlin/com/bazel_diff/cli/converter/NormalisingPathConverter.kt deleted file mode 100644 index 2626cb9b..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/converter/NormalisingPathConverter.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.bazel_diff.cli.converter - -import java.io.File -import java.nio.file.Path -import picocli.CommandLine.ITypeConverter - -class NormalisingPathConverter : ITypeConverter { - override fun convert(value: String): Path = File(value).toPath().normalize() -} diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/converter/OptionsConverter.kt b/cli/src/main/kotlin/com/bazel_diff/cli/converter/OptionsConverter.kt deleted file mode 100644 index b151b3bf..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/cli/converter/OptionsConverter.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.bazel_diff.cli.converter - -import picocli.CommandLine.ITypeConverter - -class OptionsConverter : ITypeConverter> { - override fun convert(value: String): List { - return value.split(" ").dropLastWhile { it.isEmpty() } - } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt b/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt deleted file mode 100644 index 557d4f9f..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt +++ /dev/null @@ -1,190 +0,0 @@ -package com.bazel_diff.di - -import com.bazel_diff.bazel.BazelClient -import com.bazel_diff.bazel.BazelModService -import com.bazel_diff.bazel.BazelQueryService -import com.bazel_diff.bazel.ModuleGraphParser -import com.bazel_diff.hash.* -import com.bazel_diff.io.ContentHashProvider -import com.bazel_diff.log.Logger -import com.bazel_diff.log.StderrLogger -import com.bazel_diff.process.Redirect -import com.bazel_diff.process.process -import com.google.gson.GsonBuilder -import java.io.File -import java.nio.file.Path -import java.nio.file.Paths -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking -import org.koin.core.module.Module -import org.koin.core.qualifier.named -import org.koin.dsl.module - -@OptIn(ExperimentalCoroutinesApi::class) -fun hasherModule( - workingDirectory: Path, - bazelPath: Path, - contentHashPath: File?, - startupOptions: List, - commandOptions: List, - cqueryOptions: List, - useCquery: Boolean, - cqueryExpression: String?, - keepGoing: Boolean, - trackDeps: Boolean, - fineGrainedHashExternalRepos: Set, - fineGrainedHashExternalReposFile: File?, - excludeExternalTargets: Boolean, - excludeTargetsQuery: String? = null, - alwaysAffectedTags: Set = emptySet(), -): Module = module { - if (fineGrainedHashExternalReposFile != null && fineGrainedHashExternalRepos.isNotEmpty()) { - System.err.println( - "Error: fineGrainedHashExternalReposFile and fineGrainedHashExternalRepos are mutually exclusive - please provide only one of them") - System.exit(1) - } - val userSuppliedFineGrainedHashExternalRepos = - fineGrainedHashExternalReposFile?.let { file -> - file.readLines().filter { it.isNotBlank() }.toSet() - } ?: fineGrainedHashExternalRepos - - // Auto-expand the user-supplied fine-grained set with any bzlmod module that transitively - // wraps one of those repos (issue #197). Without this, a main-repo target consuming - // `@middle_repo//:wrapped` -- itself an alias for `@inner_repo//:all_files` -- collapses - // `@middle_repo` into an opaque `//external:middle_repo` blob, so source changes inside - // `@inner_repo` never reach the main consumer. Adding the wrapper to the fine-grained set - // keeps `@middle_repo//:wrapped` as a real ruleInput whose `actual` attribute carries the - // chain down to the leaf. - val updatedFineGrainedHashExternalRepos = - expandFineGrainedHashExternalReposWithBzlmodDependents( - userSuppliedFineGrainedHashExternalRepos, - workingDirectory, - bazelPath, - startupOptions, - ) - - val cmd: MutableList = - ArrayList().apply { - add(bazelPath.toString()) - addAll(startupOptions) - add("info") - add("output_base") - } - val result = runBlocking { - process( - *cmd.toTypedArray(), - stdout = Redirect.CAPTURE, - workingDirectory = workingDirectory.toFile(), - stderr = Redirect.PRINT, - destroyForcibly = true, - ) - } - val outputPath = Paths.get(result.output.single()) - val debug = System.getProperty("DEBUG", "false").equals("true") - - // issue #401: when the user opts targets in via --alwaysAffectedTags, mint one per-invocation - // random sentinel and hand it to RuleHasher, which mixes it into those targets' hashes so a - // base/head diff always marks them impacted. Left empty (and never mixed) when the feature is - // unused, so every hash stays byte-for-byte identical to a run without the flag. - val alwaysAffectedSeed = - if (alwaysAffectedTags.isEmpty()) ByteArray(0) - else java.util.UUID.randomUUID().toString().toByteArray() - single { - BazelQueryService( - workingDirectory, - bazelPath, - startupOptions, - commandOptions, - cqueryOptions, - keepGoing, - debug) - } - single { BazelModService(workingDirectory, bazelPath, startupOptions, debug) } - single { - BazelClient( - useCquery, - cqueryExpression, - updatedFineGrainedHashExternalRepos, - excludeExternalTargets, - excludeTargetsQuery) - } - single { BuildGraphHasher(get()) } - single { TargetHasher() } - single { - RuleHasher( - useCquery, - trackDeps, - updatedFineGrainedHashExternalRepos, - alwaysAffectedTags, - alwaysAffectedSeed) - } - single { SourceFileHasherImpl(updatedFineGrainedHashExternalRepos) } - single { ExternalRepoResolver(workingDirectory, bazelPath, outputPath) } - single(named("working-directory")) { workingDirectory } - single(named("output-base")) { outputPath } - single { ContentHashProvider(contentHashPath) } -} - -/** - * Returns [userSupplied] together with every bzlmod module that transitively depends on a repo in - * [userSupplied]. Each entry is the apparent-name form prefixed with `@`, matching the format - * consumed by [BazelClient]/[com.bazel_diff.bazel.BazelRule] and the user-facing CLI flag. - * - * If `bazel mod graph --output=json` fails (e.g. bzlmod disabled) or the user supplied nothing, - * this is a no-op and returns [userSupplied] unchanged. This intentionally degrades gracefully for - * WORKSPACE-only setups where the bzlmod graph is unavailable -- the wrapped-repo bug (issue #197) - * is bzlmod-specific anyway. - */ -@OptIn(ExperimentalCoroutinesApi::class) -private fun expandFineGrainedHashExternalReposWithBzlmodDependents( - userSupplied: Set, - workingDirectory: Path, - bazelPath: Path, - startupOptions: List, -): Set { - if (userSupplied.isEmpty()) return userSupplied - - val cmd = - mutableListOf().apply { - add(bazelPath.toString()) - addAll(startupOptions) - add("mod") - add("graph") - add("--output=json") - } - val result = runBlocking { - process( - *cmd.toTypedArray(), - stdout = Redirect.CAPTURE, - stderr = Redirect.SILENT, - workingDirectory = workingDirectory.toFile(), - destroyForcibly = true, - ) - } - if (result.resultCode != 0) return userSupplied - val json = result.output.joinToString("\n").trim() - if (json.isEmpty()) return userSupplied - - val parser = ModuleGraphParser() - val graph = parser.parseModuleGraphEdges(json) - if (graph.edges.isEmpty()) return userSupplied - - // The user-supplied set uses the `@apparentName` form; strip leading `@`/`@@` to match the - // apparent names emitted by `bazel mod graph`. - val apparentTargets = - userSupplied - .map { it.removePrefix("@@").removePrefix("@") } - .filter { it.isNotEmpty() } - .toSet() - val dependents = - parser.findTransitiveDependents(graph.edges, apparentTargets, graph.rootApparentNames) - if (dependents.isEmpty()) return userSupplied - - return userSupplied + dependents.map { "@$it" } -} - -fun loggingModule(verbose: Boolean) = module { single { StderrLogger(verbose) } } - -fun serialisationModule() = module { - single { GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create() } -} diff --git a/cli/src/main/kotlin/com/bazel_diff/extensions/ByteArray.kt b/cli/src/main/kotlin/com/bazel_diff/extensions/ByteArray.kt deleted file mode 100644 index 0ec32589..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/extensions/ByteArray.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.bazel_diff.extensions - -import java.nio.charset.StandardCharsets - -fun ByteArray.toHexString(): String { - val hexChars = ByteArray(size * 2) - for (j in indices) { - val v = get(j).toInt() and 0xFF - hexChars[j * 2] = HEX_ARRAY[v ushr 4] - hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F] - } - return String(hexChars, StandardCharsets.UTF_8) -} - -private val HEX_ARRAY = "0123456789abcdef".toByteArray(StandardCharsets.US_ASCII) diff --git a/cli/src/main/kotlin/com/bazel_diff/extensions/ByteBuffer.kt b/cli/src/main/kotlin/com/bazel_diff/extensions/ByteBuffer.kt deleted file mode 100644 index 98effeda..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/extensions/ByteBuffer.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.bazel_diff.extensions - -import java.nio.Buffer -import java.nio.ByteBuffer - -/** Fix for NoSuchMethodError for JRE8 */ -fun ByteBuffer.compatClear() = ((this as Buffer).clear() as ByteBuffer) - -fun ByteBuffer.compatFlip() = ((this as Buffer).flip() as ByteBuffer) diff --git a/cli/src/main/kotlin/com/bazel_diff/extensions/HashingExtensions.kt b/cli/src/main/kotlin/com/bazel_diff/extensions/HashingExtensions.kt deleted file mode 100644 index 6a7ddfdb..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/extensions/HashingExtensions.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.bazel_diff.hash - -import com.bazel_diff.extensions.compatClear -import com.bazel_diff.extensions.compatFlip -import com.bazel_diff.io.ByteBufferPool -import com.google.common.hash.Hasher -import com.google.common.hash.Hashing -import java.io.BufferedInputStream -import java.io.File -import java.io.FileInputStream - -fun sha256(block: Hasher.() -> Unit): ByteArray { - val hasher = Hashing.sha256().newHasher() - hasher.apply(block) - return hasher.hash().asBytes().clone() -} - -fun Hasher.safePutBytes(block: ByteArray?) = block?.let { putBytes(it) } - -fun Hasher.putFile(file: File) { - BufferedInputStream(FileInputStream(file.absolutePath.toString())).use { stream -> - val buffer = pool.borrow() - val array = buffer!!.array() // Available for non-direct buffers - while (true) { - var length: Int - if (stream.read(array).also { length = it } == -1) break - buffer.compatFlip() - putBytes(array, 0, length) - buffer.compatClear() - } - pool.recycle(buffer) - } -} - -private val pool = ByteBufferPool(1024, 10240) // 10kb diff --git a/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt b/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt deleted file mode 100644 index b221dfbf..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt +++ /dev/null @@ -1,281 +0,0 @@ -package com.bazel_diff.hash - -import com.bazel_diff.bazel.BazelClient -import com.bazel_diff.bazel.BazelModService -import com.bazel_diff.bazel.BazelRule -import com.bazel_diff.bazel.BazelSourceFileTarget -import com.bazel_diff.bazel.BazelTarget -import com.bazel_diff.extensions.toHexString -import com.bazel_diff.log.Logger -import com.google.common.collect.Sets -import java.nio.file.Path -import java.util.Calendar -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap -import java.util.concurrent.atomic.AtomicReference -import java.util.stream.Collectors -import kotlin.io.path.readBytes -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.runBlocking -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -/** - * Mutable per-run phase timings, filled in by [BuildGraphHasher.hashAllBazelTargetsAndSourcefiles] - * when the caller passes one. The phases run sequentially, in field order; [targetHashMillis] also - * covers deriving the seed and per-package `.bzl` seeds that target hashing consumes. - */ -class HasherPhaseTimings { - var bazelQueryMillis: Long = 0 - var sourceHashMillis: Long = 0 - var targetHashMillis: Long = 0 -} - -class BuildGraphHasher(private val bazelClient: BazelClient) : KoinComponent { - private val targetHasher: TargetHasher by inject() - private val sourceFileHasher: SourceFileHasher by inject() - private val bazelModService: BazelModService by inject() - private val logger: Logger by inject() - - fun hashAllBazelTargetsAndSourcefiles( - seedFilepaths: Set = emptySet(), - ignoredAttrs: Set = emptySet(), - modifiedFilepaths: Set = emptySet(), - timings: HasherPhaseTimings? = null - ): Map { - val hashInvocationContext = HashInvocationContext() - val (sourceDigests, allTargets) = - runBlocking { - val queryStartNanos = System.nanoTime() - val targetsTask = async(Dispatchers.IO) { bazelClient.queryAllTargets() } - val allTargets = targetsTask.await() - timings?.bazelQueryMillis = (System.nanoTime() - queryStartNanos) / 1_000_000 - val sourceTargets = - allTargets - .filter { it is BazelTarget.SourceFile } - .map { it as BazelTarget.SourceFile } - - val sourceDigestsFuture = - async(Dispatchers.IO) { - val sourceHashDurationEpoch = Calendar.getInstance().getTimeInMillis() - val sourceFileTargets = hashSourcefiles(sourceTargets, modifiedFilepaths) - val sourceHashDuration = - Calendar.getInstance().getTimeInMillis() - sourceHashDurationEpoch - timings?.sourceHashMillis = sourceHashDuration - logger.i { "Source file hashes calculated in $sourceHashDuration" } - sourceFileTargets - } - - Pair(sourceDigestsFuture.await(), allTargets) - } - val targetHashStartNanos = System.nanoTime() - val seedForFilepaths = runBlocking(Dispatchers.IO) { createSeedForFilepaths(seedFilepaths) } - // With macro instantiation stacks (`--proto:instantiation_stack`), RuleHasher.ruleBzlSeed - // attributes each `.bzl` to the rules a macro produced, so the coarse package seed (and its - // source-file over-invalidation) is dropped. Fall back to it when stacks are absent (#365). - val hasInstantiationStacks = - allTargets.any { it is BazelTarget.Rule && it.rule.instantiationStack.isNotEmpty() } - val packageBzlSeeds = - if (hasInstantiationStacks) emptyMap() - else createPackageBzlSeeds(allTargets, modifiedFilepaths) - val result = - hashAllTargets( - seedForFilepaths, - packageBzlSeeds, - sourceDigests, - allTargets, - ignoredAttrs, - modifiedFilepaths, - hashInvocationContext) - timings?.targetHashMillis = (System.nanoTime() - targetHashStartNanos) / 1_000_000 - return result - } - - private fun hashSourcefiles( - targets: List, - modifiedFilepaths: Set - ): ConcurrentMap { - val exception = AtomicReference(null) - val result: ConcurrentMap = - targets - .parallelStream() - .map { sourceFile: BazelTarget.SourceFile -> - val seed = sha256 { - safePutBytes(sourceFile.name.toByteArray()) - for (subinclude in sourceFile.subincludeList) { - safePutBytes(subinclude.toByteArray()) - } - } - try { - val sourceFileTarget = BazelSourceFileTarget(sourceFile.name, seed) - Pair( - sourceFileTarget.name, - sourceFileHasher.digest(sourceFileTarget, modifiedFilepaths)) - } catch (e: Exception) { - exception.set(e) - null - } - } - .filter { pair -> pair != null } - .collect( - Collectors.toConcurrentMap( - { pair -> pair!!.first }, - { pair -> pair!!.second }, - )) - - exception.get()?.let { throw it } - return result - } - - private fun hashAllTargets( - seedHash: ByteArray, - packageBzlSeeds: Map, - sourceDigests: ConcurrentMap, - allTargets: List, - ignoredAttrs: Set, - modifiedFilepaths: Set, - hashInvocationContext: HashInvocationContext - ): Map { - val ruleHashes: ConcurrentMap = ConcurrentHashMap() - val targetToRule: MutableMap = HashMap() - traverseGraph(allTargets, targetToRule) - - return allTargets - .parallelStream() - .map { target: BazelTarget -> - val targetDigest = - targetHasher.digest( - target, - targetToRule, - sourceDigests, - ruleHashes, - seedHash, - packageBzlSeeds, - ignoredAttrs, - modifiedFilepaths, - hashInvocationContext) - Pair( - target.name, - TargetHash( - target.javaClass.name.substringAfterLast('$'), - targetDigest.overallDigest.toHexString(), - targetDigest.directDigest.toHexString(), - targetDigest.deps, - )) - } - .filter { targetEntry: Pair? -> targetEntry != null } - .collect( - Collectors.toMap( - { obj: Pair -> obj.first }, - { obj: Pair -> obj.second }, - )) - } - - /** Traverses the list of targets and revisits the targets with yet-unknown generating rule */ - private fun traverseGraph( - allTargets: List, - targetToRule: MutableMap - ) { - var targetsToAnalyse: Set = Sets.newHashSet(allTargets) - while (!targetsToAnalyse.isEmpty()) { - val initialSize = targetsToAnalyse.size - val nextTargets: MutableSet = Sets.newHashSet() - for (target in targetsToAnalyse) { - val targetName = target.name - when (target) { - is BazelTarget.GeneratedFile -> { - targetToRule[target.generatingRuleName]?.let { targetToRule[targetName] = it } - ?: nextTargets.add(target) - } - is BazelTarget.Rule -> targetToRule[targetName] = target.rule - is BazelTarget.SourceFile -> continue - } - } - val newSize = nextTargets.size - if (newSize >= initialSize) { - throw RuntimeException("Not possible to traverse the build graph") - } - targetsToAnalyse = nextTargets - } - } - - private suspend fun createSeedForFilepaths(seedFilepaths: Set): ByteArray { - return sha256 { - // Include seed filepaths in hash - for (path in seedFilepaths) { - putBytes(path.readBytes()) - } - } - } - - /** - * Builds a per-package seed-hash contribution from the contents of every `.bzl` (and `.scl`) file - * that package's BUILD file loads, keyed by package label (e.g. `//pkg`, `//` for the root - * package). - * - * Background: Bazel pre-7 populated [Build.Rule.skylark_environment_hash_code] in the query - * proto, so any change to a `.bzl` file loaded by a rule's BUILD file naturally bubbled into that - * rule's hash. Bazel 7+ leaves that field empty, which is the root cause of issues - * [#259](https://github.com/Tinder/bazel-diff/issues/259) and - * [#227](https://github.com/Tinder/bazel-diff/issues/227): editing a macro body (e.g. adding - * `print()`) no longer invalidated any caller because the emitted rule attrs were identical. - * - * Fix: each package's BUILD `SourceFile` target carries a `subincludeList` (the - * `Build.SourceFile.subinclude` proto field) listing every `.bzl` that BUILD loaded. We - * softDigest each main-repo `.bzl` and roll the digests for a given package into a seed - * attributed to that package. Callers then mix only their own package's seed into each target's - * hash, so editing a `.bzl` re-hashes exactly the targets in packages that `load()` it -- not - * every target in the workspace ([#365](https://github.com/Tinder/bazel-diff/issues/365)). A - * package that loads no tracked `.bzl` has no entry here, so nothing is mixed in and its targets - * stay byte-for-byte stable. - * - * External-repo `.bzl` files (`@repo//...`, `@@canonical//...`) are skipped because - * [SourceFileHasher.softDigest] returns null for non-main-repo labels, which keeps the seed - * stable across BCR fetches that don't actually change repo contents. - */ - private fun createPackageBzlSeeds( - allTargets: List, - modifiedFilepaths: Set - ): Map { - // Union the loaded `.bzl` labels per package. Every source file in a package shares the same - // BUILD, but the `subinclude` field is populated on the BUILD's SourceFile target; group by - // package so we don't depend on which source file carries it. - val packageToLabels = sortedMapOf>() - for (target in allTargets) { - if (target !is BazelTarget.SourceFile || target.subincludeList.isEmpty()) continue - packageToLabels - .getOrPut(labelToPackage(target.sourceFileName)) { sortedSetOf() } - .addAll(target.subincludeList) - } - - val result = mutableMapOf() - for ((pkg, labels) in packageToLabels) { - val tracked = mutableListOf>() - for (label in labels) { - val digest = - sourceFileHasher.softDigest( - BazelSourceFileTarget(label, ByteArray(0)), modifiedFilepaths) - if (digest != null) tracked += label to digest - } - if (tracked.isEmpty()) continue - result[pkg] = sha256 { - for ((label, digest) in tracked) { - safePutBytes(label.toByteArray()) - safePutBytes(digest) - } - } - } - return result - } -} - -/** - * Returns the package portion of a Bazel label, i.e. everything before the target separator `:`. - * `//pkg:a` -> `//pkg`, `//:logo` -> `//`, `@@repo//pkg:a` -> `@@repo//pkg`. Labels without a `:` - * (not expected from `bazel query` output) are returned unchanged. - */ -fun labelToPackage(label: String): String { - val colon = label.lastIndexOf(':') - return if (colon >= 0) label.substring(0, colon) else label -} diff --git a/cli/src/main/kotlin/com/bazel_diff/hash/ExternalRepoResolver.kt b/cli/src/main/kotlin/com/bazel_diff/hash/ExternalRepoResolver.kt deleted file mode 100644 index 2dfcbe4a..00000000 --- a/cli/src/main/kotlin/com/bazel_diff/hash/ExternalRepoResolver.kt +++ /dev/null @@ -1,98 +0,0 @@ -package com.bazel_diff.hash - -import com.bazel_diff.log.Logger -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.util.concurrent.TimeUnit -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -class ExternalRepoResolver( - private val workingDirectory: Path, - private val bazelPath: Path, - private val outputBase: Path, -) : KoinComponent { - private val logger: Logger by inject() - - private val externalRoot: Path by lazy { outputBase.resolve("external") } - - private val cache = - CacheBuilder.newBuilder() - .build( - CacheLoader.from { repoName: String -> - externalRoot.resolve(repoName).let { - if (Files.exists(it)) { - return@from it - } - } - - externalRoot.resolve("$repoName+").let { - if (Files.exists(it)) { - return@from it - } - } - - resolveBzlModPath(repoName) - }) - - fun resolveExternalRepoRoot(repoName: String): Path { - return cache.get(repoName) - } - - private fun resolveBzlModPath(repoName: String): Path { - // Query result line should look something like "/external//some/bazel/target: