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,
+)