Sync and Release automation (#13694)

Based on https://github.com/rust-lang/rust-clippy/pull/13693

Adds 2 subcommands to `cargo dev`:

- `cargo dev sync update_nightly`: Which updates the nightly versions in
`rust-toolchain` and `clippy_utils/README.md`
- `cargo dev release bump_version`: Bumps the version in all relevant
`Cargo.toml` files

Those are pulled out of
https://github.com/rust-lang/rust-clippy/pull/12759, which I'll rebase
on this.

Next step is to update the documentation, which I'll partly pull out of
https://github.com/rust-lang/rust-clippy/pull/12762

r? @blyxyas (as you reviewed the first PR in the chain and were assigned
to the second one)

cc https://github.com/rust-lang/rust-clippy/issues/13556

changelog: none
This commit is contained in:
Alejandra González 2024-11-19 22:43:56 +00:00 committed by GitHub
commit a19d69d273
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 263 additions and 154 deletions

View file

@ -1,6 +1,8 @@
[package]
name = "clippy"
# begin autogenerated version
version = "0.1.84"
# 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"

View file

@ -1,6 +1,8 @@
[package]
name = "clippy_config"
# begin autogenerated version
version = "0.1.84"
# end autogenerated version
edition = "2021"
publish = false

View file

@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
aho-corasick = "1.0"
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
clap = { version = "4.4", features = ["derive"] }
indoc = "1.0"
itertools = "0.12"

View file

@ -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

View file

@ -1,4 +1,4 @@
use crate::clippy_project_root;
use crate::utils::clippy_project_root;
use itertools::Itertools;
use rustc_lexer::{TokenKind, tokenize};
use shell_escape::escape;

View file

@ -14,69 +14,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<ExitStatus>) {
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;

View file

@ -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};

View file

@ -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,12 @@ 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),
DevCommand::Sync(SyncCommand { subcommand }) => match subcommand {
SyncSubcommand::UpdateNightly => sync::update_nightly(),
},
DevCommand::Release(ReleaseCommand { subcommand }) => match subcommand {
ReleaseSubcommand::BumpVersion => release::bump_version(),
},
}
}
@ -225,6 +231,10 @@ enum DevCommand {
/// The reason for deprecation
reason: String,
},
/// Sync between the rust repo and the Clippy repo
Sync(SyncCommand),
/// Manage Clippy releases
Release(ReleaseCommand),
}
#[derive(Args)]
@ -291,3 +301,29 @@ 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 {
#[command(name = "update_nightly")]
/// Update nightly version in rust-toolchain and `clippy_utils`
UpdateNightly,
}
#[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,
}

View file

@ -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<String> {
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::<u32>().ok()?,
patch.parse::<u32>().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 {

27
clippy_dev/src/release.rs Normal file
View file

@ -0,0 +1,27 @@
use std::fmt::Write;
use std::path::Path;
use crate::utils::{UpdateMode, clippy_version, replace_region_in_file};
const CARGO_TOML_FILES: [&str; 4] = [
"clippy_config/Cargo.toml",
"clippy_lints/Cargo.toml",
"clippy_utils/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();
},
);
}
}

33
clippy_dev/src/sync.rs Normal file
View file

@ -0,0 +1,33 @@
use std::fmt::Write;
use std::path::Path;
use chrono::offset::Utc;
use crate::utils::{UpdateMode, replace_region_in_file};
pub fn update_nightly() {
// Update rust-toolchain nightly version
let date = Utc::now().format("%Y-%m-%d").to_string();
replace_region_in_file(
UpdateMode::Change,
Path::new("rust-toolchain"),
"# begin autogenerated nightly\n",
"# end autogenerated nightly",
|res| {
writeln!(res, "channel = \"nightly-{date}\"").unwrap();
},
);
// Update clippy_utils nightly version
replace_region_in_file(
UpdateMode::Change,
Path::new("clippy_utils/README.md"),
"<!-- begin autogenerated nightly -->\n",
"<!-- end autogenerated nightly -->",
|res| {
writeln!(res, "```").unwrap();
writeln!(res, "nightly-{date}").unwrap();
writeln!(res, "```").unwrap();
},
);
}

View file

@ -1,4 +1,4 @@
use crate::clippy_project_root;
use crate::utils::{UpdateMode, clippy_project_root, exit_with_failure, replace_region_in_file};
use aho_corasick::AhoCorasickBuilder;
use itertools::Itertools;
use rustc_lexer::{LiteralKind, TokenKind, tokenize, unescape};
@ -17,12 +17,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.
@ -511,14 +505,6 @@ fn process_file(path: impl AsRef<Path>, 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 {
@ -851,61 +837,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<String, &'a str> {
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),

142
clippy_dev/src/utils.rs Normal file
View file

@ -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<ExitStatus>) {
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<String, &'a str> {
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)
}

View file

@ -1,6 +1,8 @@
[package]
name = "clippy_lints"
# begin autogenerated version
version = "0.1.84"
# 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"

View file

@ -1,6 +1,8 @@
[package]
name = "clippy_utils"
# begin autogenerated version
version = "0.1.84"
# end autogenerated version
edition = "2021"
description = "Helpful tools for writing lints, provided as they are used in Clippy"
repository = "https://github.com/rust-lang/rust-clippy"

View file

@ -1,4 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2024-11-14"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"