From ea17c01a3aea25bc00884f993da3000a8fd4a465 Mon Sep 17 00:00:00 2001 From: Maxwell Elliott Date: Tue, 12 May 2026 10:23:47 -0400 Subject: [PATCH 1/3] Add reproducer test for #259 / #227 .bzl macro change does not invalidate callers Both issues describe the same underlying gap: when a BUILD file `load()`s a .bzl macro, editing the macro body is not reflected in `bazel-diff get-impacted-targets`. The user in #259 noticed this after upgrading to Bazel 7. The user in #227 hit it with a shared `all_gke_service.bzl` macro loaded by many BUILD files. In both cases, none of the targets that call the macro are reported as impacted. This adds a minimal in-tree workspace `macro_invalidation` plus an `@Ignore`d E2E test: - `miniature.bzl` defines a `miniature(name, src)` macro that wraps `native.genrule`. - `BUILD` does `load(":miniature.bzl", "miniature")` and calls the macro to produce `//:logo_miniature`. - The test generate-hashes against the original workspace, mutates only `miniature.bzl` (changing the genrule cmd), generate-hashes again, then asserts `//:logo_miniature` shows up in `get-impacted-targets`. Today the assertion fails because bazel-diff does not capture .bzl file contents as part of a Rule's transitive hash. Drop the @Ignore once that's fixed. Refs https://github.com/Tinder/bazel-diff/issues/259 Refs https://github.com/Tinder/bazel-diff/issues/227 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../test/kotlin/com/bazel_diff/e2e/E2ETest.kt | 76 +++++++ .../workspaces/macro_invalidation/BUILD | 10 + .../macro_invalidation/MODULE.bazel | 4 + .../macro_invalidation/MODULE.bazel.lock | 192 ++++++++++++++++++ .../workspaces/macro_invalidation/WORKSPACE | 1 + .../workspaces/macro_invalidation/image.png | 1 + .../macro_invalidation/miniature.bzl | 19 ++ 7 files changed, 303 insertions(+) create mode 100644 cli/src/test/resources/workspaces/macro_invalidation/BUILD create mode 100644 cli/src/test/resources/workspaces/macro_invalidation/MODULE.bazel create mode 100644 cli/src/test/resources/workspaces/macro_invalidation/MODULE.bazel.lock create mode 100644 cli/src/test/resources/workspaces/macro_invalidation/WORKSPACE create mode 100644 cli/src/test/resources/workspaces/macro_invalidation/image.png create mode 100644 cli/src/test/resources/workspaces/macro_invalidation/miniature.bzl diff --git a/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt b/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt index 864596f..051f562 100644 --- a/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt +++ b/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt @@ -1446,6 +1446,82 @@ class E2ETest { .isEqualTo(true) } + // ------------------------------------------------------------------------ + // Reproducer for https://github.com/Tinder/bazel-diff/issues/259 and #227. + // ------------------------------------------------------------------------ + // Both issues describe the same underlying gap: when a BUILD file `load()`s a .bzl + // macro, editing the .bzl macro body is not reflected in `bazel-diff get-impacted-targets`. + // The user in #259 noticed this regression after upgrading to Bazel 7. The user in #227 + // hit it with a shared `all_gke_service.bzl` macro loaded by many BUILD files. In both + // cases, none of the targets that call the macro are reported as impacted. + // + // We reproduce the bug with a minimal in-tree workspace `macro_invalidation`: + // - `miniature.bzl` defines a `miniature(name, src)` macro that wraps `native.genrule`. + // - `BUILD` does `load(":miniature.bzl", "miniature")` and calls `miniature(...)` to + // produce `//:logo_miniature`. + // + // The test generates hashes against the original workspace, mutates only `miniature.bzl` + // (changing the genrule cmd), and asserts that `//:logo_miniature` shows up as impacted. + // Today it does not, because the rule's transitive hash doesn't capture loaded .bzl + // file contents. Once that's fixed, remove @Ignore. + @Test + @org.junit.Ignore( + "Reproducer for https://github.com/Tinder/bazel-diff/issues/259 (and the duplicate " + + "#227): editing a .bzl macro body does not invalidate targets that call the macro. " + + "Today bazel-diff misses .bzl file content as part of a Rule's transitive hash, " + + "so `bazel-diff get-impacted-targets` returns nothing.") + fun testMacroBzlChangeImpactsCallers_reproducerForIssue259And227() { + val workspaceA = copyTestWorkspace("macro_invalidation") + val workspaceB = copyTestWorkspace("macro_invalidation") + + // Mutate only the macro body in B. Anything that ends up in the genrule cmd is enough + // to demonstrate the issue -- the user's example was adding a `print()`. + val bzlInB = File(workspaceB, "miniature.bzl") + val originalBzl = bzlInB.readText() + val mutatedBzl = originalBzl.replace("cmd = \"cp \$< \$@\"", "cmd = \"cp \$< \$@ && true\"") + assertThat(mutatedBzl != originalBzl).isEqualTo(true) + bzlInB.writeText(mutatedBzl) + + val outputDir = temp.newFolder() + val from = File(outputDir, "starting_hashes.json") + val to = File(outputDir, "final_hashes.json") + val impactedTargetsOutput = File(outputDir, "impacted_targets.txt") + + val cli = CommandLine(BazelDiff()) + assertThat( + cli.execute( + "generate-hashes", + "-w", workspaceA.absolutePath, + "-b", "bazel", + from.absolutePath)) + .isEqualTo(0) + assertThat( + cli.execute( + "generate-hashes", + "-w", workspaceB.absolutePath, + "-b", "bazel", + to.absolutePath)) + .isEqualTo(0) + + assertThat( + cli.execute( + "get-impacted-targets", + "-w", workspaceB.absolutePath, + "-b", "bazel", + "-sh", from.absolutePath, + "-fh", to.absolutePath, + "-o", impactedTargetsOutput.absolutePath)) + .isEqualTo(0) + + val impacted = impactedTargetsOutput.readLines().filter { it.isNotBlank() }.toSet() + // Desired behaviour: the macro call site appears as impacted. + // Current behaviour: bazel-diff returns an empty list, so this assertion fails. + val callerImpacted = impacted.any { it.contains("logo_miniature") } + assertThat(callerImpacted) + .transform("//:logo_miniature should be impacted by miniature.bzl edit; got: $impacted") { it } + .isEqualTo(true) + } + private fun copyTestWorkspace(path: String): File { val testProject = temp.newFolder() diff --git a/cli/src/test/resources/workspaces/macro_invalidation/BUILD b/cli/src/test/resources/workspaces/macro_invalidation/BUILD new file mode 100644 index 0000000..a58dae3 --- /dev/null +++ b/cli/src/test/resources/workspaces/macro_invalidation/BUILD @@ -0,0 +1,10 @@ +load(":miniature.bzl", "miniature") + +# Source consumed by the macro-generated genrule. Used so the test workspace builds +# without external rules; the macro itself is what we're testing for invalidation. +exports_files(["image.png"]) + +miniature( + name = "logo_miniature", + src = "image.png", +) diff --git a/cli/src/test/resources/workspaces/macro_invalidation/MODULE.bazel b/cli/src/test/resources/workspaces/macro_invalidation/MODULE.bazel new file mode 100644 index 0000000..bed9fef --- /dev/null +++ b/cli/src/test/resources/workspaces/macro_invalidation/MODULE.bazel @@ -0,0 +1,4 @@ +module( + name = "macro_invalidation_test", + version = "0.0.0", +) diff --git a/cli/src/test/resources/workspaces/macro_invalidation/MODULE.bazel.lock b/cli/src/test/resources/workspaces/macro_invalidation/MODULE.bazel.lock new file mode 100644 index 0000000..23f8dcf --- /dev/null +++ b/cli/src/test/resources/workspaces/macro_invalidation/MODULE.bazel.lock @@ -0,0 +1,192 @@ +{ + "lockFileVersion": 26, + "registryFileHashes": { + "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/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.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", + "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", + "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/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", + "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", + "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.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", + "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", + "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.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "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", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "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", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "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.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", + "https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713", + "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/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", + "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", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "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.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", + "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/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/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-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/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "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/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_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "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.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.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "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.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "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.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", + "https://bcr.bazel.build/modules/rules_java/9.1.0/MODULE.bazel": "ee63f27e36a3fada80342869361182f120a9819c74320e8e65b1e04ba0cd7a9d", + "https://bcr.bazel.build/modules/rules_java/9.1.0/source.json": "da589573c1dee2c9ac4a568b301269a2e8191110ff0345c1a959fa7ea6c4dfd6", + "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/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "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/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_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", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "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.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.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "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.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "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.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/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/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "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/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "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" + }, + "selectedYankedVersions": {}, + "moduleExtensions": {}, + "facts": {} +} diff --git a/cli/src/test/resources/workspaces/macro_invalidation/WORKSPACE b/cli/src/test/resources/workspaces/macro_invalidation/WORKSPACE new file mode 100644 index 0000000..709926c --- /dev/null +++ b/cli/src/test/resources/workspaces/macro_invalidation/WORKSPACE @@ -0,0 +1 @@ +workspace(name = "macro_invalidation_test") diff --git a/cli/src/test/resources/workspaces/macro_invalidation/image.png b/cli/src/test/resources/workspaces/macro_invalidation/image.png new file mode 100644 index 0000000..9906aba --- /dev/null +++ b/cli/src/test/resources/workspaces/macro_invalidation/image.png @@ -0,0 +1 @@ +placeholder-not-a-real-png diff --git a/cli/src/test/resources/workspaces/macro_invalidation/miniature.bzl b/cli/src/test/resources/workspaces/macro_invalidation/miniature.bzl new file mode 100644 index 0000000..c40173a --- /dev/null +++ b/cli/src/test/resources/workspaces/macro_invalidation/miniature.bzl @@ -0,0 +1,19 @@ +"""Macro under test for https://github.com/Tinder/bazel-diff/issues/259 / #227. + +`miniature` is a thin wrapper around `native.genrule`. Issue #259's user reported that +on Bazel 7+, editing this macro (e.g. adding a `print()` call) no longer caused targets +that call the macro to be reported by `bazel-diff get-impacted-targets`. Same shape as +issue #227 where editing a shared .bzl file does not propagate to BUILD files that +`load()` it. +""" + +def miniature(name, src, **kwargs): + native.genrule( + name = name, + srcs = [src], + outs = ["small_" + src], + # Placeholder for `convert ... -resize`: just copy the file. The cmd string is + # what the issue reporter mutated when adding a `print()` to the macro body. + cmd = "cp $< $@", + **kwargs + ) From 1c0e465b7707cf9177c5f54935573e82cb044441 Mon Sep 17 00:00:00 2001 From: Maxwell Elliott Date: Tue, 12 May 2026 14:00:51 -0400 Subject: [PATCH 2/3] Fix #259 / #227 .bzl macro changes now invalidate targets 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 #259 and #227: editing a macro body (e.g. adding `print()`) no longer invalidated any caller because the emitted rule attrs were identical. Fix: BuildGraphHasher now walks every queried SourceFile target's subincludeList -- the `Build.SourceFile.subinclude` proto field, which already lists every .bzl the BUILD file loaded -- softDigests each main-repo .bzl, and mixes the union of digests into the workspace-wide seed. The seed mix-in is conditional on at least one main-repo .bzl actually being hashed. This keeps the seed (and every target's hash) byte-for-byte stable for workspaces that don't load any tracked .bzl files, preserving cached hashes from before this change. The fix is intentionally conservative -- a single .bzl edit re-hashes every target -- because .bzl edits are rare and silently missing them is the worse failure mode. Per-package precision would require mapping each rule to its package's BUILD file, which isn't a direct dep relationship in the query proto. Also updates the macro_invalidation reproducer to use a `print()`-only mutation (matching the user's exact example in #259), removes the @Ignore, and renames the test to a regression test. Refs https://github.com/Tinder/bazel-diff/issues/259 Refs https://github.com/Tinder/bazel-diff/issues/227 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../com/bazel_diff/hash/BuildGraphHasher.kt | 65 ++++++++++++++++++- .../test/kotlin/com/bazel_diff/e2e/E2ETest.kt | 44 +++++++------ 2 files changed, 87 insertions(+), 22 deletions(-) diff --git a/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt b/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt index 53a948f..cf7289b 100644 --- a/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt +++ b/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt @@ -55,8 +55,22 @@ class BuildGraphHasher(private val bazelClient: BazelClient) : KoinComponent { } val seedForFilepaths = runBlocking(Dispatchers.IO) { createSeedForFilepaths(seedFilepaths) } + val seedForBzlFiles = createSeedForBzlFiles(allTargets, modifiedFilepaths) + // Only mix the bzl-files seed when at least one main-repo `.bzl` was actually hashed. + // This keeps the seed (and therefore every target's hash) byte-for-byte stable for + // workspaces that don't load any tracked .bzl files, preserving cached hashes from + // before this change. + val combinedSeed = + if (seedForBzlFiles != null) { + sha256 { + safePutBytes(seedForFilepaths) + safePutBytes(seedForBzlFiles) + } + } else { + seedForFilepaths + } return hashAllTargets( - seedForFilepaths, sourceDigests, allTargets, ignoredAttrs, modifiedFilepaths) + combinedSeed, sourceDigests, allTargets, ignoredAttrs, modifiedFilepaths) } private fun hashSourcefiles( @@ -171,4 +185,53 @@ class BuildGraphHasher(private val bazelClient: BazelClient) : KoinComponent { } } } + + /** + * Builds a seed-hash contribution from the contents of every `.bzl` (and `.scl`) file the + * workspace's BUILD files load. + * + * 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: walk every queried SourceFile target's `subincludeList` (the `Build.SourceFile.subinclude` + * proto field, which already lists every `.bzl` the BUILD file loaded), softDigest each main-repo + * `.bzl`, and roll the union of digests into the workspace-wide seed. This is intentionally + * conservative -- a single `.bzl` edit re-hashes every target -- because `.bzl` edits are rare + * and missing them silently is the worse failure mode. Per-package precision would require + * mapping each rule to its package's BUILD file, which isn't a direct dep relationship in the + * query proto. + * + * 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 createSeedForBzlFiles( + allTargets: List, + modifiedFilepaths: Set + ): ByteArray? { + val subincludes = + allTargets + .asSequence() + .filterIsInstance() + .flatMap { it.subincludeList.asSequence() } + .toSortedSet() + val tracked = mutableListOf>() + for (label in subincludes) { + val digest = + sourceFileHasher.softDigest( + BazelSourceFileTarget(label, ByteArray(0)), modifiedFilepaths) + if (digest != null) tracked += label to digest + } + if (tracked.isEmpty()) return null + return sha256 { + for ((label, digest) in tracked) { + safePutBytes(label.toByteArray()) + safePutBytes(digest) + } + } + } } diff --git a/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt b/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt index 051f562..5880e79 100644 --- a/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt +++ b/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt @@ -1447,38 +1447,42 @@ class E2ETest { } // ------------------------------------------------------------------------ - // Reproducer for https://github.com/Tinder/bazel-diff/issues/259 and #227. + // Regression coverage for https://github.com/Tinder/bazel-diff/issues/259 and #227. // ------------------------------------------------------------------------ // Both issues describe the same underlying gap: when a BUILD file `load()`s a .bzl - // macro, editing the .bzl macro body is not reflected in `bazel-diff get-impacted-targets`. - // The user in #259 noticed this regression after upgrading to Bazel 7. The user in #227 - // hit it with a shared `all_gke_service.bzl` macro loaded by many BUILD files. In both - // cases, none of the targets that call the macro are reported as impacted. + // macro, editing the .bzl macro body in a way that does not change the generated rule's + // attrs is not reflected in `bazel-diff get-impacted-targets`. The user in #259 + // noticed this regression after upgrading to Bazel 7 -- Bazel pre-7 populated + // `Rule.skylark_environment_hash_code` in the query proto so .bzl-content changes + // bubbled in naturally; Bazel 7+ leaves that field empty, so bazel-diff missed the change. // - // We reproduce the bug with a minimal in-tree workspace `macro_invalidation`: + // The fix in BuildGraphHasher.hashAllBazelTargetsAndSourcefiles walks every BUILD + // source file's `subincludeList` (the `subinclude` proto field, which lists every .bzl + // loaded by that BUILD), softDigests each main-repo .bzl file, and mixes the union of + // digests into the seed hash. This restores the pre-Bazel-7 behaviour: a `.bzl`-only + // edit invalidates the targets that depend on it. + // + // The reproducer workspace `macro_invalidation` has: // - `miniature.bzl` defines a `miniature(name, src)` macro that wraps `native.genrule`. // - `BUILD` does `load(":miniature.bzl", "miniature")` and calls `miniature(...)` to // produce `//:logo_miniature`. // - // The test generates hashes against the original workspace, mutates only `miniature.bzl` - // (changing the genrule cmd), and asserts that `//:logo_miniature` shows up as impacted. - // Today it does not, because the rule's transitive hash doesn't capture loaded .bzl - // file contents. Once that's fixed, remove @Ignore. + // The test mutates `miniature.bzl` to add a `print()` call inside the macro body -- this + // does not change any attribute of the emitted `native.genrule`, so a fix that only looks + // at rule attrs would miss it. The user's example in #259 was exactly this pattern. @Test - @org.junit.Ignore( - "Reproducer for https://github.com/Tinder/bazel-diff/issues/259 (and the duplicate " + - "#227): editing a .bzl macro body does not invalidate targets that call the macro. " + - "Today bazel-diff misses .bzl file content as part of a Rule's transitive hash, " + - "so `bazel-diff get-impacted-targets` returns nothing.") - fun testMacroBzlChangeImpactsCallers_reproducerForIssue259And227() { + fun testMacroBzlChangeImpactsCallers_regressionForIssue259And227() { val workspaceA = copyTestWorkspace("macro_invalidation") val workspaceB = copyTestWorkspace("macro_invalidation") - // Mutate only the macro body in B. Anything that ends up in the genrule cmd is enough - // to demonstrate the issue -- the user's example was adding a `print()`. + // Mutate only the macro body in B by adding a `print()` call. This deliberately does + // not touch the genrule attrs the macro emits, so the bug shows up only via the .bzl + // file content rather than any rule-attribute hash diff. val bzlInB = File(workspaceB, "miniature.bzl") val originalBzl = bzlInB.readText() - val mutatedBzl = originalBzl.replace("cmd = \"cp \$< \$@\"", "cmd = \"cp \$< \$@ && true\"") + val mutatedBzl = originalBzl.replace( + "def miniature(name, src, **kwargs):", + "def miniature(name, src, **kwargs):\n print(\"miniature: \" + name)") assertThat(mutatedBzl != originalBzl).isEqualTo(true) bzlInB.writeText(mutatedBzl) @@ -1514,8 +1518,6 @@ class E2ETest { .isEqualTo(0) val impacted = impactedTargetsOutput.readLines().filter { it.isNotBlank() }.toSet() - // Desired behaviour: the macro call site appears as impacted. - // Current behaviour: bazel-diff returns an empty list, so this assertion fails. val callerImpacted = impacted.any { it.contains("logo_miniature") } assertThat(callerImpacted) .transform("//:logo_miniature should be impacted by miniature.bzl edit; got: $impacted") { it } From dfaf94d7a3fa86be22fef1b2b457b8acbfed4c52 Mon Sep 17 00:00:00 2001 From: Maxwell Elliott Date: Tue, 12 May 2026 15:07:50 -0400 Subject: [PATCH 3/3] Version bump --- MODULE.bazel | 2 +- README.md | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 4b74f0d..534b293 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,6 @@ module( name = "bazel-diff", - version = "19.0.1", + version = "20.0.0", compatibility_level = 0, ) diff --git a/README.md b/README.md index 7cb4887..afbe9d5 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,8 @@ workspace. ```terminal Missing required options: '--startingHashes=', '--finalHashes=', '--workspacePath=' -Usage: bazel-diff get-impacted-targets [-v] [--[no-]noBazelrc] [-b=] +Usage: bazel-diff get-impacted-targets [-v] [--[no-]excludeExternalTargets] [-- + [no-]noBazelrc] [-b=] [-d=] -fh= [-o=] @@ -275,6 +276,17 @@ Command-line utility to analyze the state of the bazel build graph 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 @@ -320,7 +332,7 @@ First, add the following snippet to your project: #### Bzlmod snippet ```bazel -bazel_dep(name = "bazel-diff", version = "19.0.1") +bazel_dep(name = "bazel-diff", version = "20.0.0") ``` You can now run the tool with: