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"""Rust Protobuf Rules""" 16 17load("@rules_proto//proto:defs.bzl", "ProtoInfo") 18load( 19 "//proto/protobuf:toolchain.bzl", 20 _generate_proto = "rust_generate_proto", 21 _generated_file_stem = "generated_file_stem", 22) 23 24# buildifier: disable=bzl-visibility 25load("//rust/private:rustc.bzl", "rustc_compile_action") 26 27# buildifier: disable=bzl-visibility 28load("//rust/private:utils.bzl", "can_build_metadata", "compute_crate_name", "determine_output_hash", "find_toolchain", "transform_deps") 29 30RustProtoInfo = provider( 31 doc = "Rust protobuf provider info", 32 fields = { 33 "proto_sources": "List[string]: list of source paths of protos", 34 "transitive_proto_sources": "depset[string]", 35 }, 36) 37 38def _compute_proto_source_path(file, source_root_attr): 39 """Take the short path of file and make it suitable for protoc. 40 41 Args: 42 file (File): The target source file. 43 source_root_attr (str): The directory relative to which the `.proto` \ 44 files defined in the proto_library are defined. 45 46 Returns: 47 str: The protoc suitible path of `file` 48 """ 49 50 # Bazel creates symlinks to the .proto files under a directory called 51 # "_virtual_imports/<rule name>" if we do any sort of munging of import 52 # paths (e.g. using strip_import_prefix / import_prefix attributes) 53 virtual_imports = "/_virtual_imports/" 54 if virtual_imports in file.path: 55 return file.path.split(virtual_imports)[1].split("/", 1)[1] 56 57 # For proto, they need to be requested with their absolute name to be 58 # compatible with the descriptor_set passed by proto_library. 59 # I.e. if you compile a protobuf at @repo1//package:file.proto, the proto 60 # compiler would generate a file descriptor with the path 61 # `package/file.proto`. Since we compile from the proto descriptor, we need 62 # to pass the list of descriptors and the list of path to compile. 63 # For the precedent example, the file (noted `f`) would have 64 # `f.short_path` returns `external/repo1/package/file.proto`. 65 # In addition, proto_library can provide a proto_source_path to change the base 66 # path, which should a be a prefix. 67 path = file.short_path 68 69 # Strip external prefix. 70 path = path.split("/", 2)[2] if path.startswith("../") else path 71 72 # Strip source_root. 73 if path.startswith(source_root_attr): 74 return path[len(source_root_attr):] 75 else: 76 return path 77 78def _rust_proto_aspect_impl(target, ctx): 79 """The implementation of the `rust_proto_aspect` aspect 80 81 Args: 82 target (Target): The target to which the aspect is applied 83 ctx (ctx): The rule context which the targetis created from 84 85 Returns: 86 list: A list containg a `RustProtoInfo` provider 87 """ 88 if ProtoInfo not in target: 89 return None 90 91 if hasattr(ctx.rule.attr, "proto_source_root"): 92 source_root = ctx.rule.attr.proto_source_root 93 else: 94 source_root = "" 95 96 if source_root and source_root[-1] != "/": 97 source_root += "/" 98 99 sources = [ 100 _compute_proto_source_path(f, source_root) 101 for f in target[ProtoInfo].direct_sources 102 ] 103 transitive_sources = [ 104 f[RustProtoInfo].transitive_proto_sources 105 for f in ctx.rule.attr.deps 106 if RustProtoInfo in f 107 ] 108 return [RustProtoInfo( 109 proto_sources = sources, 110 transitive_proto_sources = depset(transitive = transitive_sources, direct = sources), 111 )] 112 113_rust_proto_aspect = aspect( 114 doc = "An aspect that gathers rust proto direct and transitive sources", 115 implementation = _rust_proto_aspect_impl, 116 attr_aspects = ["deps"], 117) 118 119def _gen_lib(ctx, grpc, srcs, lib): 120 """Generate a lib.rs file for the crates. 121 122 Args: 123 ctx (ctx): The current rule's context object 124 grpc (bool): True if the current rule is a `gRPC` rule. 125 srcs (list): A list of protoc suitible file paths (str). 126 lib (File): The File object where the rust source file should be written 127 """ 128 content = ["extern crate protobuf;"] 129 if grpc: 130 content.append("extern crate grpc;") 131 content.append("extern crate tls_api;") 132 for f in srcs.to_list(): 133 content.append("pub mod %s;" % _generated_file_stem(f)) 134 content.append("pub use %s::*;" % _generated_file_stem(f)) 135 if grpc: 136 content.append("pub mod %s_grpc;" % _generated_file_stem(f)) 137 content.append("pub use %s_grpc::*;" % _generated_file_stem(f)) 138 ctx.actions.write(lib, "\n".join(content)) 139 140def _expand_provider(lst, provider): 141 """Gathers a list of a specific provider from a list of targets. 142 143 Args: 144 lst (list): A list of Targets 145 provider (Provider): The target provider type to extract `lst` 146 147 Returns: 148 list: A list of providers of the type from `provider`. 149 """ 150 return [el[provider] for el in lst if provider in el] 151 152def _rust_proto_compile(protos, descriptor_sets, imports, crate_name, ctx, is_grpc, compile_deps, toolchain): 153 """Create and run a rustc compile action based on the current rule's attributes 154 155 Args: 156 protos (depset): Paths of protos to compile. 157 descriptor_sets (depset): A set of transitive protobuf `FileDescriptorSet`s 158 imports (depset): A set of transitive protobuf Imports. 159 crate_name (str): The name of the Crate for the current target 160 ctx (ctx): The current rule's context object 161 is_grpc (bool): True if the current rule is a `gRPC` rule. 162 compile_deps (list): A list of Rust dependencies (`List[Target]`) 163 toolchain (rust_toolchain): the current `rust_toolchain`. 164 165 Returns: 166 list: A list of providers, see `rustc_compile_action` 167 """ 168 169 # Create all the source in a specific folder 170 proto_toolchain = ctx.toolchains[Label("//proto/protobuf:toolchain_type")] 171 output_dir = "%s.%s.rust" % (crate_name, "grpc" if is_grpc else "proto") 172 173 # Generate the proto stubs 174 srcs = _generate_proto( 175 ctx, 176 descriptor_sets, 177 protos = protos, 178 imports = imports, 179 output_dir = output_dir, 180 proto_toolchain = proto_toolchain, 181 is_grpc = is_grpc, 182 ) 183 184 # and lib.rs 185 lib_rs = ctx.actions.declare_file("%s/lib.rs" % output_dir) 186 _gen_lib(ctx, is_grpc, protos, lib_rs) 187 srcs.append(lib_rs) 188 189 # And simulate rust_library behavior 190 output_hash = determine_output_hash(lib_rs, ctx.label) 191 rust_lib = ctx.actions.declare_file("%s/lib%s-%s.rlib" % ( 192 output_dir, 193 crate_name, 194 output_hash, 195 )) 196 rust_metadata = None 197 if can_build_metadata(toolchain, ctx, "rlib"): 198 rust_metadata = ctx.actions.declare_file("%s/lib%s-%s.rmeta" % ( 199 output_dir, 200 crate_name, 201 output_hash, 202 )) 203 204 # Gather all dependencies for compilation 205 compile_action_deps = depset( 206 transform_deps( 207 compile_deps + 208 proto_toolchain.grpc_compile_deps if is_grpc else proto_toolchain.proto_compile_deps, 209 ), 210 ) 211 212 return rustc_compile_action( 213 ctx = ctx, 214 attr = ctx.attr, 215 toolchain = toolchain, 216 crate_info_dict = dict( 217 name = crate_name, 218 type = "rlib", 219 root = lib_rs, 220 srcs = depset(srcs), 221 deps = compile_action_deps, 222 proc_macro_deps = depset([]), 223 aliases = {}, 224 output = rust_lib, 225 metadata = rust_metadata, 226 edition = proto_toolchain.edition, 227 rustc_env = {}, 228 is_test = False, 229 compile_data = depset([target.files for target in getattr(ctx.attr, "compile_data", [])]), 230 compile_data_targets = depset(getattr(ctx.attr, "compile_data", [])), 231 wrapped_crate_type = None, 232 owner = ctx.label, 233 ), 234 output_hash = output_hash, 235 ) 236 237def _rust_protogrpc_library_impl(ctx, is_grpc): 238 """Implementation of the rust_(proto|grpc)_library. 239 240 Args: 241 ctx (ctx): The current rule's context object 242 is_grpc (bool): True if the current rule is a `gRPC` rule. 243 244 Returns: 245 list: A list of providers, see `_rust_proto_compile` 246 """ 247 proto = _expand_provider(ctx.attr.deps, ProtoInfo) 248 transitive_sources = [ 249 f[RustProtoInfo].transitive_proto_sources 250 for f in ctx.attr.deps 251 if RustProtoInfo in f 252 ] 253 254 toolchain = find_toolchain(ctx) 255 crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name) 256 257 return _rust_proto_compile( 258 protos = depset(transitive = transitive_sources), 259 descriptor_sets = depset(transitive = [p.transitive_descriptor_sets for p in proto]), 260 imports = depset(transitive = [p.transitive_imports for p in proto]), 261 crate_name = crate_name, 262 ctx = ctx, 263 is_grpc = is_grpc, 264 compile_deps = ctx.attr.rust_deps, 265 toolchain = toolchain, 266 ) 267 268def _rust_proto_library_impl(ctx): 269 """The implementation of the `rust_proto_library` rule 270 271 Args: 272 ctx (ctx): The rule's context object. 273 274 Returns: 275 list: A list of providers, see `_rust_protogrpc_library_impl` 276 """ 277 return _rust_protogrpc_library_impl(ctx, False) 278 279rust_proto_library = rule( 280 implementation = _rust_proto_library_impl, 281 attrs = { 282 "crate_name": attr.string( 283 doc = """\ 284 Crate name to use for this target. 285 286 This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores. 287 Defaults to the target name, with any hyphens replaced by underscores. 288 """, 289 ), 290 "deps": attr.label_list( 291 doc = ( 292 "List of proto_library dependencies that will be built. " + 293 "One crate for each proto_library will be created with the corresponding stubs." 294 ), 295 mandatory = True, 296 providers = [ProtoInfo], 297 aspects = [_rust_proto_aspect], 298 ), 299 "rust_deps": attr.label_list( 300 doc = "The crates the generated library depends on.", 301 ), 302 "rustc_flags": attr.string_list( 303 doc = """\ 304 List of compiler flags passed to `rustc`. 305 306 These strings are subject to Make variable expansion for predefined 307 source/output path variables like `$location`, `$execpath`, and 308 `$rootpath`. This expansion is useful if you wish to pass a generated 309 file of arguments to rustc: `@$(location //package:target)`. 310 """, 311 ), 312 "_cc_toolchain": attr.label( 313 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), 314 ), 315 "_optional_output_wrapper": attr.label( 316 executable = True, 317 cfg = "exec", 318 default = Label("//proto/protobuf:optional_output_wrapper"), 319 ), 320 "_process_wrapper": attr.label( 321 default = Label("//util/process_wrapper"), 322 executable = True, 323 allow_single_file = True, 324 cfg = "exec", 325 ), 326 }, 327 fragments = ["cpp"], 328 toolchains = [ 329 str(Label("//proto/protobuf:toolchain_type")), 330 str(Label("//rust:toolchain_type")), 331 "@bazel_tools//tools/cpp:toolchain_type", 332 ], 333 doc = """\ 334Builds a Rust library crate from a set of `proto_library`s. 335 336Example: 337 338```python 339load("@rules_rust//proto/protobuf:defs.bzl", "rust_proto_library") 340 341proto_library( 342 name = "my_proto", 343 srcs = ["my.proto"] 344) 345 346rust_proto_library( 347 name = "rust", 348 deps = [":my_proto"], 349) 350 351rust_binary( 352 name = "my_proto_binary", 353 srcs = ["my_proto_binary.rs"], 354 deps = [":rust"], 355) 356``` 357""", 358) 359 360def _rust_grpc_library_impl(ctx): 361 """The implementation of the `rust_grpc_library` rule 362 363 Args: 364 ctx (ctx): The rule's context object 365 366 Returns: 367 list: A list of providers. See `_rust_protogrpc_library_impl` 368 """ 369 return _rust_protogrpc_library_impl(ctx, True) 370 371rust_grpc_library = rule( 372 implementation = _rust_grpc_library_impl, 373 attrs = { 374 "crate_name": attr.string( 375 doc = """\ 376 Crate name to use for this target. 377 378 This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores. 379 Defaults to the target name, with any hyphens replaced by underscores. 380 """, 381 ), 382 "deps": attr.label_list( 383 doc = ( 384 "List of proto_library dependencies that will be built. " + 385 "One crate for each proto_library will be created with the corresponding gRPC stubs." 386 ), 387 mandatory = True, 388 providers = [ProtoInfo], 389 aspects = [_rust_proto_aspect], 390 ), 391 "rust_deps": attr.label_list( 392 doc = "The crates the generated library depends on.", 393 ), 394 "rustc_flags": attr.string_list( 395 doc = """\ 396 List of compiler flags passed to `rustc`. 397 398 These strings are subject to Make variable expansion for predefined 399 source/output path variables like `$location`, `$execpath`, and 400 `$rootpath`. This expansion is useful if you wish to pass a generated 401 file of arguments to rustc: `@$(location //package:target)`. 402 """, 403 ), 404 "_cc_toolchain": attr.label( 405 default = "@bazel_tools//tools/cpp:current_cc_toolchain", 406 ), 407 "_optional_output_wrapper": attr.label( 408 executable = True, 409 cfg = "exec", 410 default = Label("//proto/protobuf:optional_output_wrapper"), 411 ), 412 "_process_wrapper": attr.label( 413 default = Label("//util/process_wrapper"), 414 executable = True, 415 allow_single_file = True, 416 cfg = "exec", 417 ), 418 }, 419 fragments = ["cpp"], 420 toolchains = [ 421 str(Label("//proto/protobuf:toolchain_type")), 422 str(Label("//rust:toolchain_type")), 423 "@bazel_tools//tools/cpp:toolchain_type", 424 ], 425 doc = """\ 426Builds a Rust library crate from a set of `proto_library`s suitable for gRPC. 427 428Example: 429 430```python 431load("@rules_rust//proto/protobuf:defs.bzl", "rust_grpc_library") 432 433proto_library( 434 name = "my_proto", 435 srcs = ["my.proto"] 436) 437 438rust_grpc_library( 439 name = "rust", 440 deps = [":my_proto"], 441) 442 443rust_binary( 444 name = "my_service", 445 srcs = ["my_service.rs"], 446 deps = [":rust"], 447) 448``` 449""", 450) 451