bazel/bzlmod: Replace `.bazelrc` dep-flag injection with module `configure` extensions
- Dominant language
- C++
- Stars
- 28.9k
- Forks
- 5.6k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 428
Description
*Title*: bazel/bzlmod: Replace `.bazelrc` dep-flag injection with module `configure` extensions
*Description*:
Follow-up to #47129. To land the Envoy bzlmod we are (for now) injecting configuration into third-party modules via Starlark flags in `.bazelrc`:
```
# grpc
build --@grpc//third_party:ssl_lib=@envoy//bazel:ssl
build --@grpc//third_party:crypto_lib=@envoy//bazel:crypto
# kafka
build --@librdkafka//:with_ssl=false
build --@librdkafka//:with_zlib=false
# remote only
build:remote --@openssl//:parallel_builds
```
This works but has problems that need addressing asap (raised by @jwendell in review):
- **Eager fetching**: a `--@repo//pkg:flag` in `.bazelrc` has to be parsed before the target set is known, so Bazel must load `@repo//pkg:BUILD` (and therefore fetch the repo) on *every* invocation, including `//source/exe:envoy-static`, docs and tooling targets with no kafka/openssl dependency. This applies equally under bzlmod.
- **Coupling via shared `.bazelrc`**: any repo name in `.bazelrc` must exist in the *root* module's repo mapping, so anything that `import`s Envoy's `.bazelrc` (examples, mobile, downstreams that copy it) is forced to `bazel_dep` on `grpc`, `librdkafka` and `openssl` regardless of whether they build those.
- **WORKSPACE no-ops**: the `librdkafka` flags do nothing in WORKSPACE mode (the `configure_make` in `bazel/foreign_cc/BUILD` never `select()`s on them).
- **Label resolution**: bazelrc labels are always resolved from the root module, so Envoy-as-a-dependency cannot express "grpc should link `@envoy//bazel:ssl`" at all.
This is a *consumer injecting a value into a dependency* – not something a module can anticipate with a default – so the bzlmod-native answer is a module extension with a `configure` tag class, set from `MODULE.bazel`. Envoy's registry grpc patch already turns `libssl`/`libcrypto` into `label_flag`s; the extension is just a different writer for that same knob.
**Proposed approach** (in `envoyproxy/bazel-registry`, per module we own/patch – `grpc`, `librdkafka`, `openssl`):
```starlark
# modules/grpc/.envoy/overlay/bazel/ssl.bzl
def _ssl_config_impl(rctx):
rctx.file("BUILD.bazel", """
alias(name = "ssl", actual = "{ssl}", visibility = ["//visibility:public"])
alias(name = "crypto", actual = "{crypto}", visibility = ["//visibility:public"])
""".format(ssl = rctx.attr.ssl, crypto = rctx.attr.crypto))
_ssl_config = repository_rule(
implementation = _ssl_config_impl,
attrs = {"ssl": attr.label(), "crypto": attr.label()},
)
def _grpc_ssl_impl(mctx):
ssl, crypto = "@boringssl//:ssl", "@boringssl//:crypto"
# mctx.modules is root-first: root wins, otherwise first non-root writer
for mod in mctx.modules:
for tag in mod.tags.configure:
ssl, crypto = str(tag.ssl), str(tag.crypto)
break
_ssl_config(name = "grpc_ssl_config", ssl = ssl, crypto = crypto)
grpc = module_extension(
implementation = _grpc_ssl_impl,
tag_classes = {
"configure": tag_class(attrs = {
"ssl": attr.label(mandatory = True),
"crypto": attr.label(mandatory = True),
}),
},
)
```
with `_default_ssl`/`_default_crypto` in the existing `third_party/BUILD` patch aliasing to `@grpc_ssl_config//:ssl`/`:crypto` (keep the `label_flag` as an escape hatch). Envoy then does:
```starlark
# MODULE.bazel
grpc = use_extension("@grpc//bazel:ssl.bzl", "grpc")
grpc.configure(ssl = "@envoy//bazel:ssl", crypto = "@envoy//bazel:crypto")
```
Notes / open questions:
- Tag `attr.label`s are resolved in the *tagging* module's repo mapping, so Envoy-as-dep can pass `@envoy//bazel:ssl` correctly and a downstream root can re-`configure` to take over. Need to decide policy: root-only (`fail` otherwise) vs root-wins/first-writer with a conflict warning – the latter is what the published Envoy module needs.
- `single_version_override`/`archive_override` are orthogonal (they pick *which* grpc); a downstream overriding to BCR grpc will lack the extension and should `fail` with a clear message.
- The hub repo is generated once per extension evaluation, not per configuration, so this replaces *default selection* not `--config` toggling. `@openssl//:parallel_builds` under `build:remote` should instead `configure(parallel_builds_setting = "@envoy//bazel/foreign_cc:parallel_builds")` so the module `select()`s on a label Envoy owns and the `.bazelrc` line stays `@envoy//...`.
- Prefer per-module self-describing extensions (`grpc.configure`, `librdkafka.configure`, `openssl.configure`) over a single Envoy-named hub, so the registry modules remain usable outside Envoy.
- Interim mitigation while this is pending: move the dep flags behind `try-import %workspace%/bazel/deps.bazelrc` so importers of Envoy's `.bazelrc` aren't forced to declare the deps.
End state: `.bazelrc` only carries `@envoy//...` and `@rules_*` flags; dep configuration lives in `MODULE.bazel`; nothing is fetched until analysis reaches it.
*Relevant Links*:
- #47129 (review threads on `.bazelrc` L120-121 and L494)
- https://github.com/envoyproxy/bazel-registry/blob/main/modules/grpc/1.83.0.envoy/patches/grpc.patch (existing `ssl_lib`/`crypto_lib` `label_flag` patch)
- https://github.com/envoyproxy/bazel-registry/tree/main/modules/librdkafka
Contributor guide
Assessment
This issue has not been assessed yet.