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