diff --git a/Cargo.toml b/Cargo.toml index 437884990551..c717a0a231f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [package] name = "clippy" +# begin autogenerated version version = "0.1.81" +# end autogenerated version description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index be0b048ac0c7..5244aa22fdec 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,8 @@ [package] name = "clippy_config" +# begin autogenerated version version = "0.1.81" +# end autogenerated version edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 4104e7d94f14..d644c1288e17 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -6,12 +6,15 @@ edition = "2021" [dependencies] aho-corasick = "1.0" +chrono = { version = "0.4.38", default-features = false, features = ["clock"] } clap = { version = "4.4", features = ["derive"] } +directories = "5" indoc = "1.0" itertools = "0.12" opener = "0.6" shell-escape = "0.1" walkdir = "2.3" +xshell = "0.2" [features] deny-warnings = [] diff --git a/clippy_dev/src/dogfood.rs b/clippy_dev/src/dogfood.rs index a0d57f5ab483..75a4cbd2f92e 100644 --- a/clippy_dev/src/dogfood.rs +++ b/clippy_dev/src/dogfood.rs @@ -1,4 +1,4 @@ -use crate::{clippy_project_root, exit_if_err}; +use crate::utils::{clippy_project_root, exit_if_err}; use std::process::Command; /// # Panics diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 256231441817..9e0cad639cfb 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -1,4 +1,4 @@ -use crate::clippy_project_root; +use crate::utils::clippy_project_root; use itertools::Itertools; use shell_escape::escape; use std::ffi::{OsStr, OsString}; diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 3aa43dbe23ed..2341ee8f3860 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -15,69 +15,13 @@ extern crate rustc_driver; extern crate rustc_lexer; -use std::io; -use std::path::PathBuf; -use std::process::{self, ExitStatus}; - pub mod dogfood; pub mod fmt; pub mod lint; pub mod new_lint; +pub mod release; pub mod serve; pub mod setup; +pub mod sync; pub mod update_lints; - -#[cfg(not(windows))] -static CARGO_CLIPPY_EXE: &str = "cargo-clippy"; -#[cfg(windows)] -static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe"; - -/// Returns the path to the `cargo-clippy` binary -/// -/// # Panics -/// -/// Panics if the path of current executable could not be retrieved. -#[must_use] -pub fn cargo_clippy_path() -> PathBuf { - let mut path = std::env::current_exe().expect("failed to get current executable name"); - path.set_file_name(CARGO_CLIPPY_EXE); - path -} - -/// Returns the path to the Clippy project directory -/// -/// # Panics -/// -/// Panics if the current directory could not be retrieved, there was an error reading any of the -/// Cargo.toml files or ancestor directory is the clippy root directory -#[must_use] -pub fn clippy_project_root() -> PathBuf { - let current_dir = std::env::current_dir().unwrap(); - for path in current_dir.ancestors() { - let result = std::fs::read_to_string(path.join("Cargo.toml")); - if let Err(err) = &result { - if err.kind() == io::ErrorKind::NotFound { - continue; - } - } - - let content = result.unwrap(); - if content.contains("[package]\nname = \"clippy\"") { - return path.to_path_buf(); - } - } - panic!("error: Can't determine root of project. Please run inside a Clippy working dir."); -} - -/// # Panics -/// Panics if given command result was failed. -pub fn exit_if_err(status: io::Result) { - match status.expect("failed to run command").code() { - Some(0) => {}, - Some(n) => process::exit(n), - None => { - eprintln!("Killed by signal"); - process::exit(1); - }, - } -} +pub mod utils; diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs index f308f5dfdfd8..125195397e6c 100644 --- a/clippy_dev/src/lint.rs +++ b/clippy_dev/src/lint.rs @@ -1,4 +1,4 @@ -use crate::{cargo_clippy_path, exit_if_err}; +use crate::utils::{cargo_clippy_path, exit_if_err}; use std::process::{self, Command}; use std::{env, fs}; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 366b52b25dfc..058c82944b08 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -3,7 +3,7 @@ #![warn(rust_2018_idioms, unused_lifetimes)] use clap::{Args, Parser, Subcommand}; -use clippy_dev::{dogfood, fmt, lint, new_lint, serve, setup, update_lints}; +use clippy_dev::{dogfood, fmt, lint, new_lint, release, serve, setup, sync, update_lints, utils}; use std::convert::Infallible; fn main() { @@ -23,9 +23,9 @@ fn main() { if print_only { update_lints::print_lints(); } else if check { - update_lints::update(update_lints::UpdateMode::Check); + update_lints::update(utils::UpdateMode::Check); } else { - update_lints::update(update_lints::UpdateMode::Change); + update_lints::update(utils::UpdateMode::Change); } }, DevCommand::NewLint { @@ -35,7 +35,7 @@ fn main() { r#type, msrv, } => match new_lint::create(&pass, &name, &category, r#type.as_deref(), msrv) { - Ok(()) => update_lints::update(update_lints::UpdateMode::Change), + Ok(()) => update_lints::update(utils::UpdateMode::Change), Err(e) => eprintln!("Unable to create lint: {e}"), }, DevCommand::Setup(SetupCommand { subcommand }) => match subcommand { @@ -75,6 +75,19 @@ fn main() { uplift, } => update_lints::rename(&old_name, new_name.as_ref().unwrap_or(&old_name), uplift), DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, reason.as_deref()), + DevCommand::Sync(SyncCommand { subcommand }) => match subcommand { + SyncSubcommand::Pull => sync::rustc_pull(), + SyncSubcommand::Push { + repo_path, + user, + branch, + force, + } => sync::rustc_push(repo_path, &user, &branch, force), + }, + DevCommand::Release(ReleaseCommand { subcommand }) => match subcommand { + ReleaseSubcommand::BumpVersion => release::bump_version(), + ReleaseSubcommand::Commit { repo_path, branch } => release::rustc_clippy_commit(repo_path, branch), + }, } } @@ -225,6 +238,10 @@ enum DevCommand { /// The reason for deprecation reason: Option, }, + /// Sync between the rust repo and the Clippy repo + Sync(SyncCommand), + /// Manage Clippy releases + Release(ReleaseCommand), } #[derive(Args)] @@ -291,3 +308,51 @@ enum RemoveSubcommand { /// Remove the tasks added with 'cargo dev setup vscode-tasks' VscodeTasks, } + +#[derive(Args)] +struct SyncCommand { + #[command(subcommand)] + subcommand: SyncSubcommand, +} + +#[derive(Subcommand)] +enum SyncSubcommand { + /// Pull changes from rustc and update the toolchain + Pull, + /// Push changes to rustc + Push { + /// The path to a rustc repo that will be used for pushing changes + repo_path: String, + #[arg(long)] + /// The GitHub username to use for pushing changes + user: String, + #[arg(long, short, default_value = "clippy-subtree-update")] + /// The branch to push to + /// + /// This is mostly for experimentation and usually the default should be used. + branch: String, + #[arg(long, short)] + /// Force push changes + force: bool, + }, +} + +#[derive(Args)] +struct ReleaseCommand { + #[command(subcommand)] + subcommand: ReleaseSubcommand, +} + +#[derive(Subcommand)] +enum ReleaseSubcommand { + #[command(name = "bump_version")] + /// Bump the version in the Cargo.toml files + BumpVersion, + /// Print the Clippy commit in the rustc repo for the specified branch + Commit { + /// The path to a rustc repo to look for the commit + repo_path: String, + /// For which branch to print the commit + branch: release::Branch, + }, +} diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 2e56eb8ec15f..50dc1a7ec1f8 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,4 +1,4 @@ -use crate::clippy_project_root; +use crate::utils::{clippy_project_root, clippy_version}; use indoc::{formatdoc, writedoc}; use std::fmt; use std::fmt::Write as _; @@ -186,23 +186,8 @@ fn to_camel_case(name: &str) -> String { } pub(crate) fn get_stabilization_version() -> String { - fn parse_manifest(contents: &str) -> Option { - let version = contents - .lines() - .filter_map(|l| l.split_once('=')) - .find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?; - let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else { - return None; - }; - let (minor, patch) = version.split_once('.')?; - Some(format!( - "{}.{}.0", - minor.parse::().ok()?, - patch.parse::().ok()? - )) - } - let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`"); - parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`") + let (minor, patch) = clippy_version(); + format!("{minor}.{patch}.0") } fn get_test_file_contents(lint_name: &str, msrv: bool) -> String { diff --git a/clippy_dev/src/release.rs b/clippy_dev/src/release.rs new file mode 100644 index 000000000000..63f8a88bfa7d --- /dev/null +++ b/clippy_dev/src/release.rs @@ -0,0 +1,76 @@ +use std::fmt::{Display, Write}; +use std::path::Path; + +use crate::sync::PUSH_PR_DESCRIPTION; +use crate::utils::{clippy_version, replace_region_in_file, UpdateMode}; + +use clap::ValueEnum; +use xshell::{cmd, Shell}; + +const CARGO_TOML_FILES: [&str; 5] = [ + "clippy_config/Cargo.toml", + "clippy_lints/Cargo.toml", + "clippy_utils/Cargo.toml", + "declare_clippy_lint/Cargo.toml", + "Cargo.toml", +]; + +pub fn bump_version() { + let (minor, mut patch) = clippy_version(); + patch += 1; + for file in &CARGO_TOML_FILES { + replace_region_in_file( + UpdateMode::Change, + Path::new(file), + "# begin autogenerated version\n", + "# end autogenerated version", + |res| { + writeln!(res, "version = \"0.{minor}.{patch}\"").unwrap(); + }, + ); + } +} + +#[derive(ValueEnum, Copy, Clone)] +pub enum Branch { + Stable, + Beta, + Master, +} + +impl Display for Branch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Branch::Stable => write!(f, "stable"), + Branch::Beta => write!(f, "beta"), + Branch::Master => write!(f, "master"), + } + } +} + +pub fn rustc_clippy_commit(rustc_path: String, branch: Branch) { + let sh = Shell::new().expect("failed to create shell"); + sh.change_dir(rustc_path); + + let base = branch.to_string(); + cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}") + .run() + .expect("failed to fetch base commit"); + let last_rustup_commit = cmd!( + sh, + "git log -1 --merges --grep=\"{PUSH_PR_DESCRIPTION}\" FETCH_HEAD -- src/tools/clippy" + ) + .read() + .expect("failed to run git log"); + + let commit = last_rustup_commit + .lines() + .find(|c| c.contains("Sync from Clippy commit:")) + .expect("no commit found") + .trim() + .rsplit_once('@') + .expect("no commit hash found") + .1; + + println!("{commit}"); +} diff --git a/clippy_dev/src/sync.rs b/clippy_dev/src/sync.rs new file mode 100644 index 000000000000..a21d34d06d76 --- /dev/null +++ b/clippy_dev/src/sync.rs @@ -0,0 +1,239 @@ +use std::fmt::Write; +use std::path::Path; +use std::process; +use std::process::exit; + +use chrono::offset::Utc; +use xshell::{cmd, Shell}; + +use crate::utils::{clippy_project_root, replace_region_in_file, UpdateMode}; + +const JOSH_FILTER: &str = ":rev(2efebd2f0c03dabbe5c3ad7b4ebfbd99238d1fb2:prefix=src/tools/clippy):/src/tools/clippy"; +const JOSH_PORT: &str = "42042"; + +fn start_josh() -> impl Drop { + // Create a wrapper that stops it on drop. + struct Josh(process::Child); + impl Drop for Josh { + fn drop(&mut self) { + #[cfg(unix)] + { + // Try to gracefully shut it down. + process::Command::new("kill") + .args(["-s", "INT", &self.0.id().to_string()]) + .output() + .expect("failed to SIGINT josh-proxy"); + // Sadly there is no "wait with timeout"... so we just give it some time to finish. + std::thread::sleep(std::time::Duration::from_secs(1)); + // Now hopefully it is gone. + if self.0.try_wait().expect("failed to wait for josh-proxy").is_some() { + return; + } + } + // If that didn't work (or we're not on Unix), kill it hard. + eprintln!("I have to kill josh-proxy the hard way, let's hope this does not break anything."); + self.0.kill().expect("failed to SIGKILL josh-proxy"); + } + } + + // Determine cache directory. + let local_dir = { + let user_dirs = directories::ProjectDirs::from("org", "rust-lang", "clippy-josh").unwrap(); + user_dirs.cache_dir().to_owned() + }; + println!("Using local cache directory: {}", local_dir.display()); + + // Start josh, silencing its output. + let mut cmd = process::Command::new("josh-proxy"); + cmd.arg("--local").arg(local_dir); + cmd.arg("--remote").arg("https://github.com"); + cmd.arg("--port").arg(JOSH_PORT); + cmd.arg("--no-background"); + cmd.stdout(process::Stdio::null()); + cmd.stderr(process::Stdio::null()); + let josh = cmd + .spawn() + .expect("failed to start josh-proxy, make sure it is installed"); + // Give it some time so hopefully the port is open. + std::thread::sleep(std::time::Duration::from_secs(1)); + + Josh(josh) +} + +fn rustc_hash() -> String { + let sh = Shell::new().expect("failed to create shell"); + // Make sure we pick up the updated toolchain (usually rustup pins the toolchain + // inside a single cargo/rustc invocation via this env var). + sh.set_var("RUSTUP_TOOLCHAIN", ""); + cmd!(sh, "rustc --version --verbose") + .read() + .expect("failed to run `rustc -vV`") + .lines() + .find(|line| line.starts_with("commit-hash:")) + .expect("failed to parse `rustc -vV`") + .split_whitespace() + .last() + .expect("failed to get commit from `rustc -vV`") + .to_string() +} + +fn assert_clean_repo(sh: &Shell) { + if !cmd!(sh, "git status --untracked-files=no --porcelain") + .read() + .expect("failed to run git status") + .is_empty() + { + eprintln!("working directory must be clean before running `cargo dev sync pull`"); + exit(1); + } +} + +pub fn rustc_pull() { + const MERGE_COMMIT_MESSAGE: &str = "Merge from rustc"; + + let sh = Shell::new().expect("failed to create shell"); + sh.change_dir(clippy_project_root()); + + assert_clean_repo(&sh); + + // Update rust-toolchain file + let date = Utc::now().format("%Y-%m-%d").to_string(); + replace_region_in_file( + UpdateMode::Change, + Path::new("rust-toolchain"), + "# begin autogenerated version\n", + "# end autogenerated version", + |res| { + writeln!(res, "channel = \"nightly-{date}\"").unwrap(); + }, + ); + + let message = format!("Bump nightly version -> {date}"); + cmd!(sh, "git commit rust-toolchain --no-verify -m {message}") + .run() + .expect("FAILED to commit rust-toolchain file, something went wrong"); + + let commit = rustc_hash(); + + // Make sure josh is running in this scope + { + let _josh = start_josh(); + + // Fetch given rustc commit. + cmd!( + sh, + "git fetch http://localhost:{JOSH_PORT}/rust-lang/rust.git@{commit}{JOSH_FILTER}.git" + ) + .run() + .inspect_err(|_| { + // Try to un-do the previous `git commit`, to leave the repo in the state we found it. + cmd!(sh, "git reset --hard HEAD^") + .run() + .expect("FAILED to clean up again after failed `git fetch`, sorry for that"); + }) + .expect("FAILED to fetch new commits, something went wrong"); + } + + // This should not add any new root commits. So count those before and after merging. + let num_roots = || -> u32 { + cmd!(sh, "git rev-list HEAD --max-parents=0 --count") + .read() + .expect("failed to determine the number of root commits") + .parse::() + .unwrap() + }; + let num_roots_before = num_roots(); + + // Merge the fetched commit. + cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}") + .run() + .expect("FAILED to merge new commits, something went wrong"); + + // Check that the number of roots did not increase. + if num_roots() != num_roots_before { + eprintln!("Josh created a new root commit. This is probably not the history you want."); + exit(1); + } +} + +pub(crate) const PUSH_PR_DESCRIPTION: &str = "Sync from Clippy commit:"; + +pub fn rustc_push(rustc_path: String, github_user: &str, branch: &str, force: bool) { + let sh = Shell::new().expect("failed to create shell"); + sh.change_dir(clippy_project_root()); + + assert_clean_repo(&sh); + + // Prepare the branch. Pushing works much better if we use as base exactly + // the commit that we pulled from last time, so we use the `rustc --version` + // to find out which commit that would be. + let base = rustc_hash(); + + println!("Preparing {github_user}/rust (base: {base})..."); + sh.change_dir(rustc_path); + if !force + && cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}") + .ignore_stderr() + .read() + .is_ok() + { + eprintln!( + "The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again." + ); + exit(1); + } + cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}") + .run() + .expect("failed to fetch base commit"); + let force_flag = if force { "--force" } else { "" }; + cmd!( + sh, + "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch} {force_flag}" + ) + .ignore_stdout() + .ignore_stderr() // silence the "create GitHub PR" message + .run() + .expect("failed to push base commit to the new branch"); + + // Make sure josh is running in this scope + { + let _josh = start_josh(); + + // Do the actual push. + sh.change_dir(clippy_project_root()); + println!("Pushing Clippy changes..."); + cmd!( + sh, + "git push http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git HEAD:{branch}" + ) + .run() + .expect("failed to push changes to Josh"); + + // Do a round-trip check to make sure the push worked as expected. + cmd!( + sh, + "git fetch http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git {branch}" + ) + .ignore_stderr() + .read() + .expect("failed to fetch the branch from Josh"); + } + + let head = cmd!(sh, "git rev-parse HEAD") + .read() + .expect("failed to get HEAD commit"); + let fetch_head = cmd!(sh, "git rev-parse FETCH_HEAD") + .read() + .expect("failed to get FETCH_HEAD"); + if head != fetch_head { + eprintln!("Josh created a non-roundtrip push! Do NOT merge this into rustc!"); + exit(1); + } + println!("Confirmed that the push round-trips back to Clippy properly. Please create a rustc PR:"); + let description = format!("{}+rust-lang/rust-clippy@{head}", PUSH_PR_DESCRIPTION.replace(' ', "+")); + println!( + // Open PR with `subtree update` title to silence the `no-merges` triagebot check + // See https://github.com/rust-lang/rust/pull/114157 + " https://github.com/rust-lang/rust/compare/{github_user}:{branch}?quick_pull=1&title=Clippy+subtree+update&body=r?+@ghost%0A%0A{description}" + ); +} diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 45353901c98f..900706ecf9f1 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,4 +1,4 @@ -use crate::clippy_project_root; +use crate::utils::{clippy_project_root, exit_with_failure, replace_region_in_file, UpdateMode}; use aho_corasick::AhoCorasickBuilder; use indoc::writedoc; use itertools::Itertools; @@ -18,12 +18,6 @@ const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev u const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum UpdateMode { - Check, - Change, -} - /// Runs the `update_lints` command. /// /// This updates various generated values from the lint source code. @@ -547,14 +541,6 @@ fn process_file(path: impl AsRef, update_mode: UpdateMode, content: &str) } } -fn exit_with_failure() { - println!( - "Not all lints defined properly. \ - Please run `cargo dev update_lints` to make sure all lints are defined properly." - ); - std::process::exit(1); -} - /// Lint data parsed from the Clippy source code. #[derive(Clone, PartialEq, Eq, Debug)] struct Lint { @@ -936,61 +922,6 @@ fn remove_line_splices(s: &str) -> String { }); res } - -/// Replaces a region in a file delimited by two lines matching regexes. -/// -/// `path` is the relative path to the file on which you want to perform the replacement. -/// -/// See `replace_region_in_text` for documentation of the other options. -/// -/// # Panics -/// -/// Panics if the path could not read or then written -fn replace_region_in_file( - update_mode: UpdateMode, - path: &Path, - start: &str, - end: &str, - write_replacement: impl FnMut(&mut String), -) { - let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {e}", path.display())); - let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) { - Ok(x) => x, - Err(delim) => panic!("Couldn't find `{delim}` in file `{}`", path.display()), - }; - - match update_mode { - UpdateMode::Check if contents != new_contents => exit_with_failure(), - UpdateMode::Check => (), - UpdateMode::Change => { - if let Err(e) = fs::write(path, new_contents.as_bytes()) { - panic!("Cannot write to `{}`: {e}", path.display()); - } - }, - } -} - -/// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters -/// were found, or the missing delimiter if not. -fn replace_region_in_text<'a>( - text: &str, - start: &'a str, - end: &'a str, - mut write_replacement: impl FnMut(&mut String), -) -> Result { - let (text_start, rest) = text.split_once(start).ok_or(start)?; - let (_, text_end) = rest.split_once(end).ok_or(end)?; - - let mut res = String::with_capacity(text.len() + 4096); - res.push_str(text_start); - res.push_str(start); - write_replacement(&mut res); - res.push_str(end); - res.push_str(text_end); - - Ok(res) -} - 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), diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs new file mode 100644 index 000000000000..b87fcca13b1c --- /dev/null +++ b/clippy_dev/src/utils.rs @@ -0,0 +1,142 @@ +use std::path::{Path, PathBuf}; +use std::process::{self, ExitStatus}; +use std::{fs, io}; + +#[cfg(not(windows))] +static CARGO_CLIPPY_EXE: &str = "cargo-clippy"; +#[cfg(windows)] +static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe"; + +/// Returns the path to the `cargo-clippy` binary +/// +/// # Panics +/// +/// Panics if the path of current executable could not be retrieved. +#[must_use] +pub fn cargo_clippy_path() -> PathBuf { + let mut path = std::env::current_exe().expect("failed to get current executable name"); + path.set_file_name(CARGO_CLIPPY_EXE); + path +} + +/// Returns the path to the Clippy project directory +/// +/// # Panics +/// +/// Panics if the current directory could not be retrieved, there was an error reading any of the +/// Cargo.toml files or ancestor directory is the clippy root directory +#[must_use] +pub fn clippy_project_root() -> PathBuf { + let current_dir = std::env::current_dir().unwrap(); + for path in current_dir.ancestors() { + let result = fs::read_to_string(path.join("Cargo.toml")); + if let Err(err) = &result { + if err.kind() == io::ErrorKind::NotFound { + continue; + } + } + + let content = result.unwrap(); + if content.contains("[package]\nname = \"clippy\"") { + return path.to_path_buf(); + } + } + panic!("error: Can't determine root of project. Please run inside a Clippy working dir."); +} + +/// # Panics +/// Panics if given command result was failed. +pub fn exit_if_err(status: io::Result) { + match status.expect("failed to run command").code() { + Some(0) => {}, + Some(n) => process::exit(n), + None => { + eprintln!("Killed by signal"); + process::exit(1); + }, + } +} + +pub(crate) fn clippy_version() -> (u32, u32) { + fn parse_manifest(contents: &str) -> Option<(u32, u32)> { + let version = contents + .lines() + .filter_map(|l| l.split_once('=')) + .find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?; + let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else { + return None; + }; + let (minor, patch) = version.split_once('.')?; + Some((minor.parse().ok()?, patch.parse().ok()?)) + } + let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`"); + parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`") +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum UpdateMode { + Check, + Change, +} + +pub(crate) fn exit_with_failure() { + println!( + "Not all lints defined properly. \ + Please run `cargo dev update_lints` to make sure all lints are defined properly." + ); + process::exit(1); +} + +/// Replaces a region in a file delimited by two lines matching regexes. +/// +/// `path` is the relative path to the file on which you want to perform the replacement. +/// +/// See `replace_region_in_text` for documentation of the other options. +/// +/// # Panics +/// +/// Panics if the path could not read or then written +pub(crate) fn replace_region_in_file( + update_mode: UpdateMode, + path: &Path, + start: &str, + end: &str, + write_replacement: impl FnMut(&mut String), +) { + let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {e}", path.display())); + let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) { + Ok(x) => x, + Err(delim) => panic!("Couldn't find `{delim}` in file `{}`", path.display()), + }; + + match update_mode { + UpdateMode::Check if contents != new_contents => exit_with_failure(), + UpdateMode::Check => (), + UpdateMode::Change => { + if let Err(e) = fs::write(path, new_contents.as_bytes()) { + panic!("Cannot write to `{}`: {e}", path.display()); + } + }, + } +} + +/// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters +/// were found, or the missing delimiter if not. +pub(crate) fn replace_region_in_text<'a>( + text: &str, + start: &'a str, + end: &'a str, + mut write_replacement: impl FnMut(&mut String), +) -> Result { + let (text_start, rest) = text.split_once(start).ok_or(start)?; + let (_, text_end) = rest.split_once(end).ok_or(end)?; + + let mut res = String::with_capacity(text.len() + 4096); + res.push_str(text_start); + res.push_str(start); + write_replacement(&mut res); + res.push_str(end); + res.push_str(text_end); + + Ok(res) +} diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 5708ffba08fd..51b7381cfb46 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,8 @@ [package] name = "clippy_lints" +# begin autogenerated version version = "0.1.81" +# end autogenerated version description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index 6e53ff3ee6e9..186da716681b 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,8 @@ [package] name = "clippy_utils" +# begin autogenerated version version = "0.1.81" +# end autogenerated version edition = "2021" publish = false diff --git a/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml index 86d945c14a58..74b5b4930c73 100644 --- a/declare_clippy_lint/Cargo.toml +++ b/declare_clippy_lint/Cargo.toml @@ -1,6 +1,8 @@ [package] name = "declare_clippy_lint" +# begin autogenerated version version = "0.1.81" +# end autogenerated version edition = "2021" publish = false diff --git a/rust-toolchain b/rust-toolchain index 72b50d59f7e9..7cf2747b1856 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,5 @@ [toolchain] -channel = "nightly-2024-06-27" +# begin autogenerated version +channel = "nightly-2024-06-28" +# end autogenerated version components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 333a2ab58575..d4b116aa13b0 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -112,8 +112,13 @@ fn base_config(test_dir: &str) -> (Config, Args) { args.bless |= var_os("RUSTC_BLESS").is_some_and(|v| v != "0"); let target_dir = PathBuf::from(var_os("CARGO_TARGET_DIR").unwrap_or_else(|| "target".into())); + let mut skip_files = vec![]; + if IS_RUSTC_TEST_SUITE { + skip_files.push("to_string_in_format_args_incremental".to_string()); + } let mut config = Config { output_conflict_handling: OutputConflictHandling::Error, + skip_files, filter_files: env::var("TESTNAME") .map(|filters| filters.split(',').map(str::to_string).collect()) .unwrap_or_default(),