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