Skip to content

Commit

Permalink
Sync and Release automation (#13694)
Browse files Browse the repository at this point in the history
Based on #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
#12759, which I'll rebase
on this.

Next step is to update the documentation, which I'll partly pull out of
#12762

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

cc #13556

changelog: none
  • Loading branch information
blyxyas authored Nov 19, 2024
2 parents cfd17d4 + 93d5ccd commit a19d69d
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 154 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 2 additions & 0 deletions clippy_config/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[package]
name = "clippy_config"
# begin autogenerated version
version = "0.1.84"
# end autogenerated version
edition = "2021"
publish = false

Expand Down
1 change: 1 addition & 0 deletions clippy_dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion clippy_dev/src/dogfood.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion clippy_dev/src/fmt.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
62 changes: 3 additions & 59 deletions clippy_dev/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion clippy_dev/src/lint.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down
44 changes: 40 additions & 4 deletions clippy_dev/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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(),
},
}
}

Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
}
21 changes: 3 additions & 18 deletions clippy_dev/src/new_lint.rs
Original file line number Diff line number Diff line change
@@ -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 _;
Expand Down Expand Up @@ -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 {
Expand Down
27 changes: 27 additions & 0 deletions clippy_dev/src/release.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 33 additions & 0 deletions clippy_dev/src/sync.rs
Original file line number Diff line number Diff line change
@@ -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();
},
);
}
71 changes: 1 addition & 70 deletions clippy_dev/src/update_lints.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down
Loading

0 comments on commit a19d69d

Please sign in to comment.