1*d4726bddSHONG Yifan"""A module defining Rust 'unpretty' rules""" 2*d4726bddSHONG Yifan 3*d4726bddSHONG Yifanload("//rust/private:common.bzl", "rust_common") 4*d4726bddSHONG Yifanload( 5*d4726bddSHONG Yifan "//rust/private:rust.bzl", 6*d4726bddSHONG Yifan "RUSTC_ATTRS", 7*d4726bddSHONG Yifan "get_rust_test_flags", 8*d4726bddSHONG Yifan) 9*d4726bddSHONG Yifanload( 10*d4726bddSHONG Yifan "//rust/private:rustc.bzl", 11*d4726bddSHONG Yifan "collect_deps", 12*d4726bddSHONG Yifan "collect_inputs", 13*d4726bddSHONG Yifan "construct_arguments", 14*d4726bddSHONG Yifan) 15*d4726bddSHONG Yifanload( 16*d4726bddSHONG Yifan "//rust/private:utils.bzl", 17*d4726bddSHONG Yifan "determine_output_hash", 18*d4726bddSHONG Yifan "find_cc_toolchain", 19*d4726bddSHONG Yifan "find_toolchain", 20*d4726bddSHONG Yifan) 21*d4726bddSHONG Yifan 22*d4726bddSHONG Yifan# This list is determined by running the following command: 23*d4726bddSHONG Yifan# 24*d4726bddSHONG Yifan# rustc +nightly -Zunpretty= 25*d4726bddSHONG Yifan# 26*d4726bddSHONG Yifan_UNPRETTY_MODES = [ 27*d4726bddSHONG Yifan "ast-tree,expanded", 28*d4726bddSHONG Yifan "ast-tree", 29*d4726bddSHONG Yifan "expanded,hygiene", 30*d4726bddSHONG Yifan "expanded,identified", 31*d4726bddSHONG Yifan "expanded", 32*d4726bddSHONG Yifan "hir-tree", 33*d4726bddSHONG Yifan "hir,identified", 34*d4726bddSHONG Yifan "hir,typed", 35*d4726bddSHONG Yifan "hir", 36*d4726bddSHONG Yifan "identified", 37*d4726bddSHONG Yifan "mir-cfg", 38*d4726bddSHONG Yifan "mir", 39*d4726bddSHONG Yifan "normal", 40*d4726bddSHONG Yifan] 41*d4726bddSHONG Yifan 42*d4726bddSHONG YifanRustUnprettyInfo = provider( 43*d4726bddSHONG Yifan doc = "A provider describing the Rust unpretty mode.", 44*d4726bddSHONG Yifan fields = { 45*d4726bddSHONG Yifan "modes": "Depset[string]: Can be any of {}".format(["'{}'".format(m) for m in _UNPRETTY_MODES]), 46*d4726bddSHONG Yifan }, 47*d4726bddSHONG Yifan) 48*d4726bddSHONG Yifan 49*d4726bddSHONG Yifandef _rust_unpretty_flag_impl(ctx): 50*d4726bddSHONG Yifan value = ctx.build_setting_value 51*d4726bddSHONG Yifan invalid = [] 52*d4726bddSHONG Yifan for mode in value: 53*d4726bddSHONG Yifan if mode not in _UNPRETTY_MODES: 54*d4726bddSHONG Yifan invalid.append(mode) 55*d4726bddSHONG Yifan if invalid: 56*d4726bddSHONG Yifan fail("{} build setting allowed to take values [{}] but was set to unallowed values: {}".format( 57*d4726bddSHONG Yifan ctx.label, 58*d4726bddSHONG Yifan ", ".join(["'{}'".format(m) for m in _UNPRETTY_MODES]), 59*d4726bddSHONG Yifan invalid, 60*d4726bddSHONG Yifan )) 61*d4726bddSHONG Yifan 62*d4726bddSHONG Yifan return RustUnprettyInfo(modes = depset(value)) 63*d4726bddSHONG Yifan 64*d4726bddSHONG Yifanrust_unpretty_flag = rule( 65*d4726bddSHONG Yifan doc = "A build setting which represents the Rust unpretty mode. The allowed values are {}".format(_UNPRETTY_MODES), 66*d4726bddSHONG Yifan implementation = _rust_unpretty_flag_impl, 67*d4726bddSHONG Yifan build_setting = config.string_list( 68*d4726bddSHONG Yifan flag = True, 69*d4726bddSHONG Yifan repeatable = True, 70*d4726bddSHONG Yifan ), 71*d4726bddSHONG Yifan) 72*d4726bddSHONG Yifan 73*d4726bddSHONG Yifandef _nightly_unpretty_transition_impl(settings, attr): 74*d4726bddSHONG Yifan mode = settings[str(Label("//rust/settings:unpretty"))] 75*d4726bddSHONG Yifan 76*d4726bddSHONG Yifan # Use the presence of _unpretty_modes as a proxy for whether this is a rust_unpretty target. 77*d4726bddSHONG Yifan if hasattr(attr, "_unpretty_modes") and hasattr(attr, "mode"): 78*d4726bddSHONG Yifan mode = mode + [attr.mode] 79*d4726bddSHONG Yifan 80*d4726bddSHONG Yifan return { 81*d4726bddSHONG Yifan str(Label("//rust/settings:unpretty")): depset(mode).to_list(), 82*d4726bddSHONG Yifan str(Label("//rust/toolchain/channel")): "nightly", 83*d4726bddSHONG Yifan } 84*d4726bddSHONG Yifan 85*d4726bddSHONG Yifannightly_unpretty_transition = transition( 86*d4726bddSHONG Yifan implementation = _nightly_unpretty_transition_impl, 87*d4726bddSHONG Yifan inputs = [str(Label("//rust/settings:unpretty"))], 88*d4726bddSHONG Yifan outputs = [ 89*d4726bddSHONG Yifan str(Label("//rust/settings:unpretty")), 90*d4726bddSHONG Yifan str(Label("//rust/toolchain/channel")), 91*d4726bddSHONG Yifan ], 92*d4726bddSHONG Yifan) 93*d4726bddSHONG Yifan 94*d4726bddSHONG Yifandef _get_unpretty_ready_crate_info(target, aspect_ctx): 95*d4726bddSHONG Yifan """Check that a target is suitable for expansion and extract the `CrateInfo` provider from it. 96*d4726bddSHONG Yifan 97*d4726bddSHONG Yifan Args: 98*d4726bddSHONG Yifan target (Target): The target the aspect is running on. 99*d4726bddSHONG Yifan aspect_ctx (ctx, optional): The aspect's context object. 100*d4726bddSHONG Yifan 101*d4726bddSHONG Yifan Returns: 102*d4726bddSHONG Yifan CrateInfo, optional: A `CrateInfo` provider if rust unpretty should be run or `None`. 103*d4726bddSHONG Yifan """ 104*d4726bddSHONG Yifan 105*d4726bddSHONG Yifan # Ignore external targets 106*d4726bddSHONG Yifan if target.label.workspace_root.startswith("external"): 107*d4726bddSHONG Yifan return None 108*d4726bddSHONG Yifan 109*d4726bddSHONG Yifan # Targets with specific tags will not be formatted 110*d4726bddSHONG Yifan if aspect_ctx: 111*d4726bddSHONG Yifan ignore_tags = [ 112*d4726bddSHONG Yifan "nounpretty", 113*d4726bddSHONG Yifan "no-unpretty", 114*d4726bddSHONG Yifan "no_unpretty", 115*d4726bddSHONG Yifan ] 116*d4726bddSHONG Yifan 117*d4726bddSHONG Yifan for tag in ignore_tags: 118*d4726bddSHONG Yifan if tag in aspect_ctx.rule.attr.tags: 119*d4726bddSHONG Yifan return None 120*d4726bddSHONG Yifan 121*d4726bddSHONG Yifan # Obviously ignore any targets that don't contain `CrateInfo` 122*d4726bddSHONG Yifan if rust_common.crate_info not in target: 123*d4726bddSHONG Yifan return None 124*d4726bddSHONG Yifan 125*d4726bddSHONG Yifan return target[rust_common.crate_info] 126*d4726bddSHONG Yifan 127*d4726bddSHONG Yifandef _rust_unpretty_aspect_impl(target, ctx): 128*d4726bddSHONG Yifan crate_info = _get_unpretty_ready_crate_info(target, ctx) 129*d4726bddSHONG Yifan if not crate_info: 130*d4726bddSHONG Yifan return [] 131*d4726bddSHONG Yifan 132*d4726bddSHONG Yifan toolchain = find_toolchain(ctx) 133*d4726bddSHONG Yifan cc_toolchain, feature_configuration = find_cc_toolchain(ctx) 134*d4726bddSHONG Yifan 135*d4726bddSHONG Yifan dep_info, build_info, _ = collect_deps( 136*d4726bddSHONG Yifan deps = crate_info.deps, 137*d4726bddSHONG Yifan proc_macro_deps = crate_info.proc_macro_deps, 138*d4726bddSHONG Yifan aliases = crate_info.aliases, 139*d4726bddSHONG Yifan ) 140*d4726bddSHONG Yifan 141*d4726bddSHONG Yifan compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs( 142*d4726bddSHONG Yifan ctx, 143*d4726bddSHONG Yifan ctx.rule.file, 144*d4726bddSHONG Yifan ctx.rule.files, 145*d4726bddSHONG Yifan # Rust expand doesn't need to invoke transitive linking, therefore doesn't need linkstamps. 146*d4726bddSHONG Yifan depset([]), 147*d4726bddSHONG Yifan toolchain, 148*d4726bddSHONG Yifan cc_toolchain, 149*d4726bddSHONG Yifan feature_configuration, 150*d4726bddSHONG Yifan crate_info, 151*d4726bddSHONG Yifan dep_info, 152*d4726bddSHONG Yifan build_info, 153*d4726bddSHONG Yifan ) 154*d4726bddSHONG Yifan 155*d4726bddSHONG Yifan output_groups = {} 156*d4726bddSHONG Yifan outputs = [] 157*d4726bddSHONG Yifan 158*d4726bddSHONG Yifan for mode in ctx.attr._unpretty_modes[RustUnprettyInfo].modes.to_list(): 159*d4726bddSHONG Yifan pretty_mode = mode.replace("-", "_") 160*d4726bddSHONG Yifan mnemonic = "RustUnpretty{}".format("".join([ 161*d4726bddSHONG Yifan o.title() 162*d4726bddSHONG Yifan for m in pretty_mode.split(",") 163*d4726bddSHONG Yifan for o in m.split("_") 164*d4726bddSHONG Yifan ])) 165*d4726bddSHONG Yifan 166*d4726bddSHONG Yifan unpretty_out = ctx.actions.declare_file( 167*d4726bddSHONG Yifan "{}.unpretty.{}.rs".format(ctx.label.name, pretty_mode.replace(",", ".")), 168*d4726bddSHONG Yifan sibling = crate_info.output, 169*d4726bddSHONG Yifan ) 170*d4726bddSHONG Yifan 171*d4726bddSHONG Yifan output_groups.update({"rust_unpretty_{}".format(pretty_mode.replace(",", "_")): depset([unpretty_out])}) 172*d4726bddSHONG Yifan outputs.append(unpretty_out) 173*d4726bddSHONG Yifan 174*d4726bddSHONG Yifan rust_flags = [] 175*d4726bddSHONG Yifan if crate_info.is_test: 176*d4726bddSHONG Yifan rust_flags = get_rust_test_flags(ctx.rule.attr) 177*d4726bddSHONG Yifan 178*d4726bddSHONG Yifan args, env = construct_arguments( 179*d4726bddSHONG Yifan ctx = ctx, 180*d4726bddSHONG Yifan attr = ctx.rule.attr, 181*d4726bddSHONG Yifan file = ctx.file, 182*d4726bddSHONG Yifan toolchain = toolchain, 183*d4726bddSHONG Yifan tool_path = toolchain.rustc.path, 184*d4726bddSHONG Yifan cc_toolchain = cc_toolchain, 185*d4726bddSHONG Yifan feature_configuration = feature_configuration, 186*d4726bddSHONG Yifan crate_info = crate_info, 187*d4726bddSHONG Yifan dep_info = dep_info, 188*d4726bddSHONG Yifan linkstamp_outs = linkstamp_outs, 189*d4726bddSHONG Yifan ambiguous_libs = ambiguous_libs, 190*d4726bddSHONG Yifan output_hash = determine_output_hash(crate_info.root, ctx.label), 191*d4726bddSHONG Yifan rust_flags = rust_flags, 192*d4726bddSHONG Yifan out_dir = out_dir, 193*d4726bddSHONG Yifan build_env_files = build_env_files, 194*d4726bddSHONG Yifan build_flags_files = build_flags_files, 195*d4726bddSHONG Yifan emit = ["dep-info", "metadata"], 196*d4726bddSHONG Yifan skip_expanding_rustc_env = True, 197*d4726bddSHONG Yifan ) 198*d4726bddSHONG Yifan 199*d4726bddSHONG Yifan args.process_wrapper_flags.add("--stdout-file", unpretty_out) 200*d4726bddSHONG Yifan 201*d4726bddSHONG Yifan # Expand all macros and dump the source to stdout. 202*d4726bddSHONG Yifan # Tracking issue: https://github.com/rust-lang/rust/issues/43364 203*d4726bddSHONG Yifan args.rustc_flags.add("-Zunpretty={}".format(mode)) 204*d4726bddSHONG Yifan 205*d4726bddSHONG Yifan ctx.actions.run( 206*d4726bddSHONG Yifan executable = ctx.executable._process_wrapper, 207*d4726bddSHONG Yifan inputs = compile_inputs, 208*d4726bddSHONG Yifan outputs = [unpretty_out], 209*d4726bddSHONG Yifan env = env, 210*d4726bddSHONG Yifan arguments = args.all, 211*d4726bddSHONG Yifan mnemonic = mnemonic, 212*d4726bddSHONG Yifan toolchain = "@rules_rust//rust:toolchain_type", 213*d4726bddSHONG Yifan ) 214*d4726bddSHONG Yifan 215*d4726bddSHONG Yifan output_groups.update({"rust_unpretty": depset(outputs)}) 216*d4726bddSHONG Yifan 217*d4726bddSHONG Yifan return [ 218*d4726bddSHONG Yifan OutputGroupInfo(**output_groups), 219*d4726bddSHONG Yifan ] 220*d4726bddSHONG Yifan 221*d4726bddSHONG Yifan# Example: Expand all rust targets in the codebase. 222*d4726bddSHONG Yifan# bazel build --aspects=@rules_rust//rust:defs.bzl%rust_unpretty_aspect \ 223*d4726bddSHONG Yifan# --output_groups=expanded \ 224*d4726bddSHONG Yifan# //... 225*d4726bddSHONG Yifanrust_unpretty_aspect = aspect( 226*d4726bddSHONG Yifan implementation = _rust_unpretty_aspect_impl, 227*d4726bddSHONG Yifan fragments = ["cpp"], 228*d4726bddSHONG Yifan attrs = { 229*d4726bddSHONG Yifan "_unpretty_modes": attr.label( 230*d4726bddSHONG Yifan doc = "The values to pass to `--unpretty`", 231*d4726bddSHONG Yifan providers = [RustUnprettyInfo], 232*d4726bddSHONG Yifan default = Label("//rust/settings:unpretty"), 233*d4726bddSHONG Yifan ), 234*d4726bddSHONG Yifan } | RUSTC_ATTRS, 235*d4726bddSHONG Yifan toolchains = [ 236*d4726bddSHONG Yifan str(Label("//rust:toolchain_type")), 237*d4726bddSHONG Yifan "@bazel_tools//tools/cpp:toolchain_type", 238*d4726bddSHONG Yifan ], 239*d4726bddSHONG Yifan required_providers = [rust_common.crate_info], 240*d4726bddSHONG Yifan doc = """\ 241*d4726bddSHONG YifanExecutes Rust expand on specified targets. 242*d4726bddSHONG Yifan 243*d4726bddSHONG YifanThis aspect applies to existing rust_library, rust_test, and rust_binary rules. 244*d4726bddSHONG Yifan 245*d4726bddSHONG YifanAs an example, if the following is defined in `examples/hello_lib/BUILD.bazel`: 246*d4726bddSHONG Yifan 247*d4726bddSHONG Yifan```python 248*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") 249*d4726bddSHONG Yifan 250*d4726bddSHONG Yifanrust_library( 251*d4726bddSHONG Yifan name = "hello_lib", 252*d4726bddSHONG Yifan srcs = ["src/lib.rs"], 253*d4726bddSHONG Yifan) 254*d4726bddSHONG Yifan 255*d4726bddSHONG Yifanrust_test( 256*d4726bddSHONG Yifan name = "greeting_test", 257*d4726bddSHONG Yifan srcs = ["tests/greeting.rs"], 258*d4726bddSHONG Yifan deps = [":hello_lib"], 259*d4726bddSHONG Yifan) 260*d4726bddSHONG Yifan``` 261*d4726bddSHONG Yifan 262*d4726bddSHONG YifanThen the targets can be expanded with the following command: 263*d4726bddSHONG Yifan 264*d4726bddSHONG Yifan```output 265*d4726bddSHONG Yifan$ bazel build --aspects=@rules_rust//rust:defs.bzl%rust_unpretty_aspect \ 266*d4726bddSHONG Yifan --output_groups=rust_unpretty_expanded //hello_lib:all 267*d4726bddSHONG Yifan``` 268*d4726bddSHONG Yifan""", 269*d4726bddSHONG Yifan) 270*d4726bddSHONG Yifan 271*d4726bddSHONG Yifandef _rust_unpretty_rule_impl(ctx): 272*d4726bddSHONG Yifan mode = ctx.attr.mode 273*d4726bddSHONG Yifan output_group = "rust_unpretty_{}".format(mode.replace(",", "_").replace("-", "_")) 274*d4726bddSHONG Yifan files = [] 275*d4726bddSHONG Yifan for target in ctx.attr.deps: 276*d4726bddSHONG Yifan files.append(getattr(target[OutputGroupInfo], output_group)) 277*d4726bddSHONG Yifan 278*d4726bddSHONG Yifan return [DefaultInfo(files = depset(transitive = files))] 279*d4726bddSHONG Yifan 280*d4726bddSHONG Yifanrust_unpretty = rule( 281*d4726bddSHONG Yifan implementation = _rust_unpretty_rule_impl, 282*d4726bddSHONG Yifan cfg = nightly_unpretty_transition, 283*d4726bddSHONG Yifan attrs = { 284*d4726bddSHONG Yifan "deps": attr.label_list( 285*d4726bddSHONG Yifan doc = "Rust targets to run unpretty on.", 286*d4726bddSHONG Yifan providers = [rust_common.crate_info], 287*d4726bddSHONG Yifan aspects = [rust_unpretty_aspect], 288*d4726bddSHONG Yifan ), 289*d4726bddSHONG Yifan "mode": attr.string( 290*d4726bddSHONG Yifan doc = "The value to pass to `--unpretty`", 291*d4726bddSHONG Yifan values = _UNPRETTY_MODES, 292*d4726bddSHONG Yifan default = "expanded", 293*d4726bddSHONG Yifan ), 294*d4726bddSHONG Yifan "_allowlist_function_transition": attr.label( 295*d4726bddSHONG Yifan default = Label("//tools/allowlists/function_transition_allowlist"), 296*d4726bddSHONG Yifan ), 297*d4726bddSHONG Yifan "_unpretty_modes": attr.label( 298*d4726bddSHONG Yifan doc = "The values to pass to `--unpretty`", 299*d4726bddSHONG Yifan providers = [RustUnprettyInfo], 300*d4726bddSHONG Yifan default = Label("//rust/settings:unpretty"), 301*d4726bddSHONG Yifan ), 302*d4726bddSHONG Yifan }, 303*d4726bddSHONG Yifan doc = """\ 304*d4726bddSHONG YifanExecutes rust unpretty on a specific target. 305*d4726bddSHONG Yifan 306*d4726bddSHONG YifanSimilar to `rust_unpretty_aspect`, but allows specifying a list of dependencies \ 307*d4726bddSHONG Yifanwithin the build system. 308*d4726bddSHONG Yifan 309*d4726bddSHONG YifanFor example, given the following example targets: 310*d4726bddSHONG Yifan 311*d4726bddSHONG Yifan```python 312*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") 313*d4726bddSHONG Yifan 314*d4726bddSHONG Yifanrust_library( 315*d4726bddSHONG Yifan name = "hello_lib", 316*d4726bddSHONG Yifan srcs = ["src/lib.rs"], 317*d4726bddSHONG Yifan) 318*d4726bddSHONG Yifan 319*d4726bddSHONG Yifanrust_test( 320*d4726bddSHONG Yifan name = "greeting_test", 321*d4726bddSHONG Yifan srcs = ["tests/greeting.rs"], 322*d4726bddSHONG Yifan deps = [":hello_lib"], 323*d4726bddSHONG Yifan) 324*d4726bddSHONG Yifan``` 325*d4726bddSHONG Yifan 326*d4726bddSHONG YifanRust expand can be set as a build target with the following: 327*d4726bddSHONG Yifan 328*d4726bddSHONG Yifan```python 329*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_unpretty") 330*d4726bddSHONG Yifan 331*d4726bddSHONG Yifanrust_unpretty( 332*d4726bddSHONG Yifan name = "hello_library_expand", 333*d4726bddSHONG Yifan testonly = True, 334*d4726bddSHONG Yifan deps = [ 335*d4726bddSHONG Yifan ":hello_lib", 336*d4726bddSHONG Yifan ":greeting_test", 337*d4726bddSHONG Yifan ], 338*d4726bddSHONG Yifan mode = "expand", 339*d4726bddSHONG Yifan) 340*d4726bddSHONG Yifan``` 341*d4726bddSHONG Yifan""", 342*d4726bddSHONG Yifan) 343