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