Skip to content

Commit 515f0db

Browse files
committed
Add clippy support
1 parent 9a554bf commit 515f0db

10 files changed

Lines changed: 642 additions & 373 deletions

File tree

docs/src/rust_analyzer.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ Re-runnable at any time. Global flags work on any subcommand.
7575
| `--workspace <path>` | Workspace root. Defaults to `$BUILD_WORKSPACE_DIRECTORY` (set by `bazel run`). |
7676
| `--skip-proc-macro-server` | Don't manage the proc-macro key. |
7777
| `--skip-rustfmt` | Don't manage the formatter key (use host rustfmt). |
78-
| `--per-package-workspaces` | Opt in to per-package workspace splitting (see below). |
78+
| `--per-package-workspaces` / `--no-per-package-workspaces` | Opt this developer in/out of per-package workspace splitting (see below). |
79+
| `--clippy` / `--no-clippy` | Opt this developer in/out of running clippy on save and streaming its diagnostics alongside rustc's. |
80+
| `--clean` | Delete `<launcher-dir>/cache/` before running the rest of setup. See Troubleshooting. |
81+
82+
The `--clippy` and `--per-package-workspaces` toggles are **per-user**: they mutate `<launcher-dir>/user_config.json` (gitignored) instead of the shared committed settings file. Two developers on the same workspace can hold different preferences without touching the checked-in configuration. Editing `user_config.json` by hand works too.
7983

8084
The `vscode` subcommand adds:
8185

@@ -102,14 +106,13 @@ The `vscode` subcommand adds:
102106
### Symbols / deps look wrong
103107

104108
Restart rust-analyzer (or save a `BUILD` file). If that doesn't fix
105-
it, clear the discovery cache and try again:
109+
it, re-run setup with `--clean` to nuke the discovery cache:
106110

107111
```
108-
rm -rf <workspace>/<editor-dir>/.rules_rust_analyzer/cache
112+
bazel run @rules_rust//tools/rust_analyzer:setup -- --clean vscode
109113
```
110114

111-
`<editor-dir>` is `.vscode` for VSCode, `.helix` for Helix, or empty
112-
for Neovim / `print`.
115+
Works with any subcommand (`vscode` / `neovim` / `helix` / `print`).
113116

114117
### Diagnostics stopped appearing
115118

@@ -119,6 +122,25 @@ Check `<workspace>/.rules_rust_analyzer/flycheck.log`.
119122

120123
Re-run `setup`.
121124

125+
### Noisy `cargo metadata` errors on startup
126+
127+
`setup` does not manage `rust-analyzer.files.excludeDirs`. If your
128+
workspace has stub `Cargo.toml` files that aren't meant to be
129+
auto-loaded (common in `rules_rust` itself under `examples/`,
130+
`crate_universe/`, etc.), rust-analyzer still finds them and logs
131+
errors. Silence them by adding the directory names to `settings.json`
132+
yourself — your entries survive future `setup` runs:
133+
134+
```
135+
"rust-analyzer.files.excludeDirs": ["examples", "some_other_dir"]
136+
```
137+
138+
Trade-off: `files.excludeDirs` also hides those sources from
139+
rust-analyzer's virtual filesystem, so files under those directories
140+
won't get IDE features even if they're part of a Bazel-discovered
141+
crate. Only exclude directories whose sources you're willing to lose
142+
IDE support on.
143+
122144
## Workspace splitting
123145

124146
By default the whole project is treated as a single workspace.

tools/rust_analyzer/bep.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ pub const SPEC_OUTPUT_GROUP: &str = "rust_analyzer_crate_spec";
2727
/// [`generate_output_diagnostics`] in `rust/private/utils.bzl`.
2828
pub const RUSTC_OUTPUT_GROUP: &str = "rustc_output";
2929

30+
/// Output group clippy-emitted diagnostics land in when
31+
/// `--@rules_rust//rust/settings:clippy_output_diagnostics=true` is set,
32+
/// under the `rust_clippy_aspect`. Each entry is a per-crate
33+
/// `.clippy.diagnostics` file. See `rust/private/clippy.bzl`.
34+
pub const CLIPPY_OUTPUT_GROUP: &str = "clippy_output";
35+
3036
#[derive(Debug, Deserialize)]
3137
#[serde(rename_all = "camelCase")]
3238
struct BuildEvent {

tools/rust_analyzer/bin/discover_rust_project.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use camino::{Utf8Path, Utf8PathBuf};
1212
use clap::Parser;
1313
use env_logger::{fmt::Formatter, Target, WriteStyle};
1414
use gen_rust_project_lib::{
15-
bazel_info, generate_rust_project, DiscoverProject, RustAnalyzerArg, BUILD_FILE_NAMES,
16-
WORKSPACE_ROOT_FILE_NAMES,
15+
bazel_info, generate_rust_project, install_dir, user_config, DiscoverProject, RustAnalyzerArg,
16+
BUILD_FILE_NAMES, WORKSPACE_ROOT_FILE_NAMES,
1717
};
1818
use log::{LevelFilter, Record};
1919

@@ -42,11 +42,32 @@ fn project_discovery() -> anyhow::Result<DiscoverProject<'static>> {
4242
rust_analyzer_argument,
4343
} = Config::parse()?;
4444

45+
// Per-user, per-workspace preferences. Rendered
46+
// `discoverConfig.command` is intentionally identical for every
47+
// developer — clippy and per-package-workspaces toggles live in
48+
// `<launcher_dir>/user_config.json` (see `user_config.rs`).
49+
let user = user_config::load(&install_dir()?);
50+
log::info!(
51+
"user config: clippy={} per_package_workspaces={}",
52+
user.clippy,
53+
user.per_package_workspaces
54+
);
55+
4556
log::info!("got rust-analyzer argument: {rust_analyzer_argument:?}");
4657

47-
let ra_arg = match rust_analyzer_argument {
48-
Some(ra_arg) => ra_arg,
49-
None => RustAnalyzerArg::Buildfile(find_workspace_root_file(&workspace)?),
58+
// When per-package-workspaces is off, the rust-analyzer-provided
59+
// arg is discarded and we always emit the whole-workspace project.
60+
// The rendered `{arg}` template stays in the discover command
61+
// regardless — this keeps the shared settings byte-identical, at
62+
// the cost of an occasional cache-hit-only re-invocation of
63+
// discover on file open.
64+
let ra_arg = if user.per_package_workspaces {
65+
match rust_analyzer_argument {
66+
Some(ra_arg) => ra_arg,
67+
None => RustAnalyzerArg::Buildfile(find_workspace_root_file(&workspace)?),
68+
}
69+
} else {
70+
RustAnalyzerArg::Buildfile(find_workspace_root_file(&workspace)?)
5071
};
5172

5273
let rules_rust_name = env!("ASPECT_REPOSITORY");
@@ -68,6 +89,7 @@ fn project_discovery() -> anyhow::Result<DiscoverProject<'static>> {
6889
&bazel_args,
6990
rules_rust_name,
7091
&[targets],
92+
user.clippy,
7193
)?;
7294

7395
Ok(DiscoverProject::Finished { buildfile, project })

tools/rust_analyzer/bin/flycheck.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use std::{
2727
use anyhow::{Context, Result};
2828
use camino::{Utf8Path, Utf8PathBuf};
2929
use clap::Parser;
30-
use gen_rust_project_lib::bep;
30+
use gen_rust_project_lib::{bep, install_dir, user_config};
3131
use serde_json::Value;
3232

3333
#[derive(Parser, Debug)]
@@ -87,8 +87,16 @@ fn run() -> Result<u8> {
8787
std::fs::create_dir_all(&output_user_root)
8888
.with_context(|| format!("creating output_user_root {output_user_root}"))?;
8989

90-
let status = Command::new(args.bazel.as_str())
91-
.current_dir(&workspace)
90+
// Per-user preferences live in `<launcher_dir>/user_config.json`.
91+
// Clippy mode is a per-user opt-in there — the shared discover
92+
// command doesn't decide it — so we consult the config on every
93+
// save rather than baking the choice into flycheck's argv.
94+
let user = user_config::load(&install_dir()?);
95+
96+
// Assemble the bazel command. Clippy mode adds the aspect and
97+
// its diagnostics output group on top of the base build flags.
98+
let mut cmd = Command::new(args.bazel.as_str());
99+
cmd.current_dir(&workspace)
92100
// BUILD_WORKSPACE_DIRECTORY / BUILD_WORKING_DIRECTORY leak in from
93101
// the outer `bazel run` invocation and would confuse the nested
94102
// bazel client; clear them so the nested call rediscovers the
@@ -109,19 +117,39 @@ fn run() -> Result<u8> {
109117
.arg("--@rules_rust//rust/settings:rustc_output_diagnostics=true")
110118
.arg(format!("--output_groups=+{}", bep::RUSTC_OUTPUT_GROUP))
111119
.arg("--keep_going")
112-
.arg(format!("--build_event_json_file={bep_path}"))
120+
.arg(format!("--build_event_json_file={bep_path}"));
121+
if user.clippy {
122+
cmd.arg("--aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect")
123+
// Diagnostics go to a declared `.clippy.diagnostics` file per
124+
// crate, exposed via the `clippy_output` output group. Without
125+
// this flag the aspect only emits a marker file, so we'd have
126+
// nowhere to read clippy JSON from.
127+
.arg("--@rules_rust//rust/settings:clippy_output_diagnostics=true")
128+
.arg(format!("--output_groups=+{}", bep::CLIPPY_OUTPUT_GROUP));
129+
}
130+
let status = cmd
113131
.status()
114132
.with_context(|| format!("invoking {}", args.bazel))?;
115133

116-
let stderr_files = match bep::parse_action_stderr_paths(&bep_path) {
134+
let mut diagnostic_files = match bep::parse_action_stderr_paths(&bep_path) {
117135
Ok(paths) => paths,
118136
Err(e) => {
119137
eprintln!("flycheck: parsing BEP failed: {e:#}");
120138
Vec::new()
121139
}
122140
};
141+
if user.clippy {
142+
// Additive: the `clippy_output` group holds `.clippy.diagnostics`
143+
// files that action-stderr harvesting doesn't cover (clippy's JSON
144+
// goes to the declared file, not stderr, when
145+
// `clippy_output_diagnostics=true`).
146+
match bep::parse_output_group_paths(&bep_path, bep::CLIPPY_OUTPUT_GROUP) {
147+
Ok(paths) => diagnostic_files.extend(paths),
148+
Err(e) => eprintln!("flycheck: parsing clippy_output group failed: {e:#}"),
149+
}
150+
}
123151

124-
emit_diagnostics(&stderr_files, &workspace)?;
152+
emit_diagnostics(&diagnostic_files, &workspace)?;
125153

126154
// Forward Bazel's exit code so rust-analyzer can tell apart "build
127155
// succeeded with diagnostics" from "build tool itself broke".

tools/rust_analyzer/bin/gen_rust_project.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ fn write_rust_project() -> anyhow::Result<()> {
3030
&bazel_args,
3131
rules_rust_name,
3232
&targets,
33+
false,
3334
)?;
3435

3536
let rust_project_path = &workspace.join("rust-project.json");

0 commit comments

Comments
 (0)