|
14 | 14 |
|
15 | 15 | """The implementation of the `py_proto_library` rule and its aspect.""" |
16 | 16 |
|
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()}), |
94 | 29 | ) |
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], |
127 | 30 | ) |
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