• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 The Bazel Authors. All rights reserved.
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
15"""Toolchain for compiling rust stubs from protobuf and gRPC."""
16
17# buildifier: disable=bzl-visibility
18load("//rust/private:utils.bzl", "name_to_crate_name")
19
20def generated_file_stem(file_path):
21    """Returns the basename of a file without any extensions.
22
23    Example:
24    ```python
25    content.append("pub mod %s;" % _generated_file_stem(f))
26    ```
27
28    Args:
29        file_path (string): A path to a file
30
31    Returns:
32        string: The file stem of the filename
33    """
34    basename = file_path.rsplit("/", 2)[-1]
35    basename = name_to_crate_name(basename)
36    return basename.rsplit(".", 2)[0]
37
38def rust_generate_proto(
39        ctx,
40        transitive_descriptor_sets,
41        protos,
42        imports,
43        output_dir,
44        proto_toolchain,
45        is_grpc = False):
46    """Generate a proto compilation action.
47
48    Args:
49        ctx (ctx): rule context.
50        transitive_descriptor_sets (depset): descriptor generated by previous protobuf libraries.
51        protos (list): list of paths of protos to compile.
52        imports (depset): directory, relative to the package, to output the list of stubs.
53        output_dir (str): The basename of the output directory for for the output generated stubs
54        proto_toolchain (ToolchainInfo): The toolchain for rust-proto compilation. See `rust_proto_toolchain`
55        is_grpc (bool, optional): generate gRPC stubs. Defaults to False.
56
57    Returns:
58        list: the list of generate stubs (File)
59    """
60
61    tools = [
62        proto_toolchain.protoc,
63        proto_toolchain.proto_plugin,
64    ]
65    executable = proto_toolchain.protoc
66    args = ctx.actions.args()
67
68    if not protos:
69        fail("Protobuf compilation requested without inputs!")
70    paths = ["%s/%s" % (output_dir, generated_file_stem(i)) for i in protos.to_list()]
71    outs = [ctx.actions.declare_file(path + ".rs") for path in paths]
72    output_directory = outs[0].dirname
73
74    if is_grpc:
75        # Add grpc stubs to the list of outputs
76        grpc_files = [ctx.actions.declare_file(path + "_grpc.rs") for path in paths]
77        outs.extend(grpc_files)
78
79        # gRPC stubs is generated only if a service is defined in the proto,
80        # so we create an empty grpc module in the other case.
81        tools.append(proto_toolchain.grpc_plugin)
82        tools.append(ctx.executable._optional_output_wrapper)
83        args.add_all(grpc_files)
84        args.add_all([
85            "--",
86            proto_toolchain.protoc,
87            "--plugin=protoc-gen-grpc-rust=" + proto_toolchain.grpc_plugin.path,
88            "--grpc-rust_out=" + output_directory,
89        ])
90        executable = ctx.executable._optional_output_wrapper
91
92    args.add_all([
93        "--plugin=protoc-gen-rust=" + proto_toolchain.proto_plugin.path,
94        "--rust_out=" + output_directory,
95    ])
96
97    args.add_joined(
98        transitive_descriptor_sets,
99        join_with = ":",
100        format_joined = "--descriptor_set_in=%s",
101    )
102
103    args.add_all(protos)
104    ctx.actions.run(
105        inputs = depset(
106            transitive = [
107                transitive_descriptor_sets,
108                imports,
109            ],
110        ),
111        outputs = outs,
112        tools = tools,
113        progress_message = "Generating Rust protobuf stubs",
114        mnemonic = "RustProtocGen",
115        executable = executable,
116        arguments = [args],
117    )
118    return outs
119
120def _rust_proto_toolchain_impl(ctx):
121    return platform_common.ToolchainInfo(
122        edition = ctx.attr.edition,
123        grpc_compile_deps = ctx.attr.grpc_compile_deps,
124        grpc_plugin = ctx.file.grpc_plugin,
125        proto_compile_deps = ctx.attr.proto_compile_deps,
126        proto_plugin = ctx.file.proto_plugin,
127        protoc = ctx.executable.protoc,
128    )
129
130# Default dependencies needed to compile protobuf stubs.
131PROTO_COMPILE_DEPS = [
132    Label("//proto/protobuf/3rdparty/crates:protobuf"),
133]
134
135# Default dependencies needed to compile gRPC stubs.
136GRPC_COMPILE_DEPS = PROTO_COMPILE_DEPS + [
137    Label("//proto/protobuf/3rdparty/crates:grpc"),
138    Label("//proto/protobuf/3rdparty/crates:tls-api"),
139    Label("//proto/protobuf/3rdparty/crates:tls-api-stub"),
140]
141
142rust_proto_toolchain = rule(
143    implementation = _rust_proto_toolchain_impl,
144    attrs = {
145        "edition": attr.string(
146            doc = "The edition used by the generated rust source.",
147        ),
148        "grpc_compile_deps": attr.label_list(
149            doc = "The crates the generated grpc libraries depends on.",
150            cfg = "target",
151            default = GRPC_COMPILE_DEPS,
152        ),
153        "grpc_plugin": attr.label(
154            doc = "The location of the Rust protobuf compiler plugin to generate rust gRPC stubs.",
155            allow_single_file = True,
156            cfg = "exec",
157            default = Label("//proto/protobuf/3rdparty/crates:grpc-compiler__protoc-gen-rust-grpc"),
158        ),
159        "proto_compile_deps": attr.label_list(
160            doc = "The crates the generated protobuf libraries depends on.",
161            cfg = "target",
162            default = PROTO_COMPILE_DEPS,
163        ),
164        "proto_plugin": attr.label(
165            doc = "The location of the Rust protobuf compiler plugin used to generate rust sources.",
166            allow_single_file = True,
167            cfg = "exec",
168            default = Label("//proto/protobuf/3rdparty/crates:protobuf-codegen__protoc-gen-rust"),
169        ),
170        "protoc": attr.label(
171            doc = "The location of the `protoc` binary. It should be an executable target.",
172            executable = True,
173            cfg = "exec",
174            default = Label("@com_google_protobuf//:protoc"),
175        ),
176    },
177    doc = """\
178Declares a Rust Proto toolchain for use.
179
180This is used to configure proto compilation and can be used to set different \
181protobuf compiler plugin.
182
183Example:
184
185Suppose a new nicer gRPC plugin has came out. The new plugin can be \
186used in Bazel by defining a new toolchain definition and declaration:
187
188```python
189load('@rules_rust//proto/protobuf:toolchain.bzl', 'rust_proto_toolchain')
190
191rust_proto_toolchain(
192   name="rust_proto_impl",
193   grpc_plugin="@rust_grpc//:grpc_plugin",
194   grpc_compile_deps=["@rust_grpc//:grpc_deps"],
195)
196
197toolchain(
198    name="rust_proto",
199    exec_compatible_with = [
200        "@platforms//cpu:cpuX",
201    ],
202    target_compatible_with = [
203        "@platforms//cpu:cpuX",
204    ],
205    toolchain = ":rust_proto_impl",
206)
207```
208
209Then, either add the label of the toolchain rule to register_toolchains in the WORKSPACE, or pass \
210it to the `--extra_toolchains` flag for Bazel, and it will be used.
211
212See @rules_rust//proto:BUILD for examples of defining the toolchain.
213""",
214)
215