1*d4726bddSHONG Yifan"""A module defining rustfmt rules""" 2*d4726bddSHONG Yifan 3*d4726bddSHONG Yifanload(":common.bzl", "rust_common") 4*d4726bddSHONG Yifan 5*d4726bddSHONG Yifandef _get_rustfmt_ready_crate_info(target): 6*d4726bddSHONG Yifan """Check that a target is suitable for rustfmt and extract the `CrateInfo` provider from it. 7*d4726bddSHONG Yifan 8*d4726bddSHONG Yifan Args: 9*d4726bddSHONG Yifan target (Target): The target the aspect is running on. 10*d4726bddSHONG Yifan 11*d4726bddSHONG Yifan Returns: 12*d4726bddSHONG Yifan CrateInfo, optional: A `CrateInfo` provider if clippy should be run or `None`. 13*d4726bddSHONG Yifan """ 14*d4726bddSHONG Yifan 15*d4726bddSHONG Yifan # Ignore external targets 16*d4726bddSHONG Yifan if target.label.workspace_name: 17*d4726bddSHONG Yifan return None 18*d4726bddSHONG Yifan 19*d4726bddSHONG Yifan # Obviously ignore any targets that don't contain `CrateInfo` 20*d4726bddSHONG Yifan if rust_common.crate_info in target: 21*d4726bddSHONG Yifan return target[rust_common.crate_info] 22*d4726bddSHONG Yifan elif rust_common.test_crate_info in target: 23*d4726bddSHONG Yifan return target[rust_common.test_crate_info].crate 24*d4726bddSHONG Yifan else: 25*d4726bddSHONG Yifan return None 26*d4726bddSHONG Yifan 27*d4726bddSHONG Yifandef _find_rustfmtable_srcs(crate_info, aspect_ctx = None): 28*d4726bddSHONG Yifan """Parse a `CrateInfo` provider for rustfmt formattable sources. 29*d4726bddSHONG Yifan 30*d4726bddSHONG Yifan Args: 31*d4726bddSHONG Yifan crate_info (CrateInfo): A `CrateInfo` provider. 32*d4726bddSHONG Yifan aspect_ctx (ctx, optional): The aspect's context object. 33*d4726bddSHONG Yifan 34*d4726bddSHONG Yifan Returns: 35*d4726bddSHONG Yifan list: A list of formattable sources (`File`). 36*d4726bddSHONG Yifan """ 37*d4726bddSHONG Yifan 38*d4726bddSHONG Yifan # Targets with specific tags will not be formatted 39*d4726bddSHONG Yifan if aspect_ctx: 40*d4726bddSHONG Yifan ignore_tags = [ 41*d4726bddSHONG Yifan "no-format", 42*d4726bddSHONG Yifan "no-rustfmt", 43*d4726bddSHONG Yifan "norustfmt", 44*d4726bddSHONG Yifan ] 45*d4726bddSHONG Yifan 46*d4726bddSHONG Yifan for tag in ignore_tags: 47*d4726bddSHONG Yifan if tag in aspect_ctx.rule.attr.tags: 48*d4726bddSHONG Yifan return [] 49*d4726bddSHONG Yifan 50*d4726bddSHONG Yifan # Filter out any generated files 51*d4726bddSHONG Yifan srcs = [src for src in crate_info.srcs.to_list() if src.is_source] 52*d4726bddSHONG Yifan 53*d4726bddSHONG Yifan return srcs 54*d4726bddSHONG Yifan 55*d4726bddSHONG Yifandef _generate_manifest(edition, srcs, ctx): 56*d4726bddSHONG Yifan workspace = ctx.label.workspace_name or ctx.workspace_name 57*d4726bddSHONG Yifan 58*d4726bddSHONG Yifan # Gather the source paths to non-generated files 59*d4726bddSHONG Yifan content = ctx.actions.args() 60*d4726bddSHONG Yifan content.set_param_file_format("multiline") 61*d4726bddSHONG Yifan content.add_all(srcs, format_each = workspace + "/%s") 62*d4726bddSHONG Yifan content.add(edition) 63*d4726bddSHONG Yifan 64*d4726bddSHONG Yifan # Write the rustfmt manifest 65*d4726bddSHONG Yifan manifest = ctx.actions.declare_file(ctx.label.name + ".rustfmt") 66*d4726bddSHONG Yifan ctx.actions.write( 67*d4726bddSHONG Yifan output = manifest, 68*d4726bddSHONG Yifan content = content, 69*d4726bddSHONG Yifan ) 70*d4726bddSHONG Yifan 71*d4726bddSHONG Yifan return manifest 72*d4726bddSHONG Yifan 73*d4726bddSHONG Yifandef _perform_check(edition, srcs, ctx): 74*d4726bddSHONG Yifan rustfmt_toolchain = ctx.toolchains[Label("//rust/rustfmt:toolchain_type")] 75*d4726bddSHONG Yifan 76*d4726bddSHONG Yifan config = ctx.file._config 77*d4726bddSHONG Yifan marker = ctx.actions.declare_file(ctx.label.name + ".rustfmt.ok") 78*d4726bddSHONG Yifan 79*d4726bddSHONG Yifan args = ctx.actions.args() 80*d4726bddSHONG Yifan args.add("--touch-file", marker) 81*d4726bddSHONG Yifan args.add("--") 82*d4726bddSHONG Yifan args.add(rustfmt_toolchain.rustfmt) 83*d4726bddSHONG Yifan args.add("--config-path", config) 84*d4726bddSHONG Yifan args.add("--edition", edition) 85*d4726bddSHONG Yifan args.add("--check") 86*d4726bddSHONG Yifan args.add_all(srcs) 87*d4726bddSHONG Yifan 88*d4726bddSHONG Yifan ctx.actions.run( 89*d4726bddSHONG Yifan executable = ctx.executable._process_wrapper, 90*d4726bddSHONG Yifan inputs = srcs + [config], 91*d4726bddSHONG Yifan outputs = [marker], 92*d4726bddSHONG Yifan tools = [rustfmt_toolchain.all_files], 93*d4726bddSHONG Yifan arguments = [args], 94*d4726bddSHONG Yifan mnemonic = "Rustfmt", 95*d4726bddSHONG Yifan ) 96*d4726bddSHONG Yifan 97*d4726bddSHONG Yifan return marker 98*d4726bddSHONG Yifan 99*d4726bddSHONG Yifandef _rustfmt_aspect_impl(target, ctx): 100*d4726bddSHONG Yifan crate_info = _get_rustfmt_ready_crate_info(target) 101*d4726bddSHONG Yifan 102*d4726bddSHONG Yifan if not crate_info: 103*d4726bddSHONG Yifan return [] 104*d4726bddSHONG Yifan 105*d4726bddSHONG Yifan srcs = _find_rustfmtable_srcs(crate_info, ctx) 106*d4726bddSHONG Yifan 107*d4726bddSHONG Yifan # If there are no formattable sources, do nothing. 108*d4726bddSHONG Yifan if not srcs: 109*d4726bddSHONG Yifan return [] 110*d4726bddSHONG Yifan 111*d4726bddSHONG Yifan edition = crate_info.edition 112*d4726bddSHONG Yifan 113*d4726bddSHONG Yifan marker = _perform_check(edition, srcs, ctx) 114*d4726bddSHONG Yifan 115*d4726bddSHONG Yifan return [ 116*d4726bddSHONG Yifan OutputGroupInfo( 117*d4726bddSHONG Yifan rustfmt_checks = depset([marker]), 118*d4726bddSHONG Yifan ), 119*d4726bddSHONG Yifan ] 120*d4726bddSHONG Yifan 121*d4726bddSHONG Yifanrustfmt_aspect = aspect( 122*d4726bddSHONG Yifan implementation = _rustfmt_aspect_impl, 123*d4726bddSHONG Yifan doc = """\ 124*d4726bddSHONG YifanThis aspect is used to gather information about a crate for use in rustfmt and perform rustfmt checks 125*d4726bddSHONG Yifan 126*d4726bddSHONG YifanOutput Groups: 127*d4726bddSHONG Yifan 128*d4726bddSHONG Yifan- `rustfmt_checks`: Executes `rustfmt --check` on the specified target. 129*d4726bddSHONG Yifan 130*d4726bddSHONG YifanThe build setting `@rules_rust//:rustfmt.toml` is used to control the Rustfmt [configuration settings][cs] 131*d4726bddSHONG Yifanused at runtime. 132*d4726bddSHONG Yifan 133*d4726bddSHONG Yifan[cs]: https://rust-lang.github.io/rustfmt/ 134*d4726bddSHONG Yifan 135*d4726bddSHONG YifanThis aspect is executed on any target which provides the `CrateInfo` provider. However 136*d4726bddSHONG Yifanusers may tag a target with `no-rustfmt` or `no-format` to have it skipped. Additionally, 137*d4726bddSHONG Yifangenerated source files are also ignored by this aspect. 138*d4726bddSHONG Yifan""", 139*d4726bddSHONG Yifan attrs = { 140*d4726bddSHONG Yifan "_config": attr.label( 141*d4726bddSHONG Yifan doc = "The `rustfmt.toml` file used for formatting", 142*d4726bddSHONG Yifan allow_single_file = True, 143*d4726bddSHONG Yifan default = Label("//:rustfmt.toml"), 144*d4726bddSHONG Yifan ), 145*d4726bddSHONG Yifan "_process_wrapper": attr.label( 146*d4726bddSHONG Yifan doc = "A process wrapper for running rustfmt on all platforms", 147*d4726bddSHONG Yifan cfg = "exec", 148*d4726bddSHONG Yifan executable = True, 149*d4726bddSHONG Yifan default = Label("//util/process_wrapper"), 150*d4726bddSHONG Yifan ), 151*d4726bddSHONG Yifan }, 152*d4726bddSHONG Yifan required_providers = [ 153*d4726bddSHONG Yifan [rust_common.crate_info], 154*d4726bddSHONG Yifan [rust_common.test_crate_info], 155*d4726bddSHONG Yifan ], 156*d4726bddSHONG Yifan fragments = ["cpp"], 157*d4726bddSHONG Yifan toolchains = [ 158*d4726bddSHONG Yifan str(Label("//rust/rustfmt:toolchain_type")), 159*d4726bddSHONG Yifan ], 160*d4726bddSHONG Yifan) 161*d4726bddSHONG Yifan 162*d4726bddSHONG Yifandef _rustfmt_test_manifest_aspect_impl(target, ctx): 163*d4726bddSHONG Yifan crate_info = _get_rustfmt_ready_crate_info(target) 164*d4726bddSHONG Yifan 165*d4726bddSHONG Yifan if not crate_info: 166*d4726bddSHONG Yifan return [] 167*d4726bddSHONG Yifan 168*d4726bddSHONG Yifan # Parse the edition to use for formatting from the target 169*d4726bddSHONG Yifan edition = crate_info.edition 170*d4726bddSHONG Yifan 171*d4726bddSHONG Yifan srcs = _find_rustfmtable_srcs(crate_info, ctx) 172*d4726bddSHONG Yifan manifest = _generate_manifest(edition, srcs, ctx) 173*d4726bddSHONG Yifan 174*d4726bddSHONG Yifan return [ 175*d4726bddSHONG Yifan OutputGroupInfo( 176*d4726bddSHONG Yifan rustfmt_manifest = depset([manifest]), 177*d4726bddSHONG Yifan ), 178*d4726bddSHONG Yifan ] 179*d4726bddSHONG Yifan 180*d4726bddSHONG Yifan# This aspect contains functionality split out of `rustfmt_aspect` which broke when 181*d4726bddSHONG Yifan# `required_providers` was added to it. Aspects which have `required_providers` seems 182*d4726bddSHONG Yifan# to not function with attributes that also require providers. 183*d4726bddSHONG Yifan_rustfmt_test_manifest_aspect = aspect( 184*d4726bddSHONG Yifan implementation = _rustfmt_test_manifest_aspect_impl, 185*d4726bddSHONG Yifan doc = """\ 186*d4726bddSHONG YifanThis aspect is used to gather information about a crate for use in `rustfmt_test` 187*d4726bddSHONG Yifan 188*d4726bddSHONG YifanOutput Groups: 189*d4726bddSHONG Yifan 190*d4726bddSHONG Yifan- `rustfmt_manifest`: A manifest used by rustfmt binaries to provide crate specific settings. 191*d4726bddSHONG Yifan""", 192*d4726bddSHONG Yifan fragments = ["cpp"], 193*d4726bddSHONG Yifan toolchains = [ 194*d4726bddSHONG Yifan str(Label("//rust/rustfmt:toolchain_type")), 195*d4726bddSHONG Yifan ], 196*d4726bddSHONG Yifan) 197*d4726bddSHONG Yifan 198*d4726bddSHONG Yifandef _rustfmt_test_impl(ctx): 199*d4726bddSHONG Yifan # The executable of a test target must be the output of an action in 200*d4726bddSHONG Yifan # the rule implementation. This file is simply a symlink to the real 201*d4726bddSHONG Yifan # rustfmt test runner. 202*d4726bddSHONG Yifan is_windows = ctx.executable._runner.extension == ".exe" 203*d4726bddSHONG Yifan runner = ctx.actions.declare_file("{}{}".format( 204*d4726bddSHONG Yifan ctx.label.name, 205*d4726bddSHONG Yifan ".exe" if is_windows else "", 206*d4726bddSHONG Yifan )) 207*d4726bddSHONG Yifan 208*d4726bddSHONG Yifan ctx.actions.symlink( 209*d4726bddSHONG Yifan output = runner, 210*d4726bddSHONG Yifan target_file = ctx.executable._runner, 211*d4726bddSHONG Yifan is_executable = True, 212*d4726bddSHONG Yifan ) 213*d4726bddSHONG Yifan 214*d4726bddSHONG Yifan crate_infos = [_get_rustfmt_ready_crate_info(target) for target in ctx.attr.targets] 215*d4726bddSHONG Yifan srcs = [depset(_find_rustfmtable_srcs(crate_info)) for crate_info in crate_infos if crate_info] 216*d4726bddSHONG Yifan 217*d4726bddSHONG Yifan # Some targets may be included in tests but tagged as "no-format". In this 218*d4726bddSHONG Yifan # case, there will be no manifest. 219*d4726bddSHONG Yifan manifests = [getattr(target[OutputGroupInfo], "rustfmt_manifest", None) for target in ctx.attr.targets] 220*d4726bddSHONG Yifan manifests = depset(transitive = [manifest for manifest in manifests if manifest]) 221*d4726bddSHONG Yifan 222*d4726bddSHONG Yifan runfiles = ctx.runfiles( 223*d4726bddSHONG Yifan transitive_files = depset(transitive = srcs + [manifests]), 224*d4726bddSHONG Yifan ) 225*d4726bddSHONG Yifan 226*d4726bddSHONG Yifan runfiles = runfiles.merge( 227*d4726bddSHONG Yifan ctx.attr._runner[DefaultInfo].default_runfiles, 228*d4726bddSHONG Yifan ) 229*d4726bddSHONG Yifan 230*d4726bddSHONG Yifan workspace = ctx.label.workspace_name or ctx.workspace_name 231*d4726bddSHONG Yifan 232*d4726bddSHONG Yifan return [ 233*d4726bddSHONG Yifan DefaultInfo( 234*d4726bddSHONG Yifan files = depset([runner]), 235*d4726bddSHONG Yifan runfiles = runfiles, 236*d4726bddSHONG Yifan executable = runner, 237*d4726bddSHONG Yifan ), 238*d4726bddSHONG Yifan testing.TestEnvironment({ 239*d4726bddSHONG Yifan "RUSTFMT_MANIFESTS": ctx.configuration.host_path_separator.join([ 240*d4726bddSHONG Yifan workspace + "/" + manifest.short_path 241*d4726bddSHONG Yifan for manifest in sorted(manifests.to_list()) 242*d4726bddSHONG Yifan ]), 243*d4726bddSHONG Yifan "RUST_BACKTRACE": "1", 244*d4726bddSHONG Yifan }), 245*d4726bddSHONG Yifan ] 246*d4726bddSHONG Yifan 247*d4726bddSHONG Yifanrustfmt_test = rule( 248*d4726bddSHONG Yifan implementation = _rustfmt_test_impl, 249*d4726bddSHONG Yifan doc = "A test rule for performing `rustfmt --check` on a set of targets", 250*d4726bddSHONG Yifan attrs = { 251*d4726bddSHONG Yifan "targets": attr.label_list( 252*d4726bddSHONG Yifan doc = "Rust targets to run `rustfmt --check` on.", 253*d4726bddSHONG Yifan providers = [ 254*d4726bddSHONG Yifan [rust_common.crate_info], 255*d4726bddSHONG Yifan [rust_common.test_crate_info], 256*d4726bddSHONG Yifan ], 257*d4726bddSHONG Yifan aspects = [_rustfmt_test_manifest_aspect], 258*d4726bddSHONG Yifan ), 259*d4726bddSHONG Yifan "_runner": attr.label( 260*d4726bddSHONG Yifan doc = "The rustfmt test runner", 261*d4726bddSHONG Yifan cfg = "exec", 262*d4726bddSHONG Yifan executable = True, 263*d4726bddSHONG Yifan default = Label("//tools/rustfmt:rustfmt_test"), 264*d4726bddSHONG Yifan ), 265*d4726bddSHONG Yifan }, 266*d4726bddSHONG Yifan test = True, 267*d4726bddSHONG Yifan) 268*d4726bddSHONG Yifan 269*d4726bddSHONG Yifandef _rustfmt_toolchain_impl(ctx): 270*d4726bddSHONG Yifan make_variables = { 271*d4726bddSHONG Yifan "RUSTFMT": ctx.file.rustfmt.path, 272*d4726bddSHONG Yifan } 273*d4726bddSHONG Yifan 274*d4726bddSHONG Yifan if ctx.attr.rustc: 275*d4726bddSHONG Yifan make_variables.update({ 276*d4726bddSHONG Yifan "RUSTC": ctx.file.rustc.path, 277*d4726bddSHONG Yifan }) 278*d4726bddSHONG Yifan 279*d4726bddSHONG Yifan make_variable_info = platform_common.TemplateVariableInfo(make_variables) 280*d4726bddSHONG Yifan 281*d4726bddSHONG Yifan all_files = [ctx.file.rustfmt] + ctx.files.rustc_lib 282*d4726bddSHONG Yifan if ctx.file.rustc: 283*d4726bddSHONG Yifan all_files.append(ctx.file.rustc) 284*d4726bddSHONG Yifan 285*d4726bddSHONG Yifan toolchain = platform_common.ToolchainInfo( 286*d4726bddSHONG Yifan rustfmt = ctx.file.rustfmt, 287*d4726bddSHONG Yifan rustc = ctx.file.rustc, 288*d4726bddSHONG Yifan rustc_lib = depset(ctx.files.rustc_lib), 289*d4726bddSHONG Yifan all_files = depset(all_files), 290*d4726bddSHONG Yifan make_variables = make_variable_info, 291*d4726bddSHONG Yifan ) 292*d4726bddSHONG Yifan 293*d4726bddSHONG Yifan return [ 294*d4726bddSHONG Yifan toolchain, 295*d4726bddSHONG Yifan make_variable_info, 296*d4726bddSHONG Yifan ] 297*d4726bddSHONG Yifan 298*d4726bddSHONG Yifanrustfmt_toolchain = rule( 299*d4726bddSHONG Yifan doc = "A toolchain for [rustfmt](https://rust-lang.github.io/rustfmt/)", 300*d4726bddSHONG Yifan implementation = _rustfmt_toolchain_impl, 301*d4726bddSHONG Yifan attrs = { 302*d4726bddSHONG Yifan "rustc": attr.label( 303*d4726bddSHONG Yifan doc = "The location of the `rustc` binary. Can be a direct source or a filegroup containing one item.", 304*d4726bddSHONG Yifan allow_single_file = True, 305*d4726bddSHONG Yifan cfg = "exec", 306*d4726bddSHONG Yifan ), 307*d4726bddSHONG Yifan "rustc_lib": attr.label( 308*d4726bddSHONG Yifan doc = "The libraries used by rustc during compilation.", 309*d4726bddSHONG Yifan cfg = "exec", 310*d4726bddSHONG Yifan ), 311*d4726bddSHONG Yifan "rustfmt": attr.label( 312*d4726bddSHONG Yifan doc = "The location of the `rustfmt` binary. Can be a direct source or a filegroup containing one item.", 313*d4726bddSHONG Yifan allow_single_file = True, 314*d4726bddSHONG Yifan cfg = "exec", 315*d4726bddSHONG Yifan mandatory = True, 316*d4726bddSHONG Yifan ), 317*d4726bddSHONG Yifan }, 318*d4726bddSHONG Yifan toolchains = [ 319*d4726bddSHONG Yifan str(Label("@rules_rust//rust:toolchain_type")), 320*d4726bddSHONG Yifan ], 321*d4726bddSHONG Yifan) 322*d4726bddSHONG Yifan 323*d4726bddSHONG Yifandef _current_rustfmt_toolchain_impl(ctx): 324*d4726bddSHONG Yifan toolchain = ctx.toolchains[str(Label("@rules_rust//rust/rustfmt:toolchain_type"))] 325*d4726bddSHONG Yifan 326*d4726bddSHONG Yifan return [ 327*d4726bddSHONG Yifan toolchain, 328*d4726bddSHONG Yifan toolchain.make_variables, 329*d4726bddSHONG Yifan DefaultInfo( 330*d4726bddSHONG Yifan files = depset([ 331*d4726bddSHONG Yifan toolchain.rustfmt, 332*d4726bddSHONG Yifan ]), 333*d4726bddSHONG Yifan runfiles = ctx.runfiles(transitive_files = toolchain.all_files), 334*d4726bddSHONG Yifan ), 335*d4726bddSHONG Yifan ] 336*d4726bddSHONG Yifan 337*d4726bddSHONG Yifancurrent_rustfmt_toolchain = rule( 338*d4726bddSHONG Yifan doc = "A rule for exposing the current registered `rustfmt_toolchain`.", 339*d4726bddSHONG Yifan implementation = _current_rustfmt_toolchain_impl, 340*d4726bddSHONG Yifan toolchains = [ 341*d4726bddSHONG Yifan str(Label("@rules_rust//rust/rustfmt:toolchain_type")), 342*d4726bddSHONG Yifan ], 343*d4726bddSHONG Yifan) 344