1"""This file implements rust_proto_library aspect.""" 2 3load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") 4 5# buildifier: disable=bzl-visibility 6load("@rules_rust//rust/private:providers.bzl", "CrateInfo", "DepInfo", "DepVariantInfo") 7 8# buildifier: disable=bzl-visibility 9load("@rules_rust//rust/private:rustc.bzl", "rustc_compile_action") 10load("//bazel:upb_minitable_proto_library.bzl", "UpbMinitableCcInfo", "upb_minitable_proto_library_aspect") 11load("//bazel/common:proto_common.bzl", "proto_common") 12load("//bazel/common:proto_info.bzl", "ProtoInfo") 13 14visibility(["//rust/..."]) 15 16CrateMappingInfo = provider( 17 doc = "Struct mapping crate name to the .proto import paths", 18 fields = { 19 "crate_name": "Crate name of the proto_library target", 20 "import_paths": "Import path used in .proto files of dependants to import the .proto " + 21 "file of the current proto_library.", 22 }, 23) 24RustProtoInfo = provider( 25 doc = "Rust protobuf provider info", 26 fields = { 27 "dep_variant_info": "DepVariantInfo for the compiled Rust gencode (also covers its " + 28 "transitive dependencies)", 29 "crate_mapping": "depset(CrateMappingInfo) containing mappings of all transitive " + 30 "dependencies of the current proto_library.", 31 }, 32) 33 34def label_to_crate_name(ctx, label, toolchain): 35 return label.name.replace("-", "_") 36 37def proto_rust_toolchain_label(is_upb): 38 if is_upb: 39 return "//rust:proto_rust_upb_toolchain" 40 else: 41 return "//rust:proto_rust_cpp_toolchain" 42 43def _register_crate_mapping_write_action(name, actions, crate_mappings): 44 """Registers an action that generates a crate mapping for a proto_library. 45 46 Args: 47 name: The name of the target being analyzed. 48 actions: The context's actions object. 49 crate_mappings: depset(CrateMappingInfo) to be rendered. 50 This sequence should already have duplicates removed. 51 52 Returns: 53 The generated `File` with the crate mapping. 54 """ 55 mapping_file = actions.declare_file( 56 "{}.rust_crate_mapping".format(name), 57 ) 58 content = actions.args() 59 content.set_param_file_format("multiline") 60 content.add_all(crate_mappings, map_each = _render_text_crate_mapping) 61 actions.write(content = content, output = mapping_file) 62 63 return mapping_file 64 65def _render_text_crate_mapping(mapping): 66 """Renders the mapping to an easily parseable file for a crate mapping. 67 68 Args: 69 mapping (CrateMappingInfo): A single crate mapping. 70 71 Returns: 72 A string containing the crate mapping for the target in simple format: 73 <crate_name>\n 74 <number of lines to follow>\n 75 <one import path per line>\n 76 """ 77 crate_name = mapping.crate_name 78 import_paths = mapping.import_paths 79 return "\n".join(([crate_name, str(len(import_paths))] + list(import_paths))) 80 81def _generate_rust_gencode( 82 ctx, 83 proto_info, 84 proto_lang_toolchain, 85 crate_mapping, 86 is_upb): 87 """Generates Rust gencode 88 89 This function uses proto_common APIs and a ProtoLangToolchain to register an action 90 that invokes protoc with the right flags. 91 Args: 92 ctx (RuleContext): current rule context 93 proto_info (ProtoInfo): ProtoInfo of the proto_library target for which we are generating 94 gencode 95 proto_lang_toolchain (ProtoLangToolchainInfo): proto lang toolchain for Rust 96 crate_mapping (File): File containing the mapping from .proto file import path to its 97 corresponding containing Rust crate name. 98 is_upb (Bool): True when generating gencode for UPB, False otherwise. 99 Returns: 100 rs_outputs (([File], [File]): tuple(generated Rust files, generated C++ thunks). 101 """ 102 actions = ctx.actions 103 rs_outputs = proto_common.declare_generated_files( 104 actions = actions, 105 proto_info = proto_info, 106 extension = ".{}.pb.rs".format("u" if is_upb else "c"), 107 ) 108 if is_upb: 109 cc_outputs = [] 110 else: 111 cc_outputs = proto_common.declare_generated_files( 112 actions = actions, 113 proto_info = proto_info, 114 extension = ".pb.thunks.cc", 115 ) 116 additional_args = ctx.actions.args() 117 118 additional_args.add( 119 "--rust_opt=experimental-codegen=enabled,kernel={},bazel_crate_mapping={}".format( 120 "upb" if is_upb else "cpp", 121 crate_mapping.path, 122 ), 123 ) 124 125 proto_common.compile( 126 actions = ctx.actions, 127 proto_info = proto_info, 128 additional_inputs = depset(direct = [crate_mapping]), 129 additional_args = additional_args, 130 generated_files = rs_outputs + cc_outputs, 131 proto_lang_toolchain_info = proto_lang_toolchain, 132 plugin_output = ctx.bin_dir.path, 133 ) 134 return (rs_outputs, cc_outputs) 135 136def _get_crate_info(providers): 137 for provider in providers: 138 if hasattr(provider, "name"): 139 return provider 140 fail("Couldn't find a CrateInfo in the list of providers") 141 142def _get_dep_info(providers): 143 for provider in providers: 144 if hasattr(provider, "direct_crates"): 145 return provider 146 fail("Couldn't find a DepInfo in the list of providers") 147 148def _get_cc_info(providers): 149 for provider in providers: 150 if hasattr(provider, "linking_context"): 151 return provider 152 fail("Couldn't find a CcInfo in the list of providers") 153 154def _compile_cc( 155 ctx, 156 attr, 157 cc_toolchain, 158 feature_configuration, 159 src, 160 cc_infos): 161 """Compiles a C++ source file. 162 163 Args: 164 ctx: The rule context. 165 attr: The current rule's attributes. 166 cc_toolchain: A cc_toolchain. 167 feature_configuration: A feature configuration. 168 src: The source file to be compiled. 169 cc_infos: List[CcInfo]: A list of CcInfo dependencies. 170 171 Returns: 172 A CcInfo provider. 173 """ 174 cc_info = cc_common.merge_cc_infos(direct_cc_infos = cc_infos) 175 176 (compilation_context, compilation_outputs) = cc_common.compile( 177 name = src.basename, 178 actions = ctx.actions, 179 feature_configuration = feature_configuration, 180 cc_toolchain = cc_toolchain, 181 srcs = [src], 182 user_compile_flags = attr.copts if hasattr(attr, "copts") else [], 183 compilation_contexts = [cc_info.compilation_context], 184 ) 185 186 (linking_context, _) = cc_common.create_linking_context_from_compilation_outputs( 187 name = src.basename, 188 actions = ctx.actions, 189 feature_configuration = feature_configuration, 190 cc_toolchain = cc_toolchain, 191 compilation_outputs = compilation_outputs, 192 linking_contexts = [cc_info.linking_context], 193 ) 194 195 return CcInfo( 196 compilation_context = compilation_context, 197 linking_context = linking_context, 198 ) 199 200def _compile_rust(ctx, attr, src, extra_srcs, deps): 201 """Compiles a Rust source file. 202 203 Eventually this function could be upstreamed into rules_rust and be made present in rust_common. 204 205 Args: 206 ctx (RuleContext): The rule context. 207 attr (Attrs): The current rule's attributes (`ctx.attr` for rules, `ctx.rule.attr` for aspects) 208 src (File): The crate root source file to be compiled. 209 extra_srcs ([File]): Additional source files to include in the crate. 210 deps (List[DepVariantInfo]): A list of dependencies needed. 211 212 Returns: 213 A DepVariantInfo provider. 214 """ 215 toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"] 216 output_hash = repr(hash(src.path)) 217 218 crate_name = label_to_crate_name(ctx, ctx.label, toolchain) 219 220 lib_name = "{prefix}{name}-{lib_hash}{extension}".format( 221 prefix = "lib", 222 name = crate_name, 223 lib_hash = output_hash, 224 extension = ".rlib", 225 ) 226 227 rmeta_name = "{prefix}{name}-{lib_hash}{extension}".format( 228 prefix = "lib", 229 name = crate_name, 230 lib_hash = output_hash, 231 extension = ".rmeta", 232 ) 233 234 lib = ctx.actions.declare_file(lib_name) 235 rmeta = ctx.actions.declare_file(rmeta_name) 236 237 # TODO: Use higher level rules_rust API once available. 238 providers = rustc_compile_action( 239 ctx = ctx, 240 attr = attr, 241 toolchain = toolchain, 242 crate_info_dict = dict( 243 name = crate_name, 244 type = "rlib", 245 root = src, 246 srcs = depset([src] + extra_srcs), 247 deps = depset(deps), 248 proc_macro_deps = depset([]), 249 aliases = {}, 250 output = lib, 251 metadata = rmeta, 252 edition = "2021", 253 is_test = False, 254 rustc_env = {}, 255 compile_data = depset([]), 256 compile_data_targets = depset([]), 257 owner = ctx.label, 258 ), 259 # Needed to make transitive public imports not violate layering. 260 force_all_deps_direct = True, 261 output_hash = output_hash, 262 ) 263 264 return DepVariantInfo( 265 crate_info = _get_crate_info(providers), 266 dep_info = _get_dep_info(providers), 267 cc_info = _get_cc_info(providers), 268 build_info = None, 269 ) 270 271def _rust_upb_proto_aspect_impl(target, ctx): 272 """Implements the Rust protobuf aspect logic for UPB kernel.""" 273 return _rust_proto_aspect_common(target, ctx, is_upb = True) 274 275def _rust_cc_proto_aspect_impl(target, ctx): 276 """Implements the Rust protobuf aspect logic for C++ kernel.""" 277 return _rust_proto_aspect_common(target, ctx, is_upb = False) 278 279def get_import_path(f): 280 if hasattr(proto_common, "get_import_path"): 281 return proto_common.get_import_path(f) 282 else: 283 return f.path 284 285def _rust_proto_aspect_common(target, ctx, is_upb): 286 if RustProtoInfo in target: 287 return [] 288 289 proto_lang_toolchain = ctx.attr._proto_lang_toolchain[proto_common.ProtoLangToolchainInfo] 290 cc_toolchain = find_cpp_toolchain(ctx) 291 toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"] 292 293 feature_configuration = cc_common.configure_features( 294 ctx = ctx, 295 cc_toolchain = cc_toolchain, 296 requested_features = ctx.features, 297 unsupported_features = ctx.disabled_features, 298 ) 299 300 proto_srcs = getattr(ctx.rule.files, "srcs", []) 301 proto_deps = getattr(ctx.rule.attr, "deps", []) 302 transitive_crate_mappings = [] 303 for dep in proto_deps: 304 rust_proto_info = dep[RustProtoInfo] 305 transitive_crate_mappings.append(rust_proto_info.crate_mapping) 306 307 mapping_for_current_target = depset(transitive = transitive_crate_mappings) 308 crate_mapping_file = _register_crate_mapping_write_action( 309 target.label.name, 310 ctx.actions, 311 mapping_for_current_target, 312 ) 313 314 (gencode, thunks) = _generate_rust_gencode( 315 ctx, 316 target[ProtoInfo], 317 proto_lang_toolchain, 318 crate_mapping_file, 319 is_upb, 320 ) 321 322 if is_upb: 323 thunks_cc_info = target[UpbMinitableCcInfo].cc_info 324 else: 325 dep_cc_infos = [] 326 for dep in proto_deps: 327 dep_cc_infos.append(dep[CcInfo]) 328 329 thunks_cc_info = cc_common.merge_cc_infos(cc_infos = [_compile_cc( 330 feature_configuration = feature_configuration, 331 src = thunk, 332 ctx = ctx, 333 attr = attr, 334 cc_toolchain = cc_toolchain, 335 cc_infos = [target[CcInfo]] + [dep[CcInfo] for dep in ctx.attr._cpp_thunks_deps] + dep_cc_infos, 336 ) for thunk in thunks]) 337 338 runtime = proto_lang_toolchain.runtime 339 dep_variant_info_for_runtime = DepVariantInfo( 340 crate_info = runtime[CrateInfo] if CrateInfo in runtime else None, 341 dep_info = runtime[DepInfo] if DepInfo in runtime else None, 342 cc_info = runtime[CcInfo] if CcInfo in runtime else None, 343 build_info = None, 344 ) 345 dep_variant_info_for_native_gencode = DepVariantInfo(cc_info = thunks_cc_info) 346 347 dep_variant_info = _compile_rust( 348 ctx = ctx, 349 attr = ctx.rule.attr, 350 src = gencode[0], 351 extra_srcs = gencode[1:], 352 deps = [dep_variant_info_for_runtime, dep_variant_info_for_native_gencode] + ( 353 [d[RustProtoInfo].dep_variant_info for d in proto_deps] 354 ), 355 ) 356 return [RustProtoInfo( 357 dep_variant_info = dep_variant_info, 358 crate_mapping = depset( 359 direct = [CrateMappingInfo( 360 crate_name = label_to_crate_name(ctx, target.label, toolchain), 361 import_paths = tuple([get_import_path(f) for f in proto_srcs]), 362 )], 363 transitive = transitive_crate_mappings, 364 ), 365 )] 366 367def _make_proto_library_aspect(is_upb): 368 return aspect( 369 implementation = (_rust_upb_proto_aspect_impl if is_upb else _rust_cc_proto_aspect_impl), 370 attr_aspects = ["deps"], 371 requires = ([upb_minitable_proto_library_aspect] if is_upb else [cc_proto_aspect]), 372 attrs = { 373 "_cc_toolchain": attr.label( 374 doc = ( 375 "In order to use find_cc_toolchain, your rule has to depend " + 376 "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " + 377 "docs for details." 378 ), 379 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), 380 ), 381 "_collect_cc_coverage": attr.label( 382 default = Label("@rules_rust//util:collect_coverage"), 383 executable = True, 384 cfg = "exec", 385 ), 386 "_cpp_thunks_deps": attr.label_list( 387 default = [ 388 Label("//rust/cpp_kernel:cpp_api"), 389 Label("//src/google/protobuf"), 390 Label("//src/google/protobuf:protobuf_lite"), 391 ], 392 ), 393 "_error_format": attr.label( 394 default = Label("@rules_rust//:error_format"), 395 ), 396 "_extra_exec_rustc_flag": attr.label( 397 default = Label("@rules_rust//:extra_exec_rustc_flag"), 398 ), 399 "_extra_exec_rustc_flags": attr.label( 400 default = Label("@rules_rust//:extra_exec_rustc_flags"), 401 ), 402 "_extra_rustc_flag": attr.label( 403 default = Label("@rules_rust//:extra_rustc_flag"), 404 ), 405 "_extra_rustc_flags": attr.label( 406 default = Label("@rules_rust//:extra_rustc_flags"), 407 ), 408 "_process_wrapper": attr.label( 409 doc = "A process wrapper for running rustc on all platforms.", 410 default = Label("@rules_rust//util/process_wrapper"), 411 executable = True, 412 allow_single_file = True, 413 cfg = "exec", 414 ), 415 "_proto_lang_toolchain": attr.label( 416 default = Label(proto_rust_toolchain_label(is_upb)), 417 ), 418 }, 419 fragments = ["cpp"], 420 toolchains = [ 421 "@rules_rust//rust:toolchain_type", 422 "@bazel_tools//tools/cpp:toolchain_type", 423 ], 424 ) 425 426rust_upb_proto_library_aspect = _make_proto_library_aspect(is_upb = True) 427rust_cc_proto_library_aspect = _make_proto_library_aspect(is_upb = False) 428