Squashed 'third_party/rules_rust/' content from commit bf59038ca
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/rust/private/rustfmt.bzl b/rust/private/rustfmt.bzl
new file mode 100644
index 0000000..9c59f48
--- /dev/null
+++ b/rust/private/rustfmt.bzl
@@ -0,0 +1,186 @@
+"""A module defining rustfmt rules"""
+
+load(":common.bzl", "rust_common")
+load(":utils.bzl", "find_toolchain")
+
+def _find_rustfmtable_srcs(target, aspect_ctx = None):
+ """Parse a target for rustfmt formattable sources.
+
+ Args:
+ target (Target): The target the aspect is running on.
+ aspect_ctx (ctx, optional): The aspect's context object.
+
+ Returns:
+ list: A list of formattable sources (`File`).
+ """
+ if rust_common.crate_info not in target:
+ return []
+
+ # Ignore external targets
+ if target.label.workspace_root.startswith("external"):
+ return []
+
+ # Targets annotated with `norustfmt` will not be formatted
+ if aspect_ctx and "norustfmt" in aspect_ctx.rule.attr.tags:
+ return []
+
+ crate_info = target[rust_common.crate_info]
+
+ # Filter out any generated files
+ srcs = [src for src in crate_info.srcs.to_list() if src.is_source]
+
+ return srcs
+
+def _generate_manifest(edition, srcs, ctx):
+ # Gather the source paths to non-generated files
+ src_paths = [src.path for src in srcs]
+
+ # Write the rustfmt manifest
+ manifest = ctx.actions.declare_file(ctx.label.name + ".rustfmt")
+ ctx.actions.write(
+ output = manifest,
+ content = "\n".join(src_paths + [
+ edition,
+ ]),
+ )
+
+ return manifest
+
+def _perform_check(edition, srcs, ctx):
+ toolchain = find_toolchain(ctx)
+ config = ctx.file._config
+ marker = ctx.actions.declare_file(ctx.label.name + ".rustfmt.ok")
+
+ args = ctx.actions.args()
+ args.add("--touch-file")
+ args.add(marker)
+ args.add("--")
+ args.add(toolchain.rustfmt)
+ args.add("--config-path")
+ args.add(config)
+ args.add("--edition")
+ args.add(edition)
+ args.add("--check")
+ args.add_all(srcs)
+
+ ctx.actions.run(
+ executable = ctx.executable._process_wrapper,
+ inputs = srcs + [config],
+ outputs = [marker],
+ tools = [toolchain.rustfmt],
+ arguments = [args],
+ mnemonic = "Rustfmt",
+ )
+
+ return marker
+
+def _rustfmt_aspect_impl(target, ctx):
+ srcs = _find_rustfmtable_srcs(target, ctx)
+
+ # If there are no formattable sources, do nothing.
+ if not srcs:
+ return []
+
+ # Parse the edition to use for formatting from the target
+ edition = target[rust_common.crate_info].edition
+
+ manifest = _generate_manifest(edition, srcs, ctx)
+ marker = _perform_check(edition, srcs, ctx)
+
+ return [
+ OutputGroupInfo(
+ rustfmt_manifest = depset([manifest]),
+ rustfmt_checks = depset([marker]),
+ ),
+ ]
+
+rustfmt_aspect = aspect(
+ implementation = _rustfmt_aspect_impl,
+ doc = """\
+This aspect is used to gather information about a crate for use in rustfmt and perform rustfmt checks
+
+Output Groups:
+
+- `rustfmt_manifest`: A manifest used by rustfmt binaries to provide crate specific settings.
+- `rustfmt_checks`: Executes `rustfmt --check` on the specified target.
+
+The build setting `@rules_rust//:rustfmt.toml` is used to control the Rustfmt [configuration settings][cs]
+used at runtime.
+
+[cs]: https://rust-lang.github.io/rustfmt/
+
+This aspect is executed on any target which provides the `CrateInfo` provider. However
+users may tag a target with `norustfmt` to have it skipped. Additionally, generated
+source files are also ignored by this aspect.
+""",
+ attrs = {
+ "_config": attr.label(
+ doc = "The `rustfmt.toml` file used for formatting",
+ allow_single_file = True,
+ default = Label("//:rustfmt.toml"),
+ ),
+ "_process_wrapper": attr.label(
+ doc = "A process wrapper for running rustfmt on all platforms",
+ cfg = "exec",
+ executable = True,
+ default = Label("//util/process_wrapper"),
+ ),
+ },
+ incompatible_use_toolchain_transition = True,
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ ],
+)
+
+def _rustfmt_test_impl(ctx):
+ # The executable of a test target must be the output of an action in
+ # the rule implementation. This file is simply a symlink to the real
+ # rustfmt test runner.
+ runner = ctx.actions.declare_file("{}{}".format(
+ ctx.label.name,
+ ctx.executable._runner.extension,
+ ))
+
+ ctx.actions.symlink(
+ output = runner,
+ target_file = ctx.executable._runner,
+ is_executable = True,
+ )
+
+ manifests = [target[OutputGroupInfo].rustfmt_manifest for target in ctx.attr.targets]
+ srcs = [depset(_find_rustfmtable_srcs(target)) for target in ctx.attr.targets]
+
+ runfiles = ctx.runfiles(
+ transitive_files = depset(transitive = manifests + srcs),
+ )
+
+ runfiles = runfiles.merge(
+ ctx.attr._runner[DefaultInfo].default_runfiles,
+ )
+
+ return [DefaultInfo(
+ files = depset([runner]),
+ runfiles = runfiles,
+ executable = runner,
+ )]
+
+rustfmt_test = rule(
+ implementation = _rustfmt_test_impl,
+ doc = "A test rule for performing `rustfmt --check` on a set of targets",
+ attrs = {
+ "targets": attr.label_list(
+ doc = "Rust targets to run `rustfmt --check` on.",
+ providers = [rust_common.crate_info],
+ aspects = [rustfmt_aspect],
+ ),
+ "_runner": attr.label(
+ doc = "The rustfmt test runner",
+ cfg = "exec",
+ executable = True,
+ default = Label("//tools/rustfmt:rustfmt_test"),
+ ),
+ },
+ test = True,
+)