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