Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
build:clippy_settings --@rules_rust//rust/settings:rustc_output_diagnostics=true
build:clippy_settings --@rules_rust//rust/settings:clippy_output_diagnostics=true
build --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect
build --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect
build --aspects=//tools/rustfix:aspect.bzl%rustfix_aspect

build --@rules_rust//rust/settings:rustc_output_diagnostics=true
build --@rules_rust//rust/settings:clippy_output_diagnostics=true
build --@rules_rust//rust/settings:rustfmt.toml=//:rustfmt.toml

build:clippy_diag --@rules_rust//rust/settings:error_format=json
build:clippy_diag --@rules_rust//rust/settings:capture_clippy_output=true
build:clippy_diag --extra_toolchains=//toolchain:local_rust_stable_clippy_no_fail

build:clippy --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect
build:clippy --output_groups=clippy_checks
build:clippy --config=clippy_settings

build:rustfmt --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect
build:rustfmt --@rules_rust//rust/settings:rustfmt.toml=//:rustfmt.toml
build:rustfmt --output_groups=rustfmt_checks

# https://github.com/bazelbuild/bazel/issues/15516
# Disable persistent action cache with --use_action_cache=false. Only disk and
# remote cache are be disabled by the no-cache tag.
build:rustfix --use_action_cache=false
build:rustfix --config=clippy_diag
build:rustfix --output_groups=rustfix

test --test_output=errors
8 changes: 8 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ clippy_no_fail_toolchain_tools(
name = "local_clippy_no_fail_toolchain_tools",
)

crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate")
crate.spec(
package = "rustfix",
version = "0.9.4",
)
crate.from_specs(name = "crates")
use_repo(crate, "crates")

bazel_dep(
name = "buildifier_prebuilt",
version = "8.2.0.2",
Expand Down
369 changes: 369 additions & 0 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

38 changes: 21 additions & 17 deletions tools/rust-analyzer/aspect.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,36 @@ which outputs clippy lints to stdout in json format
"""

load("@rules_rust//rust:defs.bzl", "rust_clippy_aspect")
load("@rules_rust//rust:rust_common.bzl", _RUST_COMMON_PROVIDERS = "COMMON_PROVIDERS")

def _clippy_stdout_aspect_impl(target, ctx):
phony_files = []

if OutputGroupInfo in target:
clippy_output = getattr(target[OutputGroupInfo], "clippy_output", depset())
for f in clippy_output.to_list():
if f.path.endswith(".clippy.diagnostics"):
phony = ctx.actions.declare_file(f.basename + ".clippy.phony")
ctx.actions.run_shell(
inputs = [f],
outputs = [phony],
command = """\
(grep -q '{pattern}' {diag} && cat {diag}) || touch {phony}
""".format(
diag = f.path,
pattern = '^{"$message_type":"diagnostic",',
phony = phony.path,
),
)
phony_files.append(phony)
clippy_output = target[OutputGroupInfo].clippy_output
for f in clippy_output.to_list():
if not f.path.endswith(".clippy.diagnostics"):
fail("clippy_output files should end with '.clippy.diagnostics'")

phony = ctx.actions.declare_file(f.basename + ".clippy.phony")
ctx.actions.run_shell(
inputs = [f],
outputs = [phony],
command = """\
(gre '{pattern}' {diag} && cat {diag}) || touch {phony}
""".format(
diag = f.path,
pattern = '^{"$message_type":"diagnostic",',
phony = phony.path,
),
)
phony_files.append(phony)

return [OutputGroupInfo(clippy_stdout = depset(phony_files))]

clippy_stdout_aspect = aspect(
implementation = _clippy_stdout_aspect_impl,
requires = [rust_clippy_aspect],
attr_aspects = ["deps", "proc_macro_deps"],
required_aspect_providers = [OutputGroupInfo],
required_providers = _RUST_COMMON_PROVIDERS,
)
6 changes: 1 addition & 5 deletions tools/rust-analyzer/rust-analyzer-check.bash
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ fi
bazelisk \
"--output_base=${output_base}" \
build \
--extra_toolchains=//toolchain:local_rust_stable_clippy_no_fail \
--aspects=//tools/rust-analyzer:aspect.bzl%clippy_stdout_aspect \
--@rules_rust//rust/settings:error_format=json \
--@rules_rust//rust/settings:capture_clippy_output=true \
--output_groups=clippy_stdout \
--config=clippy_settings \
--config=clippy_diag \
--keep_going \
-- "$label" || true
9 changes: 9 additions & 0 deletions tools/rustfix/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
load("@rules_rust//rust:defs.bzl", "rust_binary")

rust_binary(
name = "rustfix",
srcs = ["main.rs"],
edition = "2024",
tags = ["manual"],
deps = ["@crates//:rustfix"],
)
61 changes: 61 additions & 0 deletions tools/rustfix/aspect.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
aspect to apply clippy fixes
"""

load("@rules_rust//rust:defs.bzl", "rust_clippy_aspect")
load("@rules_rust//rust:rust_common.bzl", _RUST_COMMON_PROVIDERS = "COMMON_PROVIDERS")

def _rustfix_aspect_impl(target, ctx):
phony = ctx.actions.declare_file(ctx.label.name + ".rustfix.phony")

diagnostics_files = []

clippy_output = target[OutputGroupInfo].clippy_output
for f in clippy_output.to_list():
if not f.path.endswith(".clippy.diagnostics"):
fail("clippy_output files should end with '.clippy.diagnostics'")
diagnostics_files.append(f)

args = {
"command": "touch {phony}".format(phony = phony.path),
}
if diagnostics_files:
args = {
"command": (
"{rustfix} {diags} && ".format(
rustfix = ctx.executable._rustfix.path,
diags = " ".join([f.path for f in diagnostics_files]),
) + args["command"]
),
"use_default_shell_env": True,
"execution_requirements": {
"no-sandbox": "1",
"no-cache": "1",
},
}

ctx.actions.run_shell(
arguments = [f.path for f in diagnostics_files],
inputs = diagnostics_files + ctx.rule.files.srcs + (
[ctx.executable._rustfix] if diagnostics_files else []
),
outputs = [phony],
**args
)

return [OutputGroupInfo(rustfix = depset([phony]))]

rustfix_aspect = aspect(
implementation = _rustfix_aspect_impl,
attrs = {
"_rustfix": attr.label(
default = "//tools/rustfix",
executable = True,
cfg = "exec",
),
},
requires = [rust_clippy_aspect],
attr_aspects = ["deps", "proc_macro_deps"],
required_aspect_providers = [OutputGroupInfo],
required_providers = _RUST_COMMON_PROVIDERS,
)
46 changes: 46 additions & 0 deletions tools/rustfix/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::{
collections::{HashMap, HashSet},
fs,
};

use rustfix::{CodeFix, Filter, get_suggestions_from_json};

fn main() {
for diagnostics_path in std::env::args().skip(1) {
let json =
fs::read_to_string(&diagnostics_path).unwrap_or_else(|e| panic!("failed to read {diagnostics_path}: {e}"));

let diagnostics: String = json
.lines()
.filter(|line| line.contains(r#""$message_type":"diagnostic""#))
.collect::<Vec<_>>()
.join("\n");

if diagnostics.is_empty() {
continue;
}

let suggestions =
get_suggestions_from_json(&diagnostics, &HashSet::<String>::new(), Filter::MachineApplicableOnly)
.unwrap_or_else(|e| panic!("failed to parse {diagnostics_path}: {e}"));

let mut files: HashMap<String, Vec<_>> = HashMap::new();
for suggestion in suggestions {
let file = suggestion.solutions[0].replacements[0].snippet.file_name.clone();
files.entry(file).or_default().push(suggestion);
}

for (source_file, suggestions) in &files {
let source =
fs::read_to_string(source_file).unwrap_or_else(|e| panic!("failed to read {source_file}: {e}"));
let mut fix = CodeFix::new(&source);
for suggestion in suggestions.iter().rev() {
if let Err(e) = fix.apply(suggestion) {
eprintln!("skipping suggestion in {source_file}: {e}");
}
}
fs::write(source_file, fix.finish().expect("failed to apply fixes"))
.unwrap_or_else(|e| panic!("failed to write {source_file}: {e}"));
}
}
}
Loading