"""A module defining Rust 'unpretty' rules""" load("//rust/private:common.bzl", "rust_common") load( "//rust/private:rust.bzl", "RUSTC_ATTRS", "get_rust_test_flags", ) load( "//rust/private:rustc.bzl", "collect_deps", "collect_inputs", "construct_arguments", ) load( "//rust/private:utils.bzl", "determine_output_hash", "find_cc_toolchain", "find_toolchain", ) # This list is determined by running the following command: # # rustc +nightly -Zunpretty= # _UNPRETTY_MODES = [ "ast-tree,expanded", "ast-tree", "expanded,hygiene", "expanded,identified", "expanded", "hir-tree", "hir,identified", "hir,typed", "hir", "identified", "mir-cfg", "mir", "normal", ] RustUnprettyInfo = provider( doc = "A provider describing the Rust unpretty mode.", fields = { "modes": "Depset[string]: Can be any of {}".format(["'{}'".format(m) for m in _UNPRETTY_MODES]), }, ) def _rust_unpretty_flag_impl(ctx): value = ctx.build_setting_value invalid = [] for mode in value: if mode not in _UNPRETTY_MODES: invalid.append(mode) if invalid: fail("{} build setting allowed to take values [{}] but was set to unallowed values: {}".format( ctx.label, ", ".join(["'{}'".format(m) for m in _UNPRETTY_MODES]), invalid, )) return RustUnprettyInfo(modes = depset(value)) rust_unpretty_flag = rule( doc = "A build setting which represents the Rust unpretty mode. The allowed values are {}".format(_UNPRETTY_MODES), implementation = _rust_unpretty_flag_impl, build_setting = config.string_list( flag = True, repeatable = True, ), ) def _nightly_unpretty_transition_impl(settings, attr): mode = settings[str(Label("//rust/settings:unpretty"))] # Use the presence of _unpretty_modes as a proxy for whether this is a rust_unpretty target. if hasattr(attr, "_unpretty_modes") and hasattr(attr, "mode"): mode = mode + [attr.mode] return { str(Label("//rust/settings:unpretty")): depset(mode).to_list(), str(Label("//rust/toolchain/channel")): "nightly", } nightly_unpretty_transition = transition( implementation = _nightly_unpretty_transition_impl, inputs = [str(Label("//rust/settings:unpretty"))], outputs = [ str(Label("//rust/settings:unpretty")), str(Label("//rust/toolchain/channel")), ], ) def _get_unpretty_ready_crate_info(target, aspect_ctx): """Check that a target is suitable for expansion and extract the `CrateInfo` provider from it. Args: target (Target): The target the aspect is running on. aspect_ctx (ctx, optional): The aspect's context object. Returns: CrateInfo, optional: A `CrateInfo` provider if rust unpretty should be run or `None`. """ # Ignore external targets if target.label.workspace_root.startswith("external"): return None # Targets with specific tags will not be formatted if aspect_ctx: ignore_tags = [ "nounpretty", "no-unpretty", "no_unpretty", ] for tag in ignore_tags: if tag in aspect_ctx.rule.attr.tags: return None # Obviously ignore any targets that don't contain `CrateInfo` if rust_common.crate_info not in target: return None return target[rust_common.crate_info] def _rust_unpretty_aspect_impl(target, ctx): crate_info = _get_unpretty_ready_crate_info(target, ctx) if not crate_info: return [] toolchain = find_toolchain(ctx) cc_toolchain, feature_configuration = find_cc_toolchain(ctx) dep_info, build_info, _ = collect_deps( deps = crate_info.deps, proc_macro_deps = crate_info.proc_macro_deps, aliases = crate_info.aliases, ) compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs( ctx, ctx.rule.file, ctx.rule.files, # Rust expand doesn't need to invoke transitive linking, therefore doesn't need linkstamps. depset([]), toolchain, cc_toolchain, feature_configuration, crate_info, dep_info, build_info, ) output_groups = {} outputs = [] for mode in ctx.attr._unpretty_modes[RustUnprettyInfo].modes.to_list(): pretty_mode = mode.replace("-", "_") mnemonic = "RustUnpretty{}".format("".join([ o.title() for m in pretty_mode.split(",") for o in m.split("_") ])) unpretty_out = ctx.actions.declare_file( "{}.unpretty.{}.rs".format(ctx.label.name, pretty_mode.replace(",", ".")), sibling = crate_info.output, ) output_groups.update({"rust_unpretty_{}".format(pretty_mode.replace(",", "_")): depset([unpretty_out])}) outputs.append(unpretty_out) rust_flags = [] if crate_info.is_test: rust_flags = get_rust_test_flags(ctx.rule.attr) args, env = construct_arguments( ctx = ctx, attr = ctx.rule.attr, file = ctx.file, toolchain = toolchain, tool_path = toolchain.rustc.path, cc_toolchain = cc_toolchain, feature_configuration = feature_configuration, crate_info = crate_info, dep_info = dep_info, linkstamp_outs = linkstamp_outs, ambiguous_libs = ambiguous_libs, output_hash = determine_output_hash(crate_info.root, ctx.label), rust_flags = rust_flags, out_dir = out_dir, build_env_files = build_env_files, build_flags_files = build_flags_files, emit = ["dep-info", "metadata"], skip_expanding_rustc_env = True, ) args.process_wrapper_flags.add("--stdout-file", unpretty_out) # Expand all macros and dump the source to stdout. # Tracking issue: https://github.com/rust-lang/rust/issues/43364 args.rustc_flags.add("-Zunpretty={}".format(mode)) ctx.actions.run( executable = ctx.executable._process_wrapper, inputs = compile_inputs, outputs = [unpretty_out], env = env, arguments = args.all, mnemonic = mnemonic, toolchain = "@rules_rust//rust:toolchain_type", ) output_groups.update({"rust_unpretty": depset(outputs)}) return [ OutputGroupInfo(**output_groups), ] # Example: Expand all rust targets in the codebase. # bazel build --aspects=@rules_rust//rust:defs.bzl%rust_unpretty_aspect \ # --output_groups=expanded \ # //... rust_unpretty_aspect = aspect( implementation = _rust_unpretty_aspect_impl, fragments = ["cpp"], attrs = { "_unpretty_modes": attr.label( doc = "The values to pass to `--unpretty`", providers = [RustUnprettyInfo], default = Label("//rust/settings:unpretty"), ), } | RUSTC_ATTRS, toolchains = [ str(Label("//rust:toolchain_type")), "@bazel_tools//tools/cpp:toolchain_type", ], required_providers = [rust_common.crate_info], doc = """\ Executes Rust expand on specified targets. This aspect applies to existing rust_library, rust_test, and rust_binary rules. As an example, if the following is defined in `examples/hello_lib/BUILD.bazel`: ```python load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") rust_library( name = "hello_lib", srcs = ["src/lib.rs"], ) rust_test( name = "greeting_test", srcs = ["tests/greeting.rs"], deps = [":hello_lib"], ) ``` Then the targets can be expanded with the following command: ```output $ bazel build --aspects=@rules_rust//rust:defs.bzl%rust_unpretty_aspect \ --output_groups=rust_unpretty_expanded //hello_lib:all ``` """, ) def _rust_unpretty_rule_impl(ctx): mode = ctx.attr.mode output_group = "rust_unpretty_{}".format(mode.replace(",", "_").replace("-", "_")) files = [] for target in ctx.attr.deps: files.append(getattr(target[OutputGroupInfo], output_group)) return [DefaultInfo(files = depset(transitive = files))] rust_unpretty = rule( implementation = _rust_unpretty_rule_impl, cfg = nightly_unpretty_transition, attrs = { "deps": attr.label_list( doc = "Rust targets to run unpretty on.", providers = [rust_common.crate_info], aspects = [rust_unpretty_aspect], ), "mode": attr.string( doc = "The value to pass to `--unpretty`", values = _UNPRETTY_MODES, default = "expanded", ), "_allowlist_function_transition": attr.label( default = Label("//tools/allowlists/function_transition_allowlist"), ), "_unpretty_modes": attr.label( doc = "The values to pass to `--unpretty`", providers = [RustUnprettyInfo], default = Label("//rust/settings:unpretty"), ), }, doc = """\ Executes rust unpretty on a specific target. Similar to `rust_unpretty_aspect`, but allows specifying a list of dependencies \ within the build system. For example, given the following example targets: ```python load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") rust_library( name = "hello_lib", srcs = ["src/lib.rs"], ) rust_test( name = "greeting_test", srcs = ["tests/greeting.rs"], deps = [":hello_lib"], ) ``` Rust expand can be set as a build target with the following: ```python load("@rules_rust//rust:defs.bzl", "rust_unpretty") rust_unpretty( name = "hello_library_expand", testonly = True, deps = [ ":hello_lib", ":greeting_test", ], mode = "expand", ) ``` """, )