8000 Implement py_proto_library (#832) · cflewis/rules_python@0d3c4f7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0d3c4f7

Browse files
authored
Implement py_proto_library (bazel-contrib#832)
* Add py_proto_library * Bump versions of rules_proto and protobuf * Update documentation * Bump rules_pkg version
1 parent 3ebd927 commit 0d3c4f7

File tree

8 files changed

+281
-5
lines changed

8 files changed

+281
-5
lines changed

MODULE.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ module(
66

77
bazel_dep(name = "platforms", version = "0.0.4")
88

9+
# Those are loaded only when using py_proto_library
10+
bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
11+
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "21.7")
12+
913
internal_deps = use_extension("@rules_python//python:extensions.bzl", "internal_deps")
1014
internal_deps.install()
1115
use_repo(

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
## Overview
77

88
This repository is the home of the core Python rules -- `py_library`,
9-
`py_binary`, `py_test`, and related symbols that provide the basis for Python
9+
`py_binary`, `py_test`, `py_proto_library`, and related symbols that provide the basis for Python
1010
support in Bazel. It also contains package installation rules for integrating with PyPI and other package indices. Documentation lives in the
1111
[`docs/`](https://github.com/bazelbuild/rules_python/tree/main/docs)
1212
directory and in the

internal_deps.bzl

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ def rules_python_internal_deps():
2020
http_archive,
2121
name = "rules_pkg",
2222
urls = [
23-
"https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.2.4/rules_pkg-0.2.4.tar.gz",
24-
"https://github.com/bazelbuild/rules_pkg/releases/download/0.2.4/rules_pkg-0.2.4.tar.gz",
23+
"https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.7.0/rules_pkg-0.7.0.tar.gz",
24+
"https://github.com/bazelbuild/rules_pkg/releases/download/0.7.0/rules_pkg-0.7.0.tar.gz",
2525
],
26-
sha256 = "4ba8f4ab0ff85f2484287ab06c0d871dcb31cc54d439457d28fd4ae14b18450a",
26+
sha256 = "8a298e832762eda1830597d64fe7db58178aa84cd5926d76d5b744d6558941c2",
2727
)
2828

2929
maybe(
@@ -124,3 +124,24 @@ def rules_python_internal_deps():
124124
strip_prefix = "bazel-integration-testing-165440b2dbda885f8d1ccb8d0f417e6cf8c54f17",
125125
sha256 = "2401b1369ef44cc42f91dc94443ef491208dbd06da1e1e10b702d8c189f098e3",
126126
)
127+
128+
maybe(
129+
http_archive,
130+
name = "rules_proto",
131+
sha256 = "dc3fb206a2cb3441b485eb1e423165b231235a1ea9b031b4433cf7bc1fa460dd",
132+
strip_prefix = "rules_proto-5.3.0-21.7",
133+
urls = [
134+
"https://github.com/bazelbuild/rules_proto/archive/refs/tags/5.3.0-21.7.tar.gz",
135+
],
136+
)
137+
138+
maybe(
139+
http_archive,
140+
name = "com_google_protobuf",
141+
sha256 = "75be42bd736f4df6d702a0e4e4d30de9ee40eac024c4b845d17ae4cc831fe4ae",
142+
strip_prefix = "protobuf-21.7",
143+
urls = [
144+
"https://mirror.bazel.build/github.com/protocolbuffers/protobuf/archive/v21.7.tar.gz",
145+
"https://github.com/protocolbuffers/protobuf/archive/v21.7.tar.gz",
146+
],
147+
)

internal_setup.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
load("@bazel_gazelle//:deps.bzl", "gazel 9E88 le_dependencies")
1818
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
1919
load("@build_bazel_integration_testing//tools:repositories.bzl", "bazel_binaries")
20+
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
2021
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
22+
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
2123
load("//:version.bzl", "SUPPORTED_BAZEL_VERSIONS")
2224
load("//gazelle:deps.bzl", _go_repositories = "gazelle_deps")
2325
load("//python/pip_install:repositories.bzl", "pip_install_dependencies")
@@ -41,3 +43,8 @@ def rules_python_internal_setup():
4143
go_register_toolchains(version = "1.19.2")
4244

4345
gazelle_dependencies()
46+
47+
rules_proto_dependencies()
48+
rules_proto_toolchains()
49+
50+
protobuf_deps()

python/private/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ licenses(["notice"]) # Apache 2.0
1919

2020
filegroup(
2121
name = "distribution",
22-
srcs = glob(["**"]),
22+
srcs = glob(["**"]) + ["//python/private/proto:distribution"],
2323
visibility = ["//python:__pkg__"],
2424
)
2525

python/private/proto/BUILD

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2022 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
load("@rules_proto//proto:defs.bzl", "proto_lang_toolchain")
16+
17+
package(default_visibility = ["//visibility:public"])
18+
19+
licenses(["notice"]) # Apache 2.0
20+
21+
filegroup(
22+
name = "distribution",
23+
srcs = glob(["**"]),
24+
visibility = ["//python/private:__pkg__"],
25+
)
26+
27+
proto_lang_toolchain(
28+
name = "python_toolchain",
29+
command_line = "--python_out=%s",
30+
progress_message = "Generating Python proto_library %{label}",
31+
runtime = "@com_google_protobuf//:protobuf_python",
32+
)
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Copyright 2022 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""The implementation of the `py_proto_library` rule and its aspect."""
16+
17+
load("@rules_proto//proto:defs.bzl", "ProtoInfo", "proto_common")
18+
load("//python:defs.bzl", "PyInfo")
19+
20+
ProtoLangToolchainInfo = proto_common.ProtoLangToolchainInfo
21+
22+
_PyProtoInfo = provider(
23+
doc = "Encapsulates information needed by the Python proto rules.",
24+
fields = {
25+
"runfiles_from_proto_deps": """
26+
(depset[File]) Files from the transitive closure implicit proto
27+
dependencies""",
28+
"transitive_sources": """(depset[File]) The Python sources.""",
29+
},
30+
)
31+
32+
def _filter_provider(provider, *attrs):
33+
return [dep[provider] for attr in attrs for dep in attr if provider in dep]
34+
35+
def _py_proto_aspect_impl(target, ctx):
36+
"""Generates and compiles Python code for a proto_library.
37+
38+
The function runs protobuf compiler on the `proto_library` target generating
39+
a .py file for each .proto file.
40+
41+
Args:
42+
target: (Target) A target providing `ProtoInfo`. Usually this means a
43+
`proto_library` target, but not always; you must expect to visit
44+
non-`proto_library` targets, too.
45+
ctx: (RuleContext) The rule context.
46+
47+
Returns:
48+
([_PyProtoInfo]) Providers collecting transitive information about
49+
generated files.
50+
"""
51+
52+
_proto_library = ctx.rule.attr
53+
54+
# Check Proto file names
55+
for proto in target[ProtoInfo].direct_sources:
56+
if proto.is_source and "-" in proto.dirname:
57+
fail("Cannot generate Python code for a .proto whose path contains '-' ({}).".format(
58+
proto.path,
59+
))
60+
61+
proto_lang_toolchain_info = ctx.attr._aspect_proto_toolchain[ProtoLangToolchainInfo]
62+
api_deps = [proto_lang_toolchain_info.runtime]
63+
64+
generated_sources = []
65+
proto_info = target[ProtoInfo]
66+
if proto_info.direct_sources:
67+
# Generate py files
68+
generated_sources = proto_common.declare_generated_files(
69+
actions = ctx.actions,
70+
proto_info = proto_info,
71+
extension = "_pb2.py",
72+
name_mapper = lambda name: name.replace("-", "_").replace(".", "/"),
73+
)
74+
75+
proto_common.compile(
76+
actions = ctx.actions,
77+
proto_info = proto_info,
78+
proto_lang_toolchain_info = proto_lang_toolchain_info,
79+
generated_files = generated_sources,
80+
plugin_output = ctx.bin_dir.path,
81+
)
82+
83+
# Generated sources == Python sources
84+
python_sources = generated_sources
85+
86+
deps = _filter_provider(_PyProtoInfo, getattr(_proto_library, "deps", []))
87+
runfiles_from_proto_deps = depset(
88+
transitive = [dep[DefaultInfo].default_runfiles.files for dep in 1241 api_deps] +
89+
[dep.runfiles_from_proto_deps for dep in deps],
90+
)
91+
transitive_sources = depset(
92+
direct = python_sources,
93+
transitive = [dep.transitive_sources for dep in deps],
94+
)
95+
96+
return [
97+
_PyProtoInfo(
98+
runfiles_from_proto_deps = runfiles_from_proto_deps,
99+
transitive_sources = transitive_sources,
100+
),
101+
]
102+
103+
_py_proto_aspect = aspect(
104+
implementation = _py_proto_aspect_impl,
105+
attrs = {
106+
"_aspect_proto_toolchain": attr.label(
107+
default = ":python_toolchain",
108+
),
109+
},
110+
attr_aspects = ["deps"],
111+
required_providers = [ProtoInfo],
112+
provides = [_PyProtoInfo],
113+
)
114+
115+
def _py_proto_library_rule(ctx):
116+
"""Merges results of `py_proto_aspect` in `deps`.
117+
118+
Args:
119+
ctx: (RuleContext) The rule context.
120+
Returns:
121+
([PyInfo, DefaultInfo, OutputGroupInfo])
122+
"""
123+
if not ctx.attr.deps:
124+
fail("'deps' attribute mustn't be empty.")
125+
126+
pyproto_infos = _filter_provider(_PyProtoInfo, ctx.attr.deps)
127+
default_outputs = depset(
128+
transitive = [info.transitive_sources for info in pyproto_infos],
129+
)
130+
131+
return [
132+
DefaultInfo(
133+
files = default_outputs,
134+
default_runfiles = ctx.runfiles(transitive_files = depset(
135+
transitive =
136+
[default_outputs] +
137+
[info.runfiles_from_proto_deps for info in pyproto_infos],
138+
)),
139+
),
140+
OutputGroupInfo(
141+
default = depset(),
142+
),
143+
PyInfo(
144+
transitive_sources = default_outputs,
145+
# Proto always produces 2- and 3- compatible source files
146+
has_py2_only_sources = False,
147+
has_py3_only_sources = False,
148+
),
149+
]
150+
151+
py_proto_library = rule(
152+
implementation = _py_proto_library_rule,
153+
doc = """
154+
Use `py_proto_library` to generate Python libraries from `.proto` files.
155+
156+
The convention is to name the `py_proto_library` rule `foo_py_pb2`,
157+
when it is wrapping `proto_library` rule `foo_proto`.
158+
159+
`deps` must point to a `proto_library` rule.
160+
161+
Example:
162+
163+
```starlark
164+
py_library(
165+
name = "lib",
166+
deps = [":foo_py_pb2"],
167+
)
168+
169+
py_proto_library(
170+
name = "foo_py_pb2",
171+
deps = [":foo_proto"],
172+
)
173+
174+
proto_library(
175+
name = "foo_proto",
176+
srcs = ["foo.proto"],
177+
)
178+
```""",
179+
attrs = {
180+
"deps": attr.label_list(
181+
doc = """
182+
The list of `proto_library` rules to generate Python libraries for.
183+
184+
Usually this is just the one target: the proto library of interest.
185+
It can be any target providing `ProtoInfo`.""",
186+
providers = [ProtoInfo],
187+
aspects = [_py_proto_aspect],
188+
),
189+
},
190+
provides = [PyInfo],
191+
)

python/proto.bzl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2022 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
Python proto library.
17+
"""
18+
19+
load("//python/private/proto:py_proto_library.bzl", _py_proto_library = "py_proto_library")
20+
21+
py_proto_library = _py_proto_library

0 commit comments

Comments
 (0)
0