xref: /aosp_15_r20/external/grpc-grpc/bazel/generate_cc.bzl (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1# Copyright 2021 The gRPC Authors
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"""Generates C++ grpc stubs from proto_library rules.
15
16This is an internal rule used by cc_grpc_library, and shouldn't be used
17directly.
18"""
19
20load("@rules_proto//proto:defs.bzl", "ProtoInfo")
21load(
22    "//bazel:protobuf.bzl",
23    "get_include_directory",
24    "get_plugin_args",
25    "get_proto_root",
26    "proto_path_to_generated_filename",
27)
28
29_GRPC_PROTO_HEADER_FMT = "{}.grpc.pb.h"
30_GRPC_PROTO_SRC_FMT = "{}.grpc.pb.cc"
31_GRPC_PROTO_MOCK_HEADER_FMT = "{}_mock.grpc.pb.h"
32_PROTO_HEADER_FMT = "{}.pb.h"
33_PROTO_SRC_FMT = "{}.pb.cc"
34
35def _strip_package_from_path(label_package, file):
36    prefix_len = 0
37    if not file.is_source and file.path.startswith(file.root.path):
38        prefix_len = len(file.root.path) + 1
39
40    path = file.path
41    if len(label_package) == 0:
42        return path
43    if not path.startswith(label_package + "/", prefix_len):
44        fail("'{}' does not lie within '{}'.".format(path, label_package))
45    return path[prefix_len + len(label_package + "/"):]
46
47def _get_srcs_file_path(file):
48    if not file.is_source and file.path.startswith(file.root.path):
49        return file.path[len(file.root.path) + 1:]
50    return file.path
51
52def _join_directories(directories):
53    massaged_directories = [directory for directory in directories if len(directory) != 0]
54    return "/".join(massaged_directories)
55
56def generate_cc_impl(ctx):
57    """Implementation of the generate_cc rule.
58
59    Args:
60      ctx: The context object.
61    Returns:
62      The provider for the generated files.
63    """
64    protos = [f for src in ctx.attr.srcs for f in src[ProtoInfo].check_deps_sources.to_list()]
65    includes = [
66        f
67        for src in ctx.attr.srcs
68        for f in src[ProtoInfo].transitive_imports.to_list()
69    ]
70    outs = []
71    proto_root = get_proto_root(
72        ctx.label.workspace_root,
73    )
74
75    label_package = _join_directories([ctx.label.workspace_root, ctx.label.package])
76    if ctx.executable.plugin:
77        outs += [
78            proto_path_to_generated_filename(
79                _strip_package_from_path(label_package, proto),
80                _GRPC_PROTO_HEADER_FMT,
81            )
82            for proto in protos
83        ]
84        outs += [
85            proto_path_to_generated_filename(
86                _strip_package_from_path(label_package, proto),
87                _GRPC_PROTO_SRC_FMT,
88            )
89            for proto in protos
90        ]
91        if ctx.attr.generate_mocks:
92            outs += [
93                proto_path_to_generated_filename(
94                    _strip_package_from_path(label_package, proto),
95                    _GRPC_PROTO_MOCK_HEADER_FMT,
96                )
97                for proto in protos
98            ]
99    else:
100        outs += [
101            proto_path_to_generated_filename(
102                _strip_package_from_path(label_package, proto),
103                _PROTO_HEADER_FMT,
104            )
105            for proto in protos
106        ]
107        outs += [
108            proto_path_to_generated_filename(
109                _strip_package_from_path(label_package, proto),
110                _PROTO_SRC_FMT,
111            )
112            for proto in protos
113        ]
114    out_files = [ctx.actions.declare_file(out) for out in outs]
115    dir_out = str(ctx.genfiles_dir.path + proto_root)
116
117    arguments = []
118    if ctx.executable.plugin:
119        arguments += get_plugin_args(
120            ctx.executable.plugin,
121            ctx.attr.flags,
122            dir_out,
123            ctx.attr.generate_mocks,
124        )
125        tools = [ctx.executable.plugin]
126    else:
127        arguments.append("--cpp_out=" + ",".join(ctx.attr.flags) + ":" + dir_out)
128        tools = []
129
130    arguments += [
131        "--proto_path={}".format(get_include_directory(i))
132        for i in includes
133    ]
134
135    # Include the output directory so that protoc puts the generated code in the
136    # right directory.
137    arguments.append("--proto_path={0}{1}".format(dir_out, proto_root))
138    arguments += [_get_srcs_file_path(proto) for proto in protos]
139
140    # create a list of well known proto files if the argument is non-None
141    well_known_proto_files = []
142    if ctx.attr.well_known_protos:
143        f = ctx.attr.well_known_protos.files.to_list()[0].dirname
144        if f != "external/com_google_protobuf/src/google/protobuf":
145            print(
146                "Error: Only @com_google_protobuf//:well_known_type_protos is supported",
147            )  # buildifier: disable=print
148        else:
149            # f points to "external/com_google_protobuf/src/google/protobuf"
150            # add -I argument to protoc so it knows where to look for the proto files.
151            arguments.append("-I{0}".format(f + "/../.."))
152            well_known_proto_files = [
153                f
154                for f in ctx.attr.well_known_protos.files.to_list()
155            ]
156
157    ctx.actions.run(
158        inputs = protos + includes + well_known_proto_files,
159        tools = tools,
160        outputs = out_files,
161        executable = ctx.executable._protoc,
162        arguments = arguments,
163        use_default_shell_env = True,
164    )
165
166    return DefaultInfo(files = depset(out_files))
167
168_generate_cc = rule(
169    attrs = {
170        "srcs": attr.label_list(
171            mandatory = True,
172            allow_empty = False,
173            providers = [ProtoInfo],
174        ),
175        "plugin": attr.label(
176            executable = True,
177            providers = ["files_to_run"],
178            cfg = "exec",
179        ),
180        "flags": attr.string_list(
181            mandatory = False,
182            allow_empty = True,
183        ),
184        "well_known_protos": attr.label(mandatory = False),
185        "generate_mocks": attr.bool(
186            default = False,
187            mandatory = False,
188        ),
189        "_protoc": attr.label(
190            default = Label("//external:protocol_compiler"),
191            executable = True,
192            cfg = "exec",
193        ),
194    },
195    # We generate .h files, so we need to output to genfiles.
196    output_to_genfiles = True,
197    implementation = generate_cc_impl,
198)
199
200def generate_cc(well_known_protos, **kwargs):
201    if well_known_protos:
202        _generate_cc(
203            well_known_protos = "@com_google_protobuf//:well_known_type_protos",
204            **kwargs
205        )
206    else:
207        _generate_cc(**kwargs)
208