xref: /aosp_15_r20/build/bazel/rules/python/py_proto.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1# Copyright (C) 2022 The Android Open Source Project
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
15load("@bazel_skylib//lib:paths.bzl", "paths")
16load("//build/bazel/rules:proto_file_utils.bzl", "proto_file_utils")
17
18_TYPE_DICTIONARY = {".py": "_pb2.py"}
19
20def _py_proto_sources_gen_rule_impl(ctx):
21    out_files_map = proto_file_utils.generate_proto_action(
22        proto_infos = [dep[ProtoInfo] for dep in ctx.attr.deps],
23        protoc = ctx.executable._protoc,
24        ctx = ctx,
25        type_dictionary = _TYPE_DICTIONARY,
26        out_flags = [],
27        plugin_executable = None,
28        out_arg = "--python_out",
29        mnemonic = "PyProtoGen",
30        transitive_proto_infos = [dep[ProtoInfo] for dep in ctx.attr.transitive_deps],
31    )
32
33    # proto_file_utils generates the files at <package>/<label>
34    # interesting examples
35    # 1. foo.proto will be generated in <package>/<label>/foo_pb2.py
36    # 2. foo.proto with an import prefix in proto_library will be generated in <package>/<label>/<import_prefix>/foo_pb2.py
37    imports = [paths.join("__main__", ctx.label.package, ctx.label.name)]
38
39    output_depset = depset(direct = out_files_map[".py"])
40
41    return [
42        DefaultInfo(files = output_depset),
43        PyInfo(
44            transitive_sources = output_depset,
45            imports = depset(direct = imports),
46        ),
47    ]
48
49_py_proto_sources_gen = rule(
50    implementation = _py_proto_sources_gen_rule_impl,
51    attrs = {
52        "deps": attr.label_list(
53            providers = [ProtoInfo],
54            doc = "proto_library or any other target exposing ProtoInfo provider with *.proto files",
55            mandatory = True,
56        ),
57        "transitive_deps": attr.label_list(
58            providers = [ProtoInfo],
59            doc = """
60proto_library that will be added to aprotoc -I when compiling the direct .proto sources.
61WARNING: This is an experimental attribute and is expected to be deprecated in the future.
62""",
63        ),
64        "_protoc": attr.label(
65            default = Label("//external/protobuf:aprotoc"),
66            executable = True,
67            cfg = "exec",
68        ),
69    },
70)
71
72def py_proto_library(
73        name,
74        deps = [],
75        transitive_deps = [],
76        target_compatible_with = [],
77        data = [],
78        **kwargs):
79    proto_lib_name = name + "_proto_gen"
80
81    _py_proto_sources_gen(
82        name = proto_lib_name,
83        deps = deps,
84        transitive_deps = transitive_deps,
85        **kwargs
86    )
87
88    # There may be a better way to do this, but proto_lib_name appears in both srcs
89    # and deps because it must appear in srcs to cause the protobuf files to
90    # actually be compiled, and it must appear in deps for the PyInfo provider to
91    # be respected and the "imports" path to be included in this library.
92    native.py_library(
93        name = name,
94        srcs = [":" + proto_lib_name],
95        deps = [":" + proto_lib_name] + (["//external/protobuf:libprotobuf-python"] if "libprotobuf-python" not in name else []),
96        data = data,
97        target_compatible_with = target_compatible_with,
98        **kwargs
99    )
100