diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8da580a1..484e0dd310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,8 @@ Unreleased changes template. env var. * (pypi) Downgraded versions of packages: `pip` from `24.3.2` to `24.0.0` and `packaging` from `24.2` to `24.0`. +* (proto) now `rules_python` just uses the implementation of `py_proto_library` + from the [upstream](https://github.com/protocolbuffers/protobuf/blob/main/bazel/py_proto_library.bzl). {#v0-0-0-fixed} ### Fixed diff --git a/MODULE.bazel b/MODULE.bazel index 7034357f61..35b0d2cf26 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -9,8 +9,7 @@ bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "rules_cc", version = "0.0.16") bazel_dep(name = "platforms", version = "0.0.4") -# Those are loaded only when using py_proto_library -bazel_dep(name = "rules_proto", version = "7.0.2") +# Those should be loaded only when using py_proto_library bazel_dep(name = "protobuf", version = "29.0-rc2", repo_name = "com_google_protobuf") internal_deps = use_extension("//python/private:internal_deps.bzl", "internal_deps") diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel index d8535a0115..0a1f43f98b 100644 --- a/examples/bzlmod/MODULE.bazel +++ b/examples/bzlmod/MODULE.bazel @@ -16,7 +16,7 @@ local_path_override( bazel_dep(name = "rules_proto", version = "6.0.0-rc1") # (py_proto_library specific) Add the protobuf library for well-known types (e.g. `Any`, `Timestamp`, etc) -bazel_dep(name = "protobuf", version = "27.0", repo_name = "com_google_protobuf") +bazel_dep(name = "protobuf", version = "29.3", repo_name = "com_google_protobuf") # Only needed to make rules_python's CI happy. rules_java 8.3.0+ is needed so # that --java_runtime_version=remotejdk_11 works with Bazel 8. @@ -273,11 +273,5 @@ local_path_override( path = "other_module", ) -bazel_dep(name = "foo_external", version = "") -local_path_override( - module_name = "foo_external", - path = "py_proto_library/foo_external", -) - # example test dependencies bazel_dep(name = "rules_shell", version = "0.3.0", dev_dependency = True) diff --git a/examples/bzlmod/py_proto_library/BUILD.bazel b/examples/bzlmod/py_proto_library/BUILD.bazel index 24436b48ea..d0bc683021 100644 --- a/examples/bzlmod/py_proto_library/BUILD.bazel +++ b/examples/bzlmod/py_proto_library/BUILD.bazel @@ -1,4 +1,3 @@ -load("@bazel_skylib//rules:native_binary.bzl", "native_test") load("@rules_python//python:py_test.bzl", "py_test") py_test( @@ -17,18 +16,3 @@ py_test( "//py_proto_library/example.com/another_proto:message_proto_py_pb2", ], ) - -# Regression test for https://github.com/bazelbuild/rules_python/issues/2515 -# -# This test failed before https://github.com/bazelbuild/rules_python/pull/2516 -# when ran with --legacy_external_runfiles=False (default in Bazel 8.0.0). -native_test( - name = "external_import_test", - src = "@foo_external//:py_binary_with_proto", - # Incompatible with Windows: native_test wrapping a py_binary doesn't work - # on Windows. - target_compatible_with = select({ - "@platforms//os:windows": ["@platforms//:incompatible"], - "//conditions:default": [], - }), -) diff --git a/examples/bzlmod/py_proto_library/foo_external/BUILD.bazel b/examples/bzlmod/py_proto_library/foo_external/BUILD.bazel deleted file mode 100644 index 3fa22e06e7..0000000000 --- a/examples/bzlmod/py_proto_library/foo_external/BUILD.bazel +++ /dev/null @@ -1,22 +0,0 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") -load("@rules_python//python:proto.bzl", "py_proto_library") -load("@rules_python//python:py_binary.bzl", "py_binary") - -package(default_visibility = ["//visibility:public"]) - -proto_library( - name = "proto_lib", - srcs = ["nested/foo/my_proto.proto"], - strip_import_prefix = "/nested/foo", -) - -py_proto_library( - name = "a_proto", - deps = [":proto_lib"], -) - -py_binary( - name = "py_binary_with_proto", - srcs = ["py_binary_with_proto.py"], - deps = [":a_proto"], -) diff --git a/examples/bzlmod/py_proto_library/foo_external/MODULE.bazel b/examples/bzlmod/py_proto_library/foo_external/MODULE.bazel deleted file mode 100644 index 5063f9b2d1..0000000000 --- a/examples/bzlmod/py_proto_library/foo_external/MODULE.bazel +++ /dev/null @@ -1,8 +0,0 @@ -module( - name = "foo_external", - version = "0.0.1", -) - -bazel_dep(name = "rules_python", version = "1.0.0") -bazel_dep(name = "protobuf", version = "28.2", repo_name = "com_google_protobuf") -bazel_dep(name = "rules_proto", version = "7.0.2") diff --git a/examples/bzlmod/py_proto_library/foo_external/WORKSPACE b/examples/bzlmod/py_proto_library/foo_external/WORKSPACE deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/bzlmod/py_proto_library/foo_external/nested/foo/my_proto.proto b/examples/bzlmod/py_proto_library/foo_external/nested/foo/my_proto.proto deleted file mode 100644 index 7b8440cbed..0000000000 --- a/examples/bzlmod/py_proto_library/foo_external/nested/foo/my_proto.proto +++ /dev/null @@ -1,6 +0,0 @@ -syntax = "proto3"; - -package my_proto; - -message MyMessage { -} diff --git a/examples/bzlmod/py_proto_library/foo_external/py_binary_with_proto.py b/examples/bzlmod/py_proto_library/foo_external/py_binary_with_proto.py deleted file mode 100644 index be34264b5a..0000000000 --- a/examples/bzlmod/py_proto_library/foo_external/py_binary_with_proto.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys - -if __name__ == "__main__": - import my_proto_pb2 - sys.exit(0) diff --git a/internal_dev_deps.bzl b/internal_dev_deps.bzl index 0304fb16b7..cd33475f43 100644 --- a/internal_dev_deps.bzl +++ b/internal_dev_deps.bzl @@ -177,13 +177,6 @@ def rules_python_internal_deps(): ], ) - http_archive( - name = "rules_proto", - sha256 = "904a8097fae42a690c8e08d805210e40cccb069f5f9a0f6727cf4faa7bed2c9c", - strip_prefix = "rules_proto-6.0.0-rc1", - url = "https://github.com/bazelbuild/rules_proto/releases/download/6.0.0-rc1/rules_proto-6.0.0-rc1.tar.gz", - ) - http_archive( name = "com_google_protobuf", sha256 = "23082dca1ca73a1e9c6cbe40097b41e81f71f3b4d6201e36c134acc30a1b3660", diff --git a/python/private/proto/BUILD.bazel b/python/private/proto/BUILD.bazel index dd53845638..673a538086 100644 --- a/python/private/proto/BUILD.bazel +++ b/python/private/proto/BUILD.bazel @@ -13,7 +13,6 @@ # limitations under the License. load("@bazel_skylib//:bzl_library.bzl", "bzl_library") -load("@com_google_protobuf//bazel/toolchains:proto_lang_toolchain.bzl", "proto_lang_toolchain") package(default_visibility = ["//visibility:private"]) @@ -30,19 +29,6 @@ bzl_library( srcs = ["py_proto_library.bzl"], visibility = ["//python:__pkg__"], deps = [ - "//python:py_info_bzl", - "@com_google_protobuf//bazel/common:proto_common_bzl", - "@com_google_protobuf//bazel/common:proto_info_bzl", - "@rules_proto//proto:defs", + "@com_google_protobuf//bazel:py_proto_library_bzl", ], ) - -proto_lang_toolchain( - name = "python_toolchain", - command_line = "--python_out=%s", - progress_message = "Generating Python proto_library %{label}", - runtime = "@com_google_protobuf//:protobuf_python", - # NOTE: This isn't *actually* public. It's an implicit dependency of py_proto_library, - # so must be public so user usages of the rule can reference it. - visibility = ["//visibility:public"], -) diff --git a/python/private/proto/py_proto_library.bzl b/python/private/proto/py_proto_library.bzl index 1e9df848ab..bcd96ef7c1 100644 --- a/python/private/proto/py_proto_library.bzl +++ b/python/private/proto/py_proto_library.bzl @@ -14,231 +14,18 @@ """The implementation of the `py_proto_library` rule and its aspect.""" -load("@com_google_protobuf//bazel/common:proto_common.bzl", "proto_common") -load("@com_google_protobuf//bazel/common:proto_info.bzl", "ProtoInfo") -load("//python:py_info.bzl", "PyInfo") -load("//python/api:api.bzl", _py_common = "py_common") - -PY_PROTO_TOOLCHAIN = "@rules_python//python/proto:toolchain_type" - -_PyProtoInfo = provider( - doc = "Encapsulates information needed by the Python proto rules.", - fields = { - "imports": """ - (depset[str]) The field forwarding PyInfo.imports coming from - the proto language runtime dependency.""", - "py_info": "PyInfo from proto runtime (or other deps) to propagate.", - "runfiles_from_proto_deps": """ - (depset[File]) Files from the transitive closure implicit proto - dependencies""", - "transitive_sources": """(depset[File]) The Python sources.""", - }, -) - -def _filter_provider(provider, *attrs): - return [dep[provider] for attr in attrs for dep in attr if provider in dep] - -def _incompatible_toolchains_enabled(): - return getattr(proto_common, "INCOMPATIBLE_ENABLE_PROTO_TOOLCHAIN_RESOLUTION", False) - -def _py_proto_aspect_impl(target, ctx): - """Generates and compiles Python code for a proto_library. - - The function runs protobuf compiler on the `proto_library` target generating - a .py file for each .proto file. - - Args: - target: (Target) A target providing `ProtoInfo`. Usually this means a - `proto_library` target, but not always; you must expect to visit - non-`proto_library` targets, too. - ctx: (RuleContext) The rule context. - - Returns: - ([_PyProtoInfo]) Providers collecting transitive information about - generated files. - """ - _proto_library = ctx.rule.attr - - # Check Proto file names - for proto in target[ProtoInfo].direct_sources: - if proto.is_source and "-" in proto.dirname: - fail("Cannot generate Python code for a .proto whose path contains '-' ({}).".format( - proto.path, - )) - - if _incompatible_toolchains_enabled(): - toolchain = ctx.toolchains[PY_PROTO_TOOLCHAIN] - if not toolchain: - fail("No toolchains registered for '%s'." % PY_PROTO_TOOLCHAIN) - proto_lang_toolchain_info = toolchain.proto - else: - proto_lang_toolchain_info = getattr(ctx.attr, "_aspect_proto_toolchain")[proto_common.ProtoLangToolchainInfo] - - py_common = _py_common.get(ctx) - py_info = py_common.PyInfoBuilder().merge_target( - proto_lang_toolchain_info.runtime, - ).build() - - api_deps = [proto_lang_toolchain_info.runtime] - - generated_sources = [] - proto_info = target[ProtoInfo] - proto_root = proto_info.proto_source_root - if proto_info.direct_sources: - # Generate py files - generated_sources = proto_common.declare_generated_files( - actions = ctx.actions, - proto_info = proto_info, - extension = "_pb2.py", - name_mapper = lambda name: name.replace("-", "_").replace(".", "/"), +load("@com_google_protobuf//bazel:py_proto_library.bzl", _py_proto_library = "py_proto_library") +load("//python/private:deprecation.bzl", "with_deprecation") +load("//python/private:text_util.bzl", "render") + +def py_proto_library(name, **kwargs): + return _py_proto_library( + name = name, + **with_deprecation.symbol( + kwargs, + symbol_name = "py_proto_library", + new_load = "@com_google_protobuf//bazel:py_proto_library.bzl", + old_load = "@rules_python//python:proto.bzl", + snippet = render.call(name, **{k: repr(v) for k, v in kwargs.items()}), ) - - # Handles multiple repository and virtual import cases - if proto_root.startswith(ctx.bin_dir.path): - proto_root = proto_root[len(ctx.bin_dir.path) + 1:] - - plugin_output = ctx.bin_dir.path + "/" + proto_root - - # Import path within the runfiles tree - if proto_root.startswith("external/"): - proto_root = proto_root[len("external") + 1:] - else: - proto_root = ctx.workspace_name + "/" + proto_root - - proto_common.compile( - actions = ctx.actions, - proto_info = proto_info, - proto_lang_toolchain_info = proto_lang_toolchain_info, - generated_files = generated_sources, - plugin_output = plugin_output, - ) - - # Generated sources == Python sources - python_sources = generated_sources - - deps = _filter_provider(_PyProtoInfo, getattr(_proto_library, "deps", [])) - runfiles_from_proto_deps = depset( - transitive = [dep[DefaultInfo].default_runfiles.files for dep in api_deps] + - [dep.runfiles_from_proto_deps for dep in deps], - ) - transitive_sources = depset( - direct = python_sources, - transitive = [dep.transitive_sources for dep in deps], ) - - return [ - _PyProtoInfo( - imports = depset( - # Adding to PYTHONPATH so the generated modules can be - # imported. This is necessary when there is - # strip_import_prefix, the Python modules are generated under - # _virtual_imports. But it's undesirable otherwise, because it - # will put the repo root at the top of the PYTHONPATH, ahead of - # directories added through `imports` attributes. - [proto_root] if "_virtual_imports" in proto_root else [], - transitive = [dep[PyInfo].imports for dep in api_deps] + [dep.imports for dep in deps], - ), - runfiles_from_proto_deps = runfiles_from_proto_deps, - transitive_sources = transitive_sources, - py_info = py_info, - ), - ] - -_py_proto_aspect = aspect( - implementation = _py_proto_aspect_impl, - attrs = _py_common.API_ATTRS | ( - {} if _incompatible_toolchains_enabled() else { - "_aspect_proto_toolchain": attr.label( - default = ":python_toolchain", - ), - } - ), - attr_aspects = ["deps"], - required_providers = [ProtoInfo], - provides = [_PyProtoInfo], - toolchains = [PY_PROTO_TOOLCHAIN] if _incompatible_toolchains_enabled() else [], -) - -def _py_proto_library_rule(ctx): - """Merges results of `py_proto_aspect` in `deps`. - - Args: - ctx: (RuleContext) The rule context. - Returns: - ([PyInfo, DefaultInfo, OutputGroupInfo]) - """ - if not ctx.attr.deps: - fail("'deps' attribute mustn't be empty.") - - pyproto_infos = _filter_provider(_PyProtoInfo, ctx.attr.deps) - default_outputs = depset( - transitive = [info.transitive_sources for info in pyproto_infos], - ) - - py_common = _py_common.get(ctx) - - py_info = py_common.PyInfoBuilder() - py_info.set_has_py2_only_sources(False) - py_info.set_has_py3_only_sources(False) - py_info.transitive_sources.add(default_outputs) - py_info.imports.add([info.imports for info in pyproto_infos]) - py_info.merge_all([ - pyproto_info.py_info - for pyproto_info in pyproto_infos - ]) - return [ - DefaultInfo( - files = default_outputs, - default_runfiles = ctx.runfiles(transitive_files = depset( - transitive = - [default_outputs] + - [info.runfiles_from_proto_deps for info in pyproto_infos], - )), - ), - OutputGroupInfo( - default = depset(), - ), - py_info.build(), - ] - -py_proto_library = rule( - implementation = _py_proto_library_rule, - doc = """ - Use `py_proto_library` to generate Python libraries from `.proto` files. - - The convention is to name the `py_proto_library` rule `foo_py_pb2`, - when it is wrapping `proto_library` rule `foo_proto`. - - `deps` must point to a `proto_library` rule. - - Example: - -```starlark -py_library( - name = "lib", - deps = [":foo_py_pb2"], -) - -py_proto_library( - name = "foo_py_pb2", - deps = [":foo_proto"], -) - -proto_library( - name = "foo_proto", - srcs = ["foo.proto"], -) -```""", - attrs = { - "deps": attr.label_list( - doc = """ - The list of `proto_library` rules to generate Python libraries for. - - Usually this is just the one target: the proto library of interest. - It can be any target providing `ProtoInfo`.""", - providers = [ProtoInfo], - aspects = [_py_proto_aspect], - ), - } | _py_common.API_ATTRS, - provides = [PyInfo], -)