protocolbuffers / protocolbuffers/protobuf
Bazel //:protobuf_python does not bundle the upb extension, so bazel consumers silently fall back to the pure-Python backend
@zhangskz is already working on this.
Since Jul 21, 2026.
- Dominant language
- C++
- Stars
- 72k
- Forks
- 16.3k
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 140
Description
What version of protobuf and what language are you using?
Version: main (protoc_version 37-dev); also present on all released versions since 4.21.0
Language: Python (specifically the Bazel build — depending on the //:protobuf_python target rather than the PyPI wheel)
What supported operating system version are you using (e.g. Linux, Windows)?
Linux (Ubuntu 24.04). Note: this is Bazel build-graph behavior and is OS-independent.
What supported runtime / compiler version are you using (e.g. python version, gcc version)?
Python 3.12, Bazel 8.6.0, rules_python 1.6.0. Version-independent — it reproduces on any Python/toolchain because the cause is in the BUILD dependency graph, not runtime code.
What did you do?
Steps to reproduce the behavior:
- Create a
py_binary/py_testin a Bazel workspace that depends on protobuf's//:protobuf_pythontarget (as opposed to installing the PyPI wheel) — e.g. via a generated
py_proto_library, whose runtime is//:protobuf_python. - Do not provide the upb extension through any other dependency (no protoc-gen-validate wheel or other package that vendors
google/_upb). - Run the target and check the active backend:
from google.protobuf.internal import api_implementation print(api_implementation.Type()) - Observed:
python(the pure-Python backend).
Expected:upb— the documented default (python/README.md: "upb … is now the default … requires no special installation").
Root cause
//:protobuf_python (defined in python/build_targets.bzl) only ever bundles the cpp backend extensions, and only under --define=use_fast_cpp_protos=true:
data = select({
"//conditions:default": [],
":use_fast_cpp_protos": [
":google/protobuf/internal/_api_implementation.so",
":google/protobuf/pyext/_message.so",
],
}),
It never depends on the upb extension //python:_message. So google._upb._message lands on a Bazel consumer's runfiles path only if some other dependency happens to ship it; otherwise
api_implementation (which prefers upb when importable) silently falls back to pure-Python. This makes the Bazel //:protobuf_python target diverge from the published wheel, where upb
is the default.
This appears to be an oversight rather than intent:
protobuf_pythonwas introduced inb3cbea18e("[Bazel] Move Python rules to //python", 2022-05-12) with only the cpp backend in itsdataselect — ~3 weeks aftera27ce12d3
(2022-04-22) made upb the top-priority auto-selected backend inapi_implementation.py.google/_upbhas never appeared in this target'sdata/depsin the file's history.- The gap is already worked around in-tree:
conformance/BUILD'sconformance_pythonbinary depends on//:protobuf_pythonand then adds//python:_messageseparately with the comment
# Make upb visible if we need it.— a per-consumer patch for exactly this.
Why fix at the source vs. the per-consumer workaround?
The workaround (adding //python:_message to each consumer, as conformance_python does) works, but has to be repeated by every downstream target and is easy to miss — the failure is
silent (you get a working but slow pure-Python backend, no error), so it's typically only noticed via a perf regression. Fixing it once in protobuf_python:
- makes the Bazel target's default backend match the PyPI wheel and the documented default, so there are no surprises for Bazel consumers;
- removes a footgun where new py_proto consumers silently regress to pure-Python;
- lets the existing
conformance/BUILDworkaround be deleted rather than copied; - is a no-op for the deliberate
use_fast_cpp_protos(cpp) path and for@system_python//:none/:unsupported, so it doesn't change any currently-intended behavior.
Suggested fix
Add :_message to the default deps of protobuf_python so upb is importable (and thus auto-selected) out of the box, matching the wheel. It should be omitted under
use_fast_cpp_protos (the user explicitly chose cpp) and where python is unavailable (:_message is target_compatible_with-incompatible under @system_python//:none / :unsupported).
The conformance/BUILD workaround can then be removed.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.