Skip to content

Commit c1b61ab

Browse files
committed
feat(proto): shim to upstream for py_proto_library
With this PR we attempt to remove our implementation of py_proto_library and we just forward the calls to the upstream. Hopefully this fixes bazel-contrib#2543.
1 parent ea716fe commit c1b61ab

File tree

5 files changed

+16
-250
lines changed

5 files changed

+16
-250
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ Unreleased changes template.
5757
* (rules) deprecation warnings for deprecated symbols have been turned off by
5858
default for now and can be enabled with `RULES_PYTHON_DEPRECATION_WARNINGS`
5959
env var.
60+
* (proto) now `rules_python` just uses the implementation of `py_proto_library`
61+
from the [upstream](https://github.com/protocolbuffers/protobuf/blob/main/bazel/py_proto_library.bzl).
6062

6163
{#v0-0-0-fixed}
6264
### Fixed

Diff for: MODULE.bazel

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ bazel_dep(name = "bazel_skylib", version = "1.7.1")
99
bazel_dep(name = "rules_cc", version = "0.0.16")
1010
bazel_dep(name = "platforms", version = "0.0.4")
1111

12-
# Those are loaded only when using py_proto_library
13-
bazel_dep(name = "rules_proto", version = "7.0.2")
12+
# Those should be loaded only when using py_proto_library
1413
bazel_dep(name = "protobuf", version = "29.0-rc2", repo_name = "com_google_protobuf")
1514

1615
internal_deps = use_extension("//python/private:internal_deps.bzl", "internal_deps")

Diff for: internal_dev_deps.bzl

-7
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,6 @@ def rules_python_internal_deps():
177177
],
178178
)
179179

180-
http_archive(
181-
name = "rules_proto",
182-
sha256 = "904a8097fae42a690c8e08d805210e40cccb069f5f9a0f6727cf4faa7bed2c9c",
183-
strip_prefix = "rules_proto-6.0.0-rc1",
184-
url = "https://github.com/bazelbuild/rules_proto/releases/download/6.0.0-rc1/rules_proto-6.0.0-rc1.tar.gz",
185-
)
186-
187180
http_archive(
188181
name = "com_google_protobuf",
189182
sha256 = "23082dca1ca73a1e9c6cbe40097b41e81f71f3b4d6201e36c134acc30a1b3660",

Diff for: python/private/proto/BUILD.bazel

+1-15
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# limitations under the License.
1414

1515
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
16-
load("@com_google_protobuf//bazel/toolchains:proto_lang_toolchain.bzl", "proto_lang_toolchain")
1716

1817
package(default_visibility = ["//visibility:private"])
1918

@@ -30,19 +29,6 @@ bzl_library(
3029
srcs = ["py_proto_library.bzl"],
3130
visibility = ["//python:__pkg__"],
3231
deps = [
33-
"//python:py_info_bzl",
34-
"@com_google_protobuf//bazel/common:proto_common_bzl",
35-
"@com_google_protobuf//bazel/common:proto_info_bzl",
36-
"@rules_proto//proto:defs",
32+
"@com_google_protobuf//bazel:py_proto_library_bzl",
3733
],
3834
)
39-
40-
proto_lang_toolchain(
41-
name = "python_toolchain",
42-
command_line = "--python_out=%s",
43-
progress_message = "Generating Python proto_library %{label}",
44-
runtime = "@com_google_protobuf//:protobuf_python",
45-
# NOTE: This isn't *actually* public. It's an implicit dependency of py_proto_library,
46-
# so must be public so user usages of the rule can reference it.
47-
visibility = ["//visibility:public"],
48-
)

Diff for: python/private/proto/py_proto_library.bzl

+12-226
Original file line numberDiff line numberDiff line change
@@ -14,231 +14,17 @@
1414

1515
"""The implementation of the `py_proto_library` rule and its aspect."""
1616

17-
load("@com_google_protobuf//bazel/common:proto_common.bzl", "proto_common")
18-
load("@com_google_protobuf//bazel/common:proto_info.bzl", "ProtoInfo")
19-
load("//python:py_info.bzl", "PyInfo")
20-
load("//python/api:api.bzl", _py_common = "py_common")
21-
22-
PY_PROTO_TOOLCHAIN = "@rules_python//python/proto:toolchain_type"
23-
24-
_PyProtoInfo = provider(
25-
doc = "Encapsulates information needed by the Python proto rules.",
26-
fields = {
27-
"imports": """
28-
(depset[str]) The field forwarding PyInfo.imports coming from
29-
the proto language runtime dependency.""",
30-
"py_info": "PyInfo from proto runtime (or other deps) to propagate.",
31-
"runfiles_from_proto_deps": """
32-
(depset[File]) Files from the transitive closure implicit proto
33-
dependencies""",
34-
"transitive_sources": """(depset[File]) The Python sources.""",
35-
},
36-
)
37-
38-
def _filter_provider(provider, *attrs):
39-
return [dep[provider] for attr in attrs for dep in attr if provider in dep]
40-
41-
def _incompatible_toolchains_enabled():
42-
return getattr(proto_common, "INCOMPATIBLE_ENABLE_PROTO_TOOLCHAIN_RESOLUTION", False)
43-
44-
def _py_proto_aspect_impl(target, ctx):
45-
"""Generates and compiles Python code for a proto_library.
46-
47-
The function runs protobuf compiler on the `proto_library` target generating
48-
a .py file for each .proto file.
49-
50-
Args:
51-
target: (Target) A target providing `ProtoInfo`. Usually this means a
52-
`proto_library` target, but not always; you must expect to visit
53-
non-`proto_library` targets, too.
54-
ctx: (RuleContext) The rule context.
55-
56-
Returns:
57-
([_PyProtoInfo]) Providers collecting transitive information about
58-
generated files.
59-
"""
60-
_proto_library = ctx.rule.attr
61-
62-
# Check Proto file names
63-
for proto in target[ProtoInfo].direct_sources:
64-
if proto.is_source and "-" in proto.dirname:
65-
fail("Cannot generate Python code for a .proto whose path contains '-' ({}).".format(
66-
proto.path,
67-
))
68-
69-
if _incompatible_toolchains_enabled():
70-
toolchain = ctx.toolchains[PY_PROTO_TOOLCHAIN]
71-
if not toolchain:
72-
fail("No toolchains registered for '%s'." % PY_PROTO_TOOLCHAIN)
73-
proto_lang_toolchain_info = toolchain.proto
74-
else:
75-
proto_lang_toolchain_info = getattr(ctx.attr, "_aspect_proto_toolchain")[proto_common.ProtoLangToolchainInfo]
76-
77-
py_common = _py_common.get(ctx)
78-
py_info = py_common.PyInfoBuilder().merge_target(
79-
proto_lang_toolchain_info.runtime,
80-
).build()
81-
82-
api_deps = [proto_lang_toolchain_info.runtime]
83-
84-
generated_sources = []
85-
proto_info = target[ProtoInfo]
86-
proto_root = proto_info.proto_source_root
87-
if proto_info.direct_sources:
88-
# Generate py files
89-
generated_sources = proto_common.declare_generated_files(
90-
actions = ctx.actions,
91-
proto_info = proto_info,
92-
extension = "_pb2.py",
93-
name_mapper = lambda name: name.replace("-", "_").replace(".", "/"),
17+
load("@com_google_protobuf//bazel:py_proto_library.bzl", _py_proto_library = "py_proto_library")
18+
load("//python/private:deprecation.bzl", "with_deprecation")
19+
load("//python/private:text_utils.bzl", "render")
20+
21+
def py_proto_library(**kwargs):
22+
return _py_proto_library(
23+
**with_deprecation.symbol(
24+
kwargs,
25+
symbol_name = "py_proto_library",
26+
new_load = "@com_google_protobuf//bazel:py_proto_library.bzl",
27+
old_load = "@rules_python//python:proto.bzl",
28+
snippet = render.call(name, **{k: repr(v) for k, v in kwargs.items()}),
9429
)
95-
96-
# Handles multiple repository and virtual import cases
97-
if proto_root.startswith(ctx.bin_dir.path):
98-
proto_root = proto_root[len(ctx.bin_dir.path) + 1:]
99-
100-
plugin_output = ctx.bin_dir.path + "/" + proto_root
101-
102-
# Import path within the runfiles tree
103-
if proto_root.startswith("external/"):
104-
proto_root = proto_root[len("external") + 1:]
105-
else:
106-
proto_root = ctx.workspace_name + "/" + proto_root
107-
108-
proto_common.compile(
109-
actions = ctx.actions,
110-
proto_info = proto_info,
111-
proto_lang_toolchain_info = proto_lang_toolchain_info,
112-
generated_files = generated_sources,
113-
plugin_output = plugin_output,
114-
)
115-
116-
# Generated sources == Python sources
117-
python_sources = generated_sources
118-
119-
deps = _filter_provider(_PyProtoInfo, getattr(_proto_library, "deps", []))
120-
runfiles_from_proto_deps = depset(
121-
transitive = [dep[DefaultInfo].default_runfiles.files for dep in api_deps] +
122-
[dep.runfiles_from_proto_deps for dep in deps],
123-
)
124-
transitive_sources = depset(
125-
direct = python_sources,
126-
transitive = [dep.transitive_sources for dep in deps],
12730
)
128-
129-
return [
130-
_PyProtoInfo(
131-
imports = depset(
132-
# Adding to PYTHONPATH so the generated modules can be
133-
# imported. This is necessary when there is
134-
# strip_import_prefix, the Python modules are generated under
135-
# _virtual_imports. But it's undesirable otherwise, because it
136-
# will put the repo root at the top of the PYTHONPATH, ahead of
137-
# directories added through `imports` attributes.
138-
[proto_root] if "_virtual_imports" in proto_root else [],
139-
transitive = [dep[PyInfo].imports for dep in api_deps] + [dep.imports for dep in deps],
140-
),
141-
runfiles_from_proto_deps = runfiles_from_proto_deps,
142-
transitive_sources = transitive_sources,
143-
py_info = py_info,
144-
),
145-
]
146-
147-
_py_proto_aspect = aspect(
148-
implementation = _py_proto_aspect_impl,
149-
attrs = _py_common.API_ATTRS | (
150-
{} if _incompatible_toolchains_enabled() else {
151-
"_aspect_proto_toolchain": attr.label(
152-
default = ":python_toolchain",
153-
),
154-
}
155-
),
156-
attr_aspects = ["deps"],
157-
required_providers = [ProtoInfo],
158-
provides = [_PyProtoInfo],
159-
toolchains = [PY_PROTO_TOOLCHAIN] if _incompatible_toolchains_enabled() else [],
160-
)
161-
162-
def _py_proto_library_rule(ctx):
163-
"""Merges results of `py_proto_aspect` in `deps`.
164-
165-
Args:
166-
ctx: (RuleContext) The rule context.
167-
Returns:
168-
([PyInfo, DefaultInfo, OutputGroupInfo])
169-
"""
170-
if not ctx.attr.deps:
171-
fail("'deps' attribute mustn't be empty.")
172-
173-
pyproto_infos = _filter_provider(_PyProtoInfo, ctx.attr.deps)
174-
default_outputs = depset(
175-
transitive = [info.transitive_sources for info in pyproto_infos],
176-
)
177-
178-
py_common = _py_common.get(ctx)
179-
180-
py_info = py_common.PyInfoBuilder()
181-
py_info.set_has_py2_only_sources(False)
182-
py_info.set_has_py3_only_sources(False)
183-
py_info.transitive_sources.add(default_outputs)
184-
py_info.imports.add([info.imports for info in pyproto_infos])
185-
py_info.merge_all([
186-
pyproto_info.py_info
187-
for pyproto_info in pyproto_infos
188-
])
189-
return [
190-
DefaultInfo(
191-
files = default_outputs,
192-
default_runfiles = ctx.runfiles(transitive_files = depset(
193-
transitive =
194-
[default_outputs] +
195-
[info.runfiles_from_proto_deps for info in pyproto_infos],
196-
)),
197-
),
198-
OutputGroupInfo(
199-
default = depset(),
200-
),
201-
py_info.build(),
202-
]
203-
204-
py_proto_library = rule(
205-
implementation = _py_proto_library_rule,
206-
doc = """
207-
Use `py_proto_library` to generate Python libraries from `.proto` files.
208-
209-
The convention is to name the `py_proto_library` rule `foo_py_pb2`,
210-
when it is wrapping `proto_library` rule `foo_proto`.
211-
212-
`deps` must point to a `proto_library` rule.
213-
214-
Example:
215-
216-
```starlark
217-
py_library(
218-
name = "lib",
219-
deps = [":foo_py_pb2"],
220-
)
221-
222-
py_proto_library(
223-
name = "foo_py_pb2",
224-
deps = [":foo_proto"],
225-
)
226-
227-
proto_library(
228-
name = "foo_proto",
229-
srcs = ["foo.proto"],
230-
)
231-
```""",
232-
attrs = {
233-
"deps": attr.label_list(
234-
doc = """
235-
The list of `proto_library` rules to generate Python libraries for.
236-
237-
Usually this is just the one target: the proto library of interest.
238-
It can be any target providing `ProtoInfo`.""",
239-
providers = [ProtoInfo],
240-
aspects = [_py_proto_aspect],
241-
),
242-
} | _py_common.API_ATTRS,
243-
provides = [PyInfo],
244-
)

0 commit comments

Comments
 (0)