blob: eef61a6a1f385c89dcfd0b44ed8343f2a9446a63 [file]
"""Shared helpers for `rust_clippy_test` and `rustfmt_test`.
Both rules follow the same shape: a thin wrapper aspect that walks
`deps`/`proc_macro_deps`/`crate` and collects the output-group markers
produced by the underlying real aspect (`rust_clippy_aspect` /
`rustfmt_aspect`), plus a rule impl that symlinks a shared runner binary
and hands it the collected marker rlocationpaths via `RUST_LINT_TEST_MARKERS`.
The pieces exposed here — `rlocationpath`, `platform_transition`,
`LINT_TEST_COMMON_ATTRS`, `lint_test_aspect_impl`, `lint_test_rule_impl` —
let each rule file supply only what actually differs (the provider type
and the output-group names it collects).
"""
def rlocationpath(file, workspace_name):
"""Compute the runfile rlocationpath for a `File`.
Args:
file (File): The file to compute the rlocationpath for.
workspace_name (str): The name of the current workspace.
Returns:
str: The rlocationpath the runner should look up for `file`.
"""
if file.short_path.startswith("../"):
return file.short_path[len("../"):]
return "{}/{}".format(workspace_name, file.short_path)
def _platform_transition_impl(_settings, attr):
if not attr.platform:
return {}
platform = str(attr.platform)
if not platform.startswith("@"):
platform = "@" + platform
return {"//command_line_option:platforms": platform}
platform_transition = transition(
implementation = _platform_transition_impl,
inputs = [],
outputs = ["//command_line_option:platforms"],
)
# Attrs every lint-test rule needs alongside its own `targets`. Callers
# merge this dict into their `attrs = {...}`.
LINT_TEST_COMMON_ATTRS = {
"platform": attr.label(
doc = "Optional platform to transition `targets` to before running the aspect. When set, `--platforms` is switched to this label for the duration of this rule's aspect actions.",
),
"transitive": attr.bool(
doc = "If True, lint `targets` and every crate reachable via `deps`, `proc_macro_deps`, and `crate`. If False, lint only the exact targets listed.",
default = False,
),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
"_runner": attr.label(
doc = "The shared runner (prints/inspects collected marker paths).",
cfg = "exec",
executable = True,
default = Label("//rust/private/lint_test_runner"),
),
}
def lint_test_aspect_impl(target, ctx, info_provider, output_group_names):
"""Thin collector: walk deps and roll up the markers the underlying aspect produced.
Args:
target (Target): The target the aspect is running on.
ctx (ctx): The aspect's context object.
info_provider (provider): The provider type to read from deps and return
(e.g. `RustClippyTestInfo` or `RustfmtTestInfo`).
output_group_names (list): A `list` of `str` naming the `OutputGroupInfo`
fields to collect from the current target (e.g.
`["clippy_checks", "clippy_output"]` or `["rustfmt_checks"]`).
Returns:
list: A single-element list containing an `info_provider` with `direct`
(`depset[File]`) for `target` and `checks` (`depset[File]`) that
folds in every dep's `checks`.
"""
direct_depsets = []
if OutputGroupInfo in target:
og = target[OutputGroupInfo]
for name in output_group_names:
if hasattr(og, name):
direct_depsets.append(getattr(og, name))
direct = depset(transitive = direct_depsets)
transitive = [direct]
for attr_name in ("deps", "proc_macro_deps"):
for dep in getattr(ctx.rule.attr, attr_name, []):
if info_provider in dep:
transitive.append(dep[info_provider].checks)
crate_dep = getattr(ctx.rule.attr, "crate", None)
if crate_dep and info_provider in crate_dep:
transitive.append(crate_dep[info_provider].checks)
return [info_provider(
direct = direct,
checks = depset(transitive = transitive),
)]
def lint_test_rule_impl(ctx, info_provider, output_group_names):
"""Symlink the shared runner and hand it the collected marker rlocationpaths.
Args:
ctx (ctx): The rule's context object.
info_provider (provider): The provider type produced by the rule's
aspect, carrying `direct` and `checks` depsets.
output_group_names (list): A `list` of `str` naming the
`OutputGroupInfo` fields to expose the collected markers under
(e.g. `["clippy_checks", "clippy_output"]` or
`["rustfmt_checks"]`). Each name maps to the same `checks` depset.
Returns:
list: `[DefaultInfo, RunEnvironmentInfo, OutputGroupInfo]` for the
test target.
"""
is_windows = ctx.executable._runner.extension == ".exe"
runner = ctx.actions.declare_file("{}{}".format(
ctx.label.name,
".exe" if is_windows else "",
))
ctx.actions.symlink(
output = runner,
target_file = ctx.executable._runner,
is_executable = True,
)
check_depsets = []
for target in ctx.attr.targets:
if info_provider not in target:
continue
info = target[info_provider]
check_depsets.append(info.checks if ctx.attr.transitive else info.direct)
checks = depset(transitive = check_depsets)
runfiles = ctx.runfiles(transitive_files = checks).merge(
ctx.attr._runner[DefaultInfo].default_runfiles,
)
workspace_name = ctx.workspace_name
markers_env = ctx.configuration.host_path_separator.join([
rlocationpath(f, workspace_name)
for f in checks.to_list()
])
return [
DefaultInfo(
files = depset([runner]),
runfiles = runfiles,
executable = runner,
),
RunEnvironmentInfo(environment = {
"RUST_BACKTRACE": "1",
"RUST_LINT_TEST_MARKERS": markers_env,
}),
OutputGroupInfo(**{name: checks for name in output_group_names}),
]