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 providers = 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 providers.append(OutputGroupInfo(rust_generated_srcs = srcs)) 237 return providers 238 239def _rust_protogrpc_library_impl(ctx, is_grpc): 240 """Implementation of the rust_(proto|grpc)_library. 241 242 Args: 243 ctx (ctx): The current rule's context object 244 is_grpc (bool): True if the current rule is a `gRPC` rule. 245 246 Returns: 247 list: A list of providers, see `_rust_proto_compile` 248 """ 249 proto = _expand_provider(ctx.attr.deps, ProtoInfo) 250 transitive_sources = [ 251 f[RustProtoInfo].transitive_proto_sources 252 for f in ctx.attr.deps 253 if RustProtoInfo in f 254 ] 255 256 toolchain = find_toolchain(ctx) 257 crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name) 258 259 return _rust_proto_compile( 260 protos = depset(transitive = transitive_sources), 261 descriptor_sets = depset(transitive = [p.transitive_descriptor_sets for p in proto]), 262 imports = depset(transitive = [p.transitive_imports for p in proto]), 263 crate_name = crate_name, 264 ctx = ctx, 265 is_grpc = is_grpc, 266 compile_deps = ctx.attr.rust_deps, 267 toolchain = toolchain, 268 ) 269 270def _rust_proto_library_impl(ctx): 271 """The implementation of the `rust_proto_library` rule 272 273 Args: 274 ctx (ctx): The rule's context object. 275 276 Returns: 277 list: A list of providers, see `_rust_protogrpc_library_impl` 278 """ 279 return _rust_protogrpc_library_impl(ctx, False) 280 281rust_proto_library = rule( 282 implementation = _rust_proto_library_impl, 283 attrs = { 284 "crate_name": attr.string( 285 doc = """\ 286 Crate name to use for this target. 287 288 This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores. 289 Defaults to the target name, with any hyphens replaced by underscores. 290 """, 291 ), 292 "deps": attr.label_list( 293 doc = ( 294 "List of proto_library dependencies that will be built. " + 295 "One crate for each proto_library will be created with the corresponding stubs." 296 ), 297 mandatory = True, 298 providers = [ProtoInfo], 299 aspects = [_rust_proto_aspect], 300 ), 301 "rust_deps": attr.label_list( 302 doc = "The crates the generated library depends on.", 303 ), 304 "rustc_flags": attr.string_list( 305 doc = """\ 306 List of compiler flags passed to `rustc`. 307 308 These strings are subject to Make variable expansion for predefined 309 source/output path variables like `$location`, `$execpath`, and 310 `$rootpath`. This expansion is useful if you wish to pass a generated 311 file of arguments to rustc: `@$(location //package:target)`. 312 """, 313 ), 314 "_cc_toolchain": attr.label( 315 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), 316 ), 317 "_optional_output_wrapper": attr.label( 318 executable = True, 319 cfg = "exec", 320 default = Label("//proto/protobuf:optional_output_wrapper"), 321 ), 322 "_process_wrapper": attr.label( 323 default = Label("//util/process_wrapper"), 324 executable = True, 325 allow_single_file = True, 326 cfg = "exec", 327 ), 328 }, 329 fragments = ["cpp"], 330 toolchains = [ 331 str(Label("//proto/protobuf:toolchain_type")), 332 str(Label("//rust:toolchain_type")), 333 "@bazel_tools//tools/cpp:toolchain_type", 334 ], 335 doc = """\ 336Builds a Rust library crate from a set of `proto_library`s. 337 338Example: 339 340```python 341load("@rules_rust//proto/protobuf:defs.bzl", "rust_proto_library") 342 343proto_library( 344 name = "my_proto", 345 srcs = ["my.proto"] 346) 347 348rust_proto_library( 349 name = "rust", 350 deps = [":my_proto"], 351) 352 353rust_binary( 354 name = "my_proto_binary", 355 srcs = ["my_proto_binary.rs"], 356 deps = [":rust"], 357) 358``` 359""", 360) 361 362def _rust_grpc_library_impl(ctx): 363 """The implementation of the `rust_grpc_library` rule 364 365 Args: 366 ctx (ctx): The rule's context object 367 368 Returns: 369 list: A list of providers. See `_rust_protogrpc_library_impl` 370 """ 371 return _rust_protogrpc_library_impl(ctx, True) 372 373rust_grpc_library = rule( 374 implementation = _rust_grpc_library_impl, 375 attrs = { 376 "crate_name": attr.string( 377 doc = """\ 378 Crate name to use for this target. 379 380 This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores. 381 Defaults to the target name, with any hyphens replaced by underscores. 382 """, 383 ), 384 "deps": attr.label_list( 385 doc = ( 386 "List of proto_library dependencies that will be built. " + 387 "One crate for each proto_library will be created with the corresponding gRPC stubs." 388 ), 389 mandatory = True, 390 providers = [ProtoInfo], 391 aspects = [_rust_proto_aspect], 392 ), 393 "rust_deps": attr.label_list( 394 doc = "The crates the generated library depends on.", 395 ), 396 "rustc_flags": attr.string_list( 397 doc = """\ 398 List of compiler flags passed to `rustc`. 399 400 These strings are subject to Make variable expansion for predefined 401 source/output path variables like `$location`, `$execpath`, and 402 `$rootpath`. This expansion is useful if you wish to pass a generated 403 file of arguments to rustc: `@$(location //package:target)`. 404 """, 405 ), 406 "_cc_toolchain": attr.label( 407 default = "@bazel_tools//tools/cpp:current_cc_toolchain", 408 ), 409 "_optional_output_wrapper": attr.label( 410 executable = True, 411 cfg = "exec", 412 default = Label("//proto/protobuf:optional_output_wrapper"), 413 ), 414 "_process_wrapper": attr.label( 415 default = Label("//util/process_wrapper"), 416 executable = True, 417 allow_single_file = True, 418 cfg = "exec", 419 ), 420 }, 421 fragments = ["cpp"], 422 toolchains = [ 423 str(Label("//proto/protobuf:toolchain_type")), 424 str(Label("//rust:toolchain_type")), 425 "@bazel_tools//tools/cpp:toolchain_type", 426 ], 427 doc = """\ 428Builds a Rust library crate from a set of `proto_library`s suitable for gRPC. 429 430Example: 431 432```python 433load("@rules_rust//proto/protobuf:defs.bzl", "rust_grpc_library") 434 435proto_library( 436 name = "my_proto", 437 srcs = ["my.proto"] 438) 439 440rust_grpc_library( 441 name = "rust", 442 deps = [":my_proto"], 443) 444 445rust_binary( 446 name = "my_service", 447 srcs = ["my_service.rs"], 448 deps = [":rust"], 449) 450``` 451""", 452) 453