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