Misc clippy_dev changes (#14896)

changelog: none
This commit is contained in:
Jason Newcomb 2025-08-17 11:26:46 +00:00 committed by GitHub
commit fc42a205e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 160 additions and 183 deletions

View file

@ -1,35 +1,28 @@
use crate::utils::exit_if_err;
use std::process::Command;
use crate::utils::{cargo_cmd, run_exit_on_err};
use itertools::Itertools;
/// # Panics
///
/// Panics if unable to run the dogfood test
#[allow(clippy::fn_params_excessive_bools)]
pub fn dogfood(fix: bool, allow_dirty: bool, allow_staged: bool, allow_no_vcs: bool) {
let mut cmd = Command::new("cargo");
cmd.args(["test", "--test", "dogfood"])
.args(["--features", "internal"])
.args(["--", "dogfood_clippy", "--nocapture"]);
let mut dogfood_args = Vec::new();
if fix {
dogfood_args.push("--fix");
}
if allow_dirty {
dogfood_args.push("--allow-dirty");
}
if allow_staged {
dogfood_args.push("--allow-staged");
}
if allow_no_vcs {
dogfood_args.push("--allow-no-vcs");
}
cmd.env("__CLIPPY_DOGFOOD_ARGS", dogfood_args.join(" "));
exit_if_err(cmd.status());
run_exit_on_err(
"cargo test",
cargo_cmd()
.args(["test", "--test", "dogfood"])
.args(["--features", "internal"])
.args(["--", "dogfood_clippy", "--nocapture"])
.env(
"__CLIPPY_DOGFOOD_ARGS",
[
if fix { "--fix" } else { "" },
if allow_dirty { "--allow-dirty" } else { "" },
if allow_staged { "--allow-staged" } else { "" },
if allow_no_vcs { "--allow-no-vcs" } else { "" },
]
.iter()
.filter(|x| !x.is_empty())
.join(" "),
),
);
}

View file

@ -15,8 +15,7 @@
)]
#![allow(clippy::missing_panics_doc)]
// The `rustc_driver` crate seems to be required in order to use the `rust_lexer` crate.
#[allow(unused_extern_crates)]
#[expect(unused_extern_crates, reason = "required to link to rustc crates")]
extern crate rustc_driver;
extern crate rustc_lexer;
extern crate rustc_literal_escaper;
@ -32,4 +31,6 @@ pub mod serve;
pub mod setup;
pub mod sync;
pub mod update_lints;
pub mod utils;
mod utils;
pub use utils::{ClippyInfo, UpdateMode};

View file

@ -1,19 +1,18 @@
use crate::utils::{cargo_clippy_path, exit_if_err};
use std::process::{self, Command};
use crate::utils::{ErrAction, cargo_cmd, expect_action, run_exit_on_err};
use std::process::Command;
use std::{env, fs};
pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>) {
let is_file = match fs::metadata(path) {
Ok(metadata) => metadata.is_file(),
Err(e) => {
eprintln!("Failed to read {path}: {e:?}");
process::exit(1);
},
};
#[cfg(not(windows))]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
#[cfg(windows)]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>) {
let is_file = expect_action(fs::metadata(path), ErrAction::Read, path).is_file();
if is_file {
exit_if_err(
Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
run_exit_on_err(
"cargo run",
cargo_cmd()
.args(["run", "--bin", "clippy-driver", "--"])
.args(["-L", "./target/debug"])
.args(["-Z", "no-codegen"])
@ -21,24 +20,25 @@ pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>
.arg(path)
.args(args)
// Prevent rustc from creating `rustc-ice-*` files the console output is enough.
.env("RUSTC_ICE", "0")
.status(),
.env("RUSTC_ICE", "0"),
);
} else {
exit_if_err(
Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
.arg("build")
.status(),
// Ideally this would just be `cargo run`, but the working directory needs to be
// set to clippy's directory when building, and the target project's directory
// when running clippy. `cargo` can only set a single working directory for both
// when using `run`.
run_exit_on_err("cargo build", cargo_cmd().arg("build"));
let mut exe = env::current_exe().expect("failed to get current executable name");
exe.set_file_name(CARGO_CLIPPY_EXE);
run_exit_on_err(
"cargo clippy",
Command::new(exe)
.arg("clippy")
.args(args)
// Prevent rustc from creating `rustc-ice-*` files the console output is enough.
.env("RUSTC_ICE", "0")
.current_dir(path),
);
let status = Command::new(cargo_clippy_path())
.arg("clippy")
.args(args)
// Prevent rustc from creating `rustc-ice-*` files the console output is enough.
.env("RUSTC_ICE", "0")
.current_dir(path)
.status();
exit_if_err(status);
}
}

View file

@ -4,14 +4,15 @@
use clap::{Args, Parser, Subcommand};
use clippy_dev::{
deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, update_lints, utils,
ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync,
update_lints,
};
use std::convert::Infallible;
use std::env;
fn main() {
let dev = Dev::parse();
let clippy = utils::ClippyInfo::search_for_manifest();
let clippy = ClippyInfo::search_for_manifest();
if let Err(e) = env::set_current_dir(&clippy.path) {
panic!("error setting current directory to `{}`: {e}", clippy.path.display());
}
@ -26,8 +27,8 @@ fn main() {
allow_staged,
allow_no_vcs,
} => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs),
DevCommand::Fmt { check } => fmt::run(utils::UpdateMode::from_check(check)),
DevCommand::UpdateLints { check } => update_lints::update(utils::UpdateMode::from_check(check)),
DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)),
DevCommand::UpdateLints { check } => update_lints::update(UpdateMode::from_check(check)),
DevCommand::NewLint {
pass,
name,
@ -35,7 +36,7 @@ fn main() {
r#type,
msrv,
} => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) {
Ok(()) => update_lints::update(utils::UpdateMode::Change),
Ok(()) => update_lints::update(UpdateMode::Change),
Err(e) => eprintln!("Unable to create lint: {e}"),
},
DevCommand::Setup(SetupCommand { subcommand }) => match subcommand {

View file

@ -1,7 +1,11 @@
use crate::utils::{ErrAction, cargo_cmd, expect_action};
use core::fmt::Display;
use core::mem;
use std::path::Path;
use std::process::Command;
use std::time::{Duration, SystemTime};
use std::{env, thread};
use std::{fs, thread};
use walkdir::WalkDir;
#[cfg(windows)]
const PYTHON: &str = "python";
@ -18,56 +22,83 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
Some(lint) => format!("http://localhost:{port}/#{lint}"),
});
let mut last_update = mtime("util/gh-pages/index.html");
loop {
let index_time = mtime("util/gh-pages/index.html");
let times = [
"clippy_lints/src",
"util/gh-pages/index_template.html",
"tests/compile-test.rs",
]
.map(mtime);
if times.iter().any(|&time| index_time < time) {
Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
.arg("collect-metadata")
.spawn()
.unwrap()
.wait()
.unwrap();
if is_metadata_outdated(mem::replace(&mut last_update, SystemTime::now())) {
// Ignore the command result; we'll fall back to displaying the old metadata.
let _ = expect_action(
cargo_cmd().arg("collect-metadata").status(),
ErrAction::Run,
"cargo collect-metadata",
);
last_update = SystemTime::now();
}
// Only start the web server the first time around.
if let Some(url) = url.take() {
thread::spawn(move || {
let mut child = Command::new(PYTHON)
.arg("-m")
.arg("http.server")
.arg(port.to_string())
.current_dir("util/gh-pages")
.spawn()
.unwrap();
let mut child = expect_action(
Command::new(PYTHON)
.args(["-m", "http.server", port.to_string().as_str()])
.current_dir("util/gh-pages")
.spawn(),
ErrAction::Run,
"python -m http.server",
);
// Give some time for python to start
thread::sleep(Duration::from_millis(500));
// Launch browser after first export.py has completed and http.server is up
let _result = opener::open(url);
child.wait().unwrap();
expect_action(child.wait(), ErrAction::Run, "python -m http.server");
});
}
// Delay to avoid updating the metadata too aggressively.
thread::sleep(Duration::from_millis(1000));
}
}
fn mtime(path: impl AsRef<Path>) -> SystemTime {
let path = path.as_ref();
if path.is_dir() {
path.read_dir()
.into_iter()
.flatten()
.flatten()
.map(|entry| mtime(entry.path()))
.max()
.unwrap_or(SystemTime::UNIX_EPOCH)
} else {
path.metadata()
.and_then(|metadata| metadata.modified())
.unwrap_or(SystemTime::UNIX_EPOCH)
fn log_err_and_continue<T>(res: Result<T, impl Display>, path: &Path) -> Option<T> {
match res {
Ok(x) => Some(x),
Err(ref e) => {
eprintln!("error reading `{}`: {e}", path.display());
None
},
}
}
fn mtime(path: &str) -> SystemTime {
log_err_and_continue(fs::metadata(path), path.as_ref())
.and_then(|metadata| log_err_and_continue(metadata.modified(), path.as_ref()))
.unwrap_or(SystemTime::UNIX_EPOCH)
}
fn is_metadata_outdated(time: SystemTime) -> bool {
// Ignore all IO errors here. We don't want to stop them from hosting the server.
if time < mtime("util/gh-pages/index_template.html") || time < mtime("tests/compile-test.rs") {
return true;
}
let Some(dir) = log_err_and_continue(fs::read_dir("."), ".".as_ref()) else {
return false;
};
dir.map_while(|e| log_err_and_continue(e, ".".as_ref())).any(|e| {
let name = e.file_name();
let name_bytes = name.as_encoded_bytes();
if (name_bytes.starts_with(b"clippy_lints") && name_bytes != b"clippy_lints_internal")
|| name_bytes == b"clippy_config"
{
WalkDir::new(&name)
.into_iter()
.map_while(|e| log_err_and_continue(e, name.as_ref()))
.filter(|e| e.file_type().is_file())
.filter_map(|e| {
log_err_and_continue(e.metadata(), e.path())
.and_then(|m| log_err_and_continue(m.modified(), e.path()))
})
.any(|ftime| time < ftime)
} else {
false
}
})
}

View file

@ -1,8 +1,6 @@
use std::fs;
use std::path::Path;
use super::verify_inside_clippy_dir;
/// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo.
/// I've decided against this for the sake of simplicity and to make sure that it doesn't install
/// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool
@ -35,10 +33,6 @@ pub fn install_hook(force_override: bool) {
}
fn check_precondition(force_override: bool) -> bool {
if !verify_inside_clippy_dir() {
return false;
}
// Make sure that we can find the git repository
let git_path = Path::new(REPO_GIT_DIR);
if !git_path.exists() || !git_path.is_dir() {

View file

@ -2,23 +2,3 @@ pub mod git_hook;
pub mod intellij;
pub mod toolchain;
pub mod vscode;
use std::path::Path;
const CLIPPY_DEV_DIR: &str = "clippy_dev";
/// This function verifies that the tool is being executed in the clippy directory.
/// This is useful to ensure that setups only modify Clippy's resources. The verification
/// is done by checking that `clippy_dev` is a sub directory of the current directory.
///
/// It will print an error message and return `false` if the directory could not be
/// verified.
fn verify_inside_clippy_dir() -> bool {
let path = Path::new(CLIPPY_DEV_DIR);
if path.exists() && path.is_dir() {
true
} else {
eprintln!("error: unable to verify that the working directory is clippy's directory");
false
}
}

View file

@ -1,20 +1,12 @@
use crate::utils::{cargo_cmd, run_exit_on_err};
use std::env::consts::EXE_SUFFIX;
use std::env::current_dir;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use walkdir::WalkDir;
use crate::utils::exit_if_err;
use super::verify_inside_clippy_dir;
pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
if !verify_inside_clippy_dir() {
return;
}
let rustup_home = std::env::var("RUSTUP_HOME").unwrap();
let toolchain = std::env::var("RUSTUP_TOOLCHAIN").unwrap();
@ -51,11 +43,10 @@ pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
}
}
let status = Command::new("cargo")
.arg("build")
.args(release.then_some("--release"))
.status();
exit_if_err(status);
run_exit_on_err(
"cargo build",
cargo_cmd().arg("build").args(release.then_some("--release")),
);
install_bin("cargo-clippy", &dest, standalone, release);
install_bin("clippy-driver", &dest, standalone, release);

View file

@ -1,8 +1,6 @@
use std::fs;
use std::path::Path;
use super::verify_inside_clippy_dir;
const VSCODE_DIR: &str = ".vscode";
const TASK_SOURCE_FILE: &str = "util/etc/vscode-tasks.json";
const TASK_TARGET_FILE: &str = ".vscode/tasks.json";
@ -22,10 +20,6 @@ pub fn install_tasks(force_override: bool) {
}
fn check_install_precondition(force_override: bool) -> bool {
if !verify_inside_clippy_dir() {
return false;
}
let vs_dir_path = Path::new(VSCODE_DIR);
if vs_dir_path.exists() {
// verify the target will be valid

View file

@ -8,15 +8,10 @@ use std::ffi::OsStr;
use std::fs::{self, OpenOptions};
use std::io::{self, Read as _, Seek as _, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::process::{self, Command, ExitStatus, Stdio};
use std::process::{self, Command, Stdio};
use std::{env, thread};
use walkdir::WalkDir;
#[cfg(not(windows))]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
#[cfg(windows)]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
#[derive(Clone, Copy)]
pub enum ErrAction {
Open,
@ -118,16 +113,14 @@ impl<'a> File<'a> {
}
}
/// Returns the path to the `cargo-clippy` binary
///
/// # Panics
///
/// Panics if the path of current executable could not be retrieved.
/// Creates a `Command` for running cargo.
#[must_use]
pub fn cargo_clippy_path() -> PathBuf {
let mut path = env::current_exe().expect("failed to get current executable name");
path.set_file_name(CARGO_CLIPPY_EXE);
path
pub fn cargo_cmd() -> Command {
if let Some(path) = env::var_os("CARGO") {
Command::new(path)
} else {
Command::new("cargo")
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@ -288,19 +281,6 @@ impl ClippyInfo {
}
}
/// # Panics
/// Panics if given command result was failed.
pub fn exit_if_err(status: io::Result<ExitStatus>) {
match status.expect("failed to run command").code() {
Some(0) => {},
Some(n) => process::exit(n),
None => {
eprintln!("Killed by signal");
process::exit(1);
},
}
}
#[derive(Clone, Copy)]
pub enum UpdateStatus {
Unchanged,
@ -341,6 +321,7 @@ pub struct FileUpdater {
dst_buf: String,
}
impl FileUpdater {
#[track_caller]
fn update_file_checked_inner(
&mut self,
tool: &str,
@ -364,6 +345,7 @@ impl FileUpdater {
}
}
#[track_caller]
fn update_file_inner(&mut self, path: &Path, update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus) {
let mut file = File::open(path, OpenOptions::new().read(true).write(true));
file.read_to_cleared_string(&mut self.src_buf);
@ -373,6 +355,7 @@ impl FileUpdater {
}
}
#[track_caller]
pub fn update_file_checked(
&mut self,
tool: &str,
@ -383,6 +366,7 @@ impl FileUpdater {
self.update_file_checked_inner(tool, mode, path.as_ref(), update);
}
#[track_caller]
pub fn update_file(
&mut self,
path: impl AsRef<Path>,
@ -450,7 +434,6 @@ pub enum Token<'a> {
OpenParen,
Pound,
Semi,
Slash,
}
pub struct RustSearcher<'txt> {
@ -528,7 +511,6 @@ impl<'txt> RustSearcher<'txt> {
| (Token::OpenParen, lexer::TokenKind::OpenParen)
| (Token::Pound, lexer::TokenKind::Pound)
| (Token::Semi, lexer::TokenKind::Semi)
| (Token::Slash, lexer::TokenKind::Slash)
| (
Token::LitStr,
lexer::TokenKind::Literal {
@ -601,7 +583,7 @@ impl<'txt> RustSearcher<'txt> {
}
}
#[expect(clippy::must_use_candidate)]
#[track_caller]
pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
match OpenOptions::new().create_new(true).write(true).open(new_name) {
Ok(file) => drop(file),
@ -623,7 +605,7 @@ pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
}
}
#[expect(clippy::must_use_candidate)]
#[track_caller]
pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
match fs::create_dir(new_name) {
Ok(()) => {},
@ -649,10 +631,19 @@ pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
}
}
pub fn write_file(path: &Path, contents: &str) {
expect_action(fs::write(path, contents), ErrAction::Write, path);
#[track_caller]
pub fn run_exit_on_err(path: &(impl AsRef<Path> + ?Sized), cmd: &mut Command) {
match expect_action(cmd.status(), ErrAction::Run, path.as_ref()).code() {
Some(0) => {},
Some(n) => process::exit(n),
None => {
eprintln!("{} killed by signal", path.as_ref().display());
process::exit(1);
},
}
}
#[track_caller]
#[must_use]
pub fn run_with_output(path: &(impl AsRef<Path> + ?Sized), cmd: &mut Command) -> Vec<u8> {
fn f(path: &Path, cmd: &mut Command) -> Vec<u8> {
@ -738,7 +729,7 @@ pub fn split_args_for_threads(
}
}
#[expect(clippy::must_use_candidate)]
#[track_caller]
pub fn delete_file_if_exists(path: &Path) -> bool {
match fs::remove_file(path) {
Ok(()) => true,
@ -747,6 +738,7 @@ pub fn delete_file_if_exists(path: &Path) -> bool {
}
}
#[track_caller]
pub fn delete_dir_if_exists(path: &Path) {
match fs::remove_dir_all(path) {
Ok(()) => {},