Files
ResInsight/GrpcInterface/Python/check_gencode_compatibility.py
T
Magne Sjaastad d0d472aaa2 #14723 Python: Pin grpcio-tools to keep protobuf 6 gencode loadable
grpcio-tools 1.82.0+ (including its 1.82.0 pre-releases) bundles a
protoc that emits protobuf 7.35.x gencode, which no protobuf 6 runtime
can load. Pin grpcio-tools below that (excluding pre-releases via the
'.dev0' trick, since a plain '<1.82' bound already let a 1.82.0rc
release slip through once) and add a CI check that verifies the
generated *_pb2.py files stay within the protobuf floor declared in
pyproject.toml.

Only the load-bearing bounds are kept: protobuf's '>=6.33.5' floor
(must match the emitted gencode) and grpcio-tools' '<1.82.0.dev0'
ceiling. Non-load-bearing bounds (protobuf's '<8', grpcio-tools'
'>=1.81') were dropped since they don't change dependency resolution
or protect against anything the above doesn't already cover.
2026-09-11 05:46:28 -04:00

78 lines
2.7 KiB
Python

"""Check that the generated protobuf code can be loaded by every supported runtime.
The protobuf runtime refuses to load gencode newer than itself, so the gencode
version emitted by grpcio-tools must not exceed the lower bound declared for
protobuf in pyproject.toml. Nothing ties those two together: a grpcio-tools
upgrade can move the gencode a full major version without any edit here, and
the resulting package still installs cleanly and only fails at import time.
"""
import re
import sys
import tomllib
from pathlib import Path
from packaging.requirements import Requirement
from packaging.version import Version
HERE = Path(__file__).parent
GENERATED = HERE / "rips" / "generated"
GENCODE_PATTERN = re.compile(r"^# Protobuf Python Version: (\S+)", re.MULTILINE)
def declared_protobuf_floor() -> Version:
"""Lowest protobuf runtime a user of this package can end up with."""
with (HERE / "pyproject.toml").open("rb") as f:
dependencies = tomllib.load(f)["project"]["dependencies"]
requirements = [Requirement(d) for d in dependencies]
protobuf = next((r for r in requirements if r.name == "protobuf"), None)
if protobuf is None:
sys.exit("protobuf is not declared in [project].dependencies")
floors = [
Version(s.version) for s in protobuf.specifier if s.operator in (">=", "==")
]
if not floors:
sys.exit(
f"protobuf dependency '{protobuf}' has no lower bound to check against"
)
return max(floors)
def gencode_versions() -> dict[str, Version]:
versions = {}
for path in sorted(GENERATED.glob("*_pb2.py")):
match = GENCODE_PATTERN.search(path.read_text(encoding="utf-8"))
if match is None:
sys.exit(f"{path.name} has no gencode version header")
versions[path.name] = Version(match.group(1))
return versions
def main() -> None:
floor = declared_protobuf_floor()
versions = gencode_versions()
if not versions:
sys.exit(f"no generated *_pb2.py files in {GENERATED}, nothing was verified")
too_new = {name: v for name, v in versions.items() if v > floor}
if too_new:
listing = "\n".join(f" {name}: gencode {v}" for name, v in too_new.items())
sys.exit(
f"Generated code is newer than the oldest supported protobuf runtime ({floor}).\n"
f"{listing}\n"
"Pin grpcio-tools to a release emitting older gencode, or raise the protobuf "
"lower bound in pyproject.toml. Note that raising it drops support for runtimes "
"below the new bound."
)
print(
f"{len(versions)} generated files, gencode {max(versions.values())}, runtime floor {floor}: OK"
)
if __name__ == "__main__":
main()