# Copyright 2018 The Bazel Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Toolchain for compiling rust stubs from protobuf and gRPC.""" load("@rules_proto//proto:proto_common.bzl", proto_toolchains = "toolchains") # buildifier: disable=bzl-visibility load("//rust/private:utils.bzl", "name_to_crate_name") def generated_file_stem(file_path): """Returns the basename of a file without any extensions. Example: ```python content.append("pub mod %s;" % _generated_file_stem(f)) ``` Args: file_path (string): A path to a file Returns: string: The file stem of the filename """ basename = file_path.rsplit("/", 2)[-1] basename = name_to_crate_name(basename) return basename.rsplit(".", 2)[0] def rust_generate_proto( ctx, transitive_descriptor_sets, protos, imports, output_dir, proto_toolchain, is_grpc = False): """Generate a proto compilation action. Args: ctx (ctx): rule context. transitive_descriptor_sets (depset): descriptor generated by previous protobuf libraries. protos (list): list of paths of protos to compile. imports (depset): directory, relative to the package, to output the list of stubs. output_dir (str): The basename of the output directory for for the output generated stubs proto_toolchain (ToolchainInfo): The toolchain for rust-proto compilation. See `rust_proto_toolchain` is_grpc (bool, optional): generate gRPC stubs. Defaults to False. Returns: list: the list of generate stubs (File) """ tools = [ proto_toolchain.protoc, proto_toolchain.proto_plugin, ] executable = proto_toolchain.protoc args = ctx.actions.args() if not protos: fail("Protobuf compilation requested without inputs!") paths = ["%s/%s" % (output_dir, generated_file_stem(i)) for i in protos.to_list()] outs = [ctx.actions.declare_file(path + ".rs") for path in paths] output_directory = outs[0].dirname # Throughout we use rules_rust as the name as the plugin, not rust, because rust is an unstable builtin language in protoc. # If we use rust as the plugin name, it triggers protoc to try to use its in-built support, which is experimental. # 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. if is_grpc: # Add grpc stubs to the list of outputs grpc_files = [ctx.actions.declare_file(path + "_grpc.rs") for path in paths] outs.extend(grpc_files) # gRPC stubs is generated only if a service is defined in the proto, # so we create an empty grpc module in the other case. tools.append(proto_toolchain.grpc_plugin) tools.append(ctx.executable._optional_output_wrapper) args.add_all(grpc_files) args.add_all([ "--", proto_toolchain.protoc, "--plugin=protoc-gen-grpc-rules_rust=" + proto_toolchain.grpc_plugin.path, "--grpc-rules_rust_out=" + output_directory, ]) executable = ctx.executable._optional_output_wrapper args.add_all([ "--plugin=protoc-gen-rules_rust=" + proto_toolchain.proto_plugin.path, "--rules_rust_out=" + output_directory, ]) args.add_joined( transitive_descriptor_sets, join_with = ":", format_joined = "--descriptor_set_in=%s", ) args.add_all(protos) ctx.actions.run( inputs = depset( transitive = [ transitive_descriptor_sets, imports, ], ), outputs = outs, tools = tools, progress_message = "Generating Rust protobuf stubs", mnemonic = "RustProtocGen", executable = executable, arguments = [args], ) return outs def _rust_proto_toolchain_impl(ctx): if ctx.attr.protoc: # buildifier: disable=print 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") proto_toolchain = proto_toolchains.find_toolchain( ctx, legacy_attr = "_legacy_proto_toolchain", toolchain_type = "@rules_proto//proto:toolchain_type", ) return platform_common.ToolchainInfo( edition = ctx.attr.edition, grpc_compile_deps = ctx.attr.grpc_compile_deps, grpc_plugin = ctx.attr.protoc or ctx.file.grpc_plugin, proto_compile_deps = ctx.attr.proto_compile_deps, proto_plugin = ctx.file.proto_plugin, protoc = ctx.executable.protoc or proto_toolchain.proto_compiler, ) # Default dependencies needed to compile protobuf stubs. PROTO_COMPILE_DEPS = [ Label("//proto/protobuf/3rdparty/crates:protobuf"), ] # Default dependencies needed to compile gRPC stubs. GRPC_COMPILE_DEPS = PROTO_COMPILE_DEPS + [ Label("//proto/protobuf/3rdparty/crates:grpc"), Label("//proto/protobuf/3rdparty/crates:tls-api"), Label("//proto/protobuf/3rdparty/crates:tls-api-stub"), ] rust_proto_toolchain = rule( implementation = _rust_proto_toolchain_impl, attrs = dict({ "edition": attr.string( doc = "The edition used by the generated rust source.", ), "grpc_compile_deps": attr.label_list( doc = "The crates the generated grpc libraries depends on.", cfg = "target", default = GRPC_COMPILE_DEPS, ), "grpc_plugin": attr.label( doc = "The location of the Rust protobuf compiler plugin to generate rust gRPC stubs.", allow_single_file = True, cfg = "exec", default = Label("//proto/protobuf/3rdparty/crates:grpc-compiler__protoc-gen-rust-grpc"), ), "proto_compile_deps": attr.label_list( doc = "The crates the generated protobuf libraries depends on.", cfg = "target", default = PROTO_COMPILE_DEPS, ), "proto_plugin": attr.label( doc = "The location of the Rust protobuf compiler plugin used to generate rust sources.", allow_single_file = True, cfg = "exec", default = Label("//proto/protobuf/3rdparty/crates:protobuf-codegen__protoc-gen-rust"), ), "protoc": attr.label( 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.", executable = True, cfg = "exec", ), }, **proto_toolchains.if_legacy_toolchain({ "_legacy_proto_toolchain": attr.label( default = "//proto/protobuf:legacy_proto_toolchain", ), })), doc = """\ Declares a Rust Proto toolchain for use. This is used to configure proto compilation and can be used to set different \ protobuf compiler plugin. Example: Suppose a new nicer gRPC plugin has came out. The new plugin can be \ used in Bazel by defining a new toolchain definition and declaration: ```python load('@rules_rust//proto/protobuf:toolchain.bzl', 'rust_proto_toolchain') rust_proto_toolchain( name="rust_proto_impl", grpc_plugin="@rust_grpc//:grpc_plugin", grpc_compile_deps=["@rust_grpc//:grpc_deps"], ) toolchain( name="rust_proto", exec_compatible_with = [ "@platforms//cpu:cpuX", ], target_compatible_with = [ "@platforms//cpu:cpuX", ], toolchain = ":rust_proto_impl", ) ``` Then, either add the label of the toolchain rule to register_toolchains in the WORKSPACE, or pass \ it to the `--extra_toolchains` flag for Bazel, and it will be used. See @rules_rust//proto:BUILD for examples of defining the toolchain. """, )