Skip to content

Commit 58ec9db

Browse files
authored
Rollup merge of #143398 - lolbinarycat:tidy-extra-checks-auto, r=Kobzol
tidy: add support for `--extra-checks=auto:` feature in preparation for #142924 also heavily refactored the parsing of the `--extra-checks` argument to warn about improper usage. cc ```@GuillaumeGomez``` r? ```@Kobzol```
2 parents 9af7bda + 1f80fd0 commit 58ec9db

File tree

12 files changed

+241
-48
lines changed

12 files changed

+241
-48
lines changed

src/bootstrap/src/core/config/flags.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,10 @@ pub enum Subcommand {
383383
bless: bool,
384384
#[arg(long)]
385385
/// comma-separated list of other files types to check (accepts py, py:lint,
386-
/// py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)
386+
/// py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck)
387+
///
388+
/// Any argument can be prefixed with "auto:" to only run if
389+
/// relevant files are modified (eg. "auto:py").
387390
extra_checks: Option<String>,
388391
#[arg(long)]
389392
/// rerun tests even if the inputs are unchanged

src/bootstrap/src/utils/change_tracker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,4 +461,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
461461
severity: ChangeSeverity::Warning,
462462
summary: "`download-rustc` has been temporarily disabled for the library profile due to implementation bugs (see #142505).",
463463
},
464+
ChangeInfo {
465+
change_id: 143398,
466+
severity: ChangeSeverity::Info,
467+
summary: "The --extra-checks flag now supports prefixing any check with `auto:` to only run it if relevant files are modified",
468+
},
464469
];

src/etc/completions/x.fish

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ complete -c x -n "__fish_x_using_subcommand doc" -l skip-std-check-if-no-downloa
293293
complete -c x -n "__fish_x_using_subcommand doc" -s h -l help -d 'Print help (see more with \'--help\')'
294294
complete -c x -n "__fish_x_using_subcommand test" -l test-args -d 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)' -r
295295
complete -c x -n "__fish_x_using_subcommand test" -l compiletest-rustc-args -d 'extra options to pass the compiler when running compiletest tests' -r
296-
complete -c x -n "__fish_x_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)' -r
296+
complete -c x -n "__fish_x_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck)' -r
297297
complete -c x -n "__fish_x_using_subcommand test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r
298298
complete -c x -n "__fish_x_using_subcommand test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r
299299
complete -c x -n "__fish_x_using_subcommand test" -l run -d 'whether to execute run-* tests' -r

src/etc/completions/x.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ Register-ArgumentCompleter -Native -CommandName 'x' -ScriptBlock {
339339
'x;test' {
340340
[CompletionResult]::new('--test-args', '--test-args', [CompletionResultType]::ParameterName, 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)')
341341
[CompletionResult]::new('--compiletest-rustc-args', '--compiletest-rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running compiletest tests')
342-
[CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)')
342+
[CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck)')
343343
[CompletionResult]::new('--compare-mode', '--compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to')
344344
[CompletionResult]::new('--pass', '--pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode')
345345
[CompletionResult]::new('--run', '--run', [CompletionResultType]::ParameterName, 'whether to execute run-* tests')

src/etc/completions/x.py.fish

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ complete -c x.py -n "__fish_x.py_using_subcommand doc" -l skip-std-check-if-no-d
293293
complete -c x.py -n "__fish_x.py_using_subcommand doc" -s h -l help -d 'Print help (see more with \'--help\')'
294294
complete -c x.py -n "__fish_x.py_using_subcommand test" -l test-args -d 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)' -r
295295
complete -c x.py -n "__fish_x.py_using_subcommand test" -l compiletest-rustc-args -d 'extra options to pass the compiler when running compiletest tests' -r
296-
complete -c x.py -n "__fish_x.py_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)' -r
296+
complete -c x.py -n "__fish_x.py_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck)' -r
297297
complete -c x.py -n "__fish_x.py_using_subcommand test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r
298298
complete -c x.py -n "__fish_x.py_using_subcommand test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r
299299
complete -c x.py -n "__fish_x.py_using_subcommand test" -l run -d 'whether to execute run-* tests' -r

src/etc/completions/x.py.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock {
339339
'x.py;test' {
340340
[CompletionResult]::new('--test-args', '--test-args', [CompletionResultType]::ParameterName, 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)')
341341
[CompletionResult]::new('--compiletest-rustc-args', '--compiletest-rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running compiletest tests')
342-
[CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)')
342+
[CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck)')
343343
[CompletionResult]::new('--compare-mode', '--compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to')
344344
[CompletionResult]::new('--pass', '--pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode')
345345
[CompletionResult]::new('--run', '--run', [CompletionResultType]::ParameterName, 'whether to execute run-* tests')

src/etc/completions/x.py.zsh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ _arguments "${_arguments_options[@]}" : \
338338
_arguments "${_arguments_options[@]}" : \
339339
'*--test-args=[extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)]:ARGS:_default' \
340340
'*--compiletest-rustc-args=[extra options to pass the compiler when running compiletest tests]:ARGS:_default' \
341-
'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, shell\:lint, cpp, cpp\:fmt, spellcheck, spellcheck\:fix)]:EXTRA_CHECKS:_default' \
341+
'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, shell\:lint, cpp, cpp\:fmt, spellcheck)]:EXTRA_CHECKS:_default' \
342342
'--compare-mode=[mode describing what file the actual ui output will be compared to]:COMPARE MODE:_default' \
343343
'--pass=[force {check,build,run}-pass tests to this mode]:check | build | run:_default' \
344344
'--run=[whether to execute run-* tests]:auto | always | never:_default' \

src/etc/completions/x.zsh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ _arguments "${_arguments_options[@]}" : \
338338
_arguments "${_arguments_options[@]}" : \
339339
'*--test-args=[extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)]:ARGS:_default' \
340340
'*--compiletest-rustc-args=[extra options to pass the compiler when running compiletest tests]:ARGS:_default' \
341-
'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, shell\:lint, cpp, cpp\:fmt, spellcheck, spellcheck\:fix)]:EXTRA_CHECKS:_default' \
341+
'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, shell\:lint, cpp, cpp\:fmt, spellcheck)]:EXTRA_CHECKS:_default' \
342342
'--compare-mode=[mode describing what file the actual ui output will be compared to]:COMPARE MODE:_default' \
343343
'--pass=[force {check,build,run}-pass tests to this mode]:check | build | run:_default' \
344344
'--run=[whether to execute run-* tests]:auto | always | never:_default' \

src/tools/tidy/src/ext_tool_checks.rs

Lines changed: 179 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
use std::ffi::OsStr;
2121
use std::path::{Path, PathBuf};
2222
use std::process::Command;
23+
use std::str::FromStr;
2324
use std::{fmt, fs, io};
2425

26+
use crate::CiInfo;
27+
2528
const MIN_PY_REV: (u32, u32) = (3, 9);
2629
const MIN_PY_REV_STR: &str = "≥3.9";
2730

@@ -36,22 +39,27 @@ const RUFF_CONFIG_PATH: &[&str] = &["src", "tools", "tidy", "config", "ruff.toml
3639
const RUFF_CACHE_PATH: &[&str] = &["cache", "ruff_cache"];
3740
const PIP_REQ_PATH: &[&str] = &["src", "tools", "tidy", "config", "requirements.txt"];
3841

42+
// this must be kept in sync with with .github/workflows/spellcheck.yml
43+
const SPELLCHECK_DIRS: &[&str] = &["compiler", "library", "src/bootstrap", "src/librustdoc"];
44+
3945
pub fn check(
4046
root_path: &Path,
4147
outdir: &Path,
48+
ci_info: &CiInfo,
4249
bless: bool,
4350
extra_checks: Option<&str>,
4451
pos_args: &[String],
4552
bad: &mut bool,
4653
) {
47-
if let Err(e) = check_impl(root_path, outdir, bless, extra_checks, pos_args) {
54+
if let Err(e) = check_impl(root_path, outdir, ci_info, bless, extra_checks, pos_args) {
4855
tidy_error!(bad, "{e}");
4956
}
5057
}
5158

5259
fn check_impl(
5360
root_path: &Path,
5461
outdir: &Path,
62+
ci_info: &CiInfo,
5563
bless: bool,
5664
extra_checks: Option<&str>,
5765
pos_args: &[String],
@@ -61,25 +69,45 @@ fn check_impl(
6169

6270
// Split comma-separated args up
6371
let lint_args = match extra_checks {
64-
Some(s) => s.strip_prefix("--extra-checks=").unwrap().split(',').collect(),
72+
Some(s) => s
73+
.strip_prefix("--extra-checks=")
74+
.unwrap()
75+
.split(',')
76+
.map(|s| {
77+
if s == "spellcheck:fix" {
78+
eprintln!("warning: `spellcheck:fix` is no longer valid, use `--extra-checks=spellcheck --bless`");
79+
}
80+
(ExtraCheckArg::from_str(s), s)
81+
})
82+
.filter_map(|(res, src)| match res {
83+
Ok(arg) => {
84+
if arg.is_inactive_auto(ci_info) {
85+
None
86+
} else {
87+
Some(arg)
88+
}
89+
}
90+
Err(err) => {
91+
// only warn because before bad extra checks would be silently ignored.
92+
eprintln!("warning: bad extra check argument {src:?}: {err:?}");
93+
None
94+
}
95+
})
96+
.collect(),
6597
None => vec![],
6698
};
6799

68-
if lint_args.contains(&"spellcheck:fix") {
69-
return Err(Error::Generic(
70-
"`spellcheck:fix` is no longer valid, use `--extra=check=spellcheck --bless`"
71-
.to_string(),
72-
));
100+
macro_rules! extra_check {
101+
($lang:ident, $kind:ident) => {
102+
lint_args.iter().any(|arg| arg.matches(ExtraCheckLang::$lang, ExtraCheckKind::$kind))
103+
};
73104
}
74105

75-
let python_all = lint_args.contains(&"py");
76-
let python_lint = lint_args.contains(&"py:lint") || python_all;
77-
let python_fmt = lint_args.contains(&"py:fmt") || python_all;
78-
let shell_all = lint_args.contains(&"shell");
79-
let shell_lint = lint_args.contains(&"shell:lint") || shell_all;
80-
let cpp_all = lint_args.contains(&"cpp");
81-
let cpp_fmt = lint_args.contains(&"cpp:fmt") || cpp_all;
82-
let spellcheck = lint_args.contains(&"spellcheck");
106+
let python_lint = extra_check!(Py, Lint);
107+
let python_fmt = extra_check!(Py, Fmt);
108+
let shell_lint = extra_check!(Shell, Lint);
109+
let cpp_fmt = extra_check!(Cpp, Fmt);
110+
let spellcheck = extra_check!(Spellcheck, None);
83111

84112
let mut py_path = None;
85113

@@ -234,15 +262,9 @@ fn check_impl(
234262

235263
if spellcheck {
236264
let config_path = root_path.join("typos.toml");
237-
// sync target files with .github/workflows/spellcheck.yml
238-
let mut args = vec![
239-
"-c",
240-
config_path.as_os_str().to_str().unwrap(),
241-
"./compiler",
242-
"./library",
243-
"./src/bootstrap",
244-
"./src/librustdoc",
245-
];
265+
let mut args = vec!["-c", config_path.as_os_str().to_str().unwrap()];
266+
267+
args.extend_from_slice(SPELLCHECK_DIRS);
246268

247269
if bless {
248270
eprintln!("spellcheck files and fix");
@@ -638,3 +660,136 @@ impl From<io::Error> for Error {
638660
Self::Io(value)
639661
}
640662
}
663+
664+
#[derive(Debug)]
665+
enum ExtraCheckParseError {
666+
#[allow(dead_code, reason = "shown through Debug")]
667+
UnknownKind(String),
668+
#[allow(dead_code)]
669+
UnknownLang(String),
670+
UnsupportedKindForLang,
671+
/// Too many `:`
672+
TooManyParts,
673+
/// Tried to parse the empty string
674+
Empty,
675+
/// `auto` specified without lang part.
676+
AutoRequiresLang,
677+
}
678+
679+
struct ExtraCheckArg {
680+
auto: bool,
681+
lang: ExtraCheckLang,
682+
/// None = run all extra checks for the given lang
683+
kind: Option<ExtraCheckKind>,
684+
}
685+
686+
impl ExtraCheckArg {
687+
fn matches(&self, lang: ExtraCheckLang, kind: ExtraCheckKind) -> bool {
688+
self.lang == lang && self.kind.map(|k| k == kind).unwrap_or(true)
689+
}
690+
691+
/// Returns `true` if this is an auto arg and the relevant files are not modified.
692+
fn is_inactive_auto(&self, ci_info: &CiInfo) -> bool {
693+
if !self.auto {
694+
return false;
695+
}
696+
let ext = match self.lang {
697+
ExtraCheckLang::Py => ".py",
698+
ExtraCheckLang::Cpp => ".cpp",
699+
ExtraCheckLang::Shell => ".sh",
700+
ExtraCheckLang::Spellcheck => {
701+
return !crate::files_modified(ci_info, |s| {
702+
SPELLCHECK_DIRS.iter().any(|dir| Path::new(s).starts_with(dir))
703+
});
704+
}
705+
};
706+
!crate::files_modified(ci_info, |s| s.ends_with(ext))
707+
}
708+
709+
fn has_supported_kind(&self) -> bool {
710+
let Some(kind) = self.kind else {
711+
// "run all extra checks" mode is supported for all languages.
712+
return true;
713+
};
714+
use ExtraCheckKind::*;
715+
let supported_kinds: &[_] = match self.lang {
716+
ExtraCheckLang::Py => &[Fmt, Lint],
717+
ExtraCheckLang::Cpp => &[Fmt],
718+
ExtraCheckLang::Shell => &[Lint],
719+
ExtraCheckLang::Spellcheck => &[],
720+
};
721+
supported_kinds.contains(&kind)
722+
}
723+
}
724+
725+
impl FromStr for ExtraCheckArg {
726+
type Err = ExtraCheckParseError;
727+
728+
fn from_str(s: &str) -> Result<Self, Self::Err> {
729+
let mut auto = false;
730+
let mut parts = s.split(':');
731+
let Some(mut first) = parts.next() else {
732+
return Err(ExtraCheckParseError::Empty);
733+
};
734+
if first == "auto" {
735+
let Some(part) = parts.next() else {
736+
return Err(ExtraCheckParseError::AutoRequiresLang);
737+
};
738+
auto = true;
739+
first = part;
740+
}
741+
let second = parts.next();
742+
if parts.next().is_some() {
743+
return Err(ExtraCheckParseError::TooManyParts);
744+
}
745+
let arg = Self { auto, lang: first.parse()?, kind: second.map(|s| s.parse()).transpose()? };
746+
if !arg.has_supported_kind() {
747+
return Err(ExtraCheckParseError::UnsupportedKindForLang);
748+
}
749+
750+
Ok(arg)
751+
}
752+
}
753+
754+
#[derive(PartialEq, Copy, Clone)]
755+
enum ExtraCheckLang {
756+
Py,
757+
Shell,
758+
Cpp,
759+
Spellcheck,
760+
}
761+
762+
impl FromStr for ExtraCheckLang {
763+
type Err = ExtraCheckParseError;
764+
765+
fn from_str(s: &str) -> Result<Self, Self::Err> {
766+
Ok(match s {
767+
"py" => Self::Py,
768+
"shell" => Self::Shell,
769+
"cpp" => Self::Cpp,
770+
"spellcheck" => Self::Spellcheck,
771+
_ => return Err(ExtraCheckParseError::UnknownLang(s.to_string())),
772+
})
773+
}
774+
}
775+
776+
#[derive(PartialEq, Copy, Clone)]
777+
enum ExtraCheckKind {
778+
Lint,
779+
Fmt,
780+
/// Never parsed, but used as a placeholder for
781+
/// langs that never have a specific kind.
782+
None,
783+
}
784+
785+
impl FromStr for ExtraCheckKind {
786+
type Err = ExtraCheckParseError;
787+
788+
fn from_str(s: &str) -> Result<Self, Self::Err> {
789+
Ok(match s {
790+
"lint" => Self::Lint,
791+
"fmt" => Self::Fmt,
792+
_ => return Err(ExtraCheckParseError::UnknownKind(s.to_string())),
793+
})
794+
}
795+
}

src/tools/tidy/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,40 @@ pub fn git_diff<S: AsRef<OsStr>>(base_commit: &str, extra_arg: S) -> Option<Stri
124124
Some(String::from_utf8_lossy(&output.stdout).into())
125125
}
126126

127+
/// Returns true if any modified file matches the predicate, if we are in CI, or if unable to list modified files.
128+
pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool {
129+
if CiEnv::is_ci() {
130+
// assume everything is modified on CI because we really don't want false positives there.
131+
return true;
132+
}
133+
let Some(base_commit) = &ci_info.base_commit else {
134+
eprintln!("No base commit, assuming all files are modified");
135+
return true;
136+
};
137+
match crate::git_diff(&base_commit, "--name-status") {
138+
Some(output) => {
139+
let modified_files = output.lines().filter_map(|ln| {
140+
let (status, name) = ln
141+
.trim_end()
142+
.split_once('\t')
143+
.expect("bad format from `git diff --name-status`");
144+
if status == "M" { Some(name) } else { None }
145+
});
146+
for modified_file in modified_files {
147+
if pred(modified_file) {
148+
return true;
149+
}
150+
}
151+
false
152+
}
153+
None => {
154+
eprintln!("warning: failed to run `git diff` to check for changes");
155+
eprintln!("warning: assuming all files are modified");
156+
true
157+
}
158+
}
159+
}
160+
127161
pub mod alphabetical;
128162
pub mod bins;
129163
pub mod debug_artifacts;

0 commit comments

Comments
 (0)