Skip to content

Commit fc69406

Browse files
authored
Rollup merge of #91965 - ferrocene:pa-more-granular-exclude, r=Mark-Simulacrum
Add more granular `--exclude` in `x.py` x.py has support for excluding some steps from the current invocation, but unfortunately that's not granular enough: some steps have the same name in different modules, and that prevents excluding only *some* of them. As a practical example, let's say you need to run everything in `./x.py test` except for the standard library tests, as those tests require IPv6 and need to be executed on a separate machine. Before this commit, if you were to just run this: ./x.py test --exclude library/std ...the invocation would eventually fail, as that would not only exclude running the tests for the standard library (`library/std` in the `test` module), it would also exclude generating its documentation (`library/std` in the `doc` module), breaking linkchecker. This commit adds support to the `--exclude` flag for prefixing paths with the name of the module their step is defined in, allowing the user to choose which module to exclude from: ./x.py test --exclude test::library/std This maintains backward compatibility with existing invocations, while allowing more ganular exclusion. Examples of the behavior: | `--exclude` | Docs | Tests | | ------------------- | ------- | ------- | | `library/std` | Skipped | Skipped | | `doc::library/std` | Skipped | Run | | `test::library/std` | Run | Skipped | Note that this PR only changes the `--exclude` flag, and not in other `x.py` arguments or flags yet. In the implementation I tried to limit the impact this would have with rustbuild as a whole as much as possible. The module name is extracted from the step by parsing the result of `std::any::type_name()`: unfortunately that output can change at any point in time, but IMO it's better than having to annotate all the existing and future `Step` implementations with the module name. I added a test to ensure the parsing works as expected, so hopefully if anyone makes changes to the output of `std::any::type_name()` they'll also notice they have to update rustbuild. r? `@Mark-Simulacrum`
2 parents cbaeec1 + b3ad405 commit fc69406

File tree

5 files changed

+146
-45
lines changed

5 files changed

+146
-45
lines changed

src/bootstrap/builder.rs

Lines changed: 135 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::fmt::Debug;
77
use std::fs;
88
use std::hash::Hash;
99
use std::ops::Deref;
10-
use std::path::{Path, PathBuf};
10+
use std::path::{Component, Path, PathBuf};
1111
use std::process::Command;
1212
use std::time::{Duration, Instant};
1313

@@ -105,6 +105,44 @@ struct StepDescription {
105105
should_run: fn(ShouldRun<'_>) -> ShouldRun<'_>,
106106
make_run: fn(RunConfig<'_>),
107107
name: &'static str,
108+
kind: Kind,
109+
}
110+
111+
#[derive(Clone, PartialOrd, Ord, PartialEq, Eq)]
112+
pub struct TaskPath {
113+
pub path: PathBuf,
114+
pub kind: Option<Kind>,
115+
}
116+
117+
impl TaskPath {
118+
pub fn parse(path: impl Into<PathBuf>) -> TaskPath {
119+
let mut kind = None;
120+
let mut path = path.into();
121+
122+
let mut components = path.components();
123+
if let Some(Component::Normal(os_str)) = components.next() {
124+
if let Some(str) = os_str.to_str() {
125+
if let Some((found_kind, found_prefix)) = str.split_once("::") {
126+
if found_kind.is_empty() {
127+
panic!("empty kind in task path {}", path.display());
128+
}
129+
kind = Some(Kind::parse(found_kind));
130+
path = Path::new(found_prefix).join(components.as_path());
131+
}
132+
}
133+
}
134+
135+
TaskPath { path, kind }
136+
}
137+
}
138+
139+
impl Debug for TaskPath {
140+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141+
if let Some(kind) = &self.kind {
142+
write!(f, "{}::", kind.as_str())?;
143+
}
144+
write!(f, "{}", self.path.display())
145+
}
108146
}
109147

110148
/// Collection of paths used to match a task rule.
@@ -115,50 +153,61 @@ pub enum PathSet {
115153
/// These are generally matched as a path suffix. For example, a
116154
/// command-line value of `libstd` will match if `src/libstd` is in the
117155
/// set.
118-
Set(BTreeSet<PathBuf>),
156+
Set(BTreeSet<TaskPath>),
119157
/// A "suite" of paths.
120158
///
121159
/// These can match as a path suffix (like `Set`), or as a prefix. For
122160
/// example, a command-line value of `src/test/ui/abi/variadic-ffi.rs`
123161
/// will match `src/test/ui`. A command-line value of `ui` would also
124162
/// match `src/test/ui`.
125-
Suite(PathBuf),
163+
Suite(TaskPath),
126164
}
127165

128166
impl PathSet {
129167
fn empty() -> PathSet {
130168
PathSet::Set(BTreeSet::new())
131169
}
132170

133-
fn one<P: Into<PathBuf>>(path: P) -> PathSet {
171+
fn one<P: Into<PathBuf>>(path: P, kind: Kind) -> PathSet {
134172
let mut set = BTreeSet::new();
135-
set.insert(path.into());
173+
set.insert(TaskPath { path: path.into(), kind: Some(kind.into()) });
136174
PathSet::Set(set)
137175
}
138176

139-
fn has(&self, needle: &Path) -> bool {
177+
fn has(&self, needle: &Path, module: Option<Kind>) -> bool {
178+
let check = |p: &TaskPath| {
179+
if let (Some(p_kind), Some(kind)) = (&p.kind, module) {
180+
p.path.ends_with(needle) && *p_kind == kind
181+
} else {
182+
p.path.ends_with(needle)
183+
}
184+
};
185+
140186
match self {
141-
PathSet::Set(set) => set.iter().any(|p| p.ends_with(needle)),
142-
PathSet::Suite(suite) => suite.ends_with(needle),
187+
PathSet::Set(set) => set.iter().any(check),
188+
PathSet::Suite(suite) => check(suite),
143189
}
144190
}
145191

146192
fn path(&self, builder: &Builder<'_>) -> PathBuf {
147193
match self {
148-
PathSet::Set(set) => set.iter().next().unwrap_or(&builder.build.src).to_path_buf(),
149-
PathSet::Suite(path) => PathBuf::from(path),
194+
PathSet::Set(set) => {
195+
set.iter().next().map(|p| &p.path).unwrap_or(&builder.build.src).clone()
196+
}
197+
PathSet::Suite(path) => path.path.clone(),
150198
}
151199
}
152200
}
153201

154202
impl StepDescription {
155-
fn from<S: Step>() -> StepDescription {
203+
fn from<S: Step>(kind: Kind) -> StepDescription {
156204
StepDescription {
157205
default: S::DEFAULT,
158206
only_hosts: S::ONLY_HOSTS,
159207
should_run: S::should_run,
160208
make_run: S::make_run,
161209
name: std::any::type_name::<S>(),
210+
kind,
162211
}
163212
}
164213

@@ -177,7 +226,7 @@ impl StepDescription {
177226
}
178227

179228
fn is_excluded(&self, builder: &Builder<'_>, pathset: &PathSet) -> bool {
180-
if builder.config.exclude.iter().any(|e| pathset.has(e)) {
229+
if builder.config.exclude.iter().any(|e| pathset.has(&e.path, e.kind)) {
181230
eprintln!("Skipping {:?} because it is excluded", pathset);
182231
return true;
183232
}
@@ -192,8 +241,10 @@ impl StepDescription {
192241
}
193242

194243
fn run(v: &[StepDescription], builder: &Builder<'_>, paths: &[PathBuf]) {
195-
let should_runs =
196-
v.iter().map(|desc| (desc.should_run)(ShouldRun::new(builder))).collect::<Vec<_>>();
244+
let should_runs = v
245+
.iter()
246+
.map(|desc| (desc.should_run)(ShouldRun::new(builder, desc.kind)))
247+
.collect::<Vec<_>>();
197248

198249
// sanity checks on rules
199250
for (desc, should_run) in v.iter().zip(&should_runs) {
@@ -226,7 +277,7 @@ impl StepDescription {
226277
if let Some(suite) = should_run.is_suite_path(path) {
227278
attempted_run = true;
228279
desc.maybe_run(builder, suite);
229-
} else if let Some(pathset) = should_run.pathset_for_path(path) {
280+
} else if let Some(pathset) = should_run.pathset_for_path(path, desc.kind) {
230281
attempted_run = true;
231282
desc.maybe_run(builder, pathset);
232283
}
@@ -246,6 +297,8 @@ enum ReallyDefault<'a> {
246297

247298
pub struct ShouldRun<'a> {
248299
pub builder: &'a Builder<'a>,
300+
kind: Kind,
301+
249302
// use a BTreeSet to maintain sort order
250303
paths: BTreeSet<PathSet>,
251304

@@ -255,9 +308,10 @@ pub struct ShouldRun<'a> {
255308
}
256309

257310
impl<'a> ShouldRun<'a> {
258-
fn new(builder: &'a Builder<'_>) -> ShouldRun<'a> {
311+
fn new(builder: &'a Builder<'_>, kind: Kind) -> ShouldRun<'a> {
259312
ShouldRun {
260313
builder,
314+
kind,
261315
paths: BTreeSet::new(),
262316
is_really_default: ReallyDefault::Bool(true), // by default no additional conditions
263317
}
@@ -293,7 +347,7 @@ impl<'a> ShouldRun<'a> {
293347
let mut set = BTreeSet::new();
294348
for krate in self.builder.in_tree_crates(name, None) {
295349
let path = krate.local_path(self.builder);
296-
set.insert(path);
350+
set.insert(TaskPath { path, kind: Some(self.kind) });
297351
}
298352
self.paths.insert(PathSet::Set(set));
299353
self
@@ -306,7 +360,7 @@ impl<'a> ShouldRun<'a> {
306360
pub fn krate(mut self, name: &str) -> Self {
307361
for krate in self.builder.in_tree_crates(name, None) {
308362
let path = krate.local_path(self.builder);
309-
self.paths.insert(PathSet::one(path));
363+
self.paths.insert(PathSet::one(path, self.kind));
310364
}
311365
self
312366
}
@@ -318,19 +372,25 @@ impl<'a> ShouldRun<'a> {
318372

319373
// multiple aliases for the same job
320374
pub fn paths(mut self, paths: &[&str]) -> Self {
321-
self.paths.insert(PathSet::Set(paths.iter().map(PathBuf::from).collect()));
375+
self.paths.insert(PathSet::Set(
376+
paths
377+
.iter()
378+
.map(|p| TaskPath { path: p.into(), kind: Some(self.kind.into()) })
379+
.collect(),
380+
));
322381
self
323382
}
324383

325384
pub fn is_suite_path(&self, path: &Path) -> Option<&PathSet> {
326385
self.paths.iter().find(|pathset| match pathset {
327-
PathSet::Suite(p) => path.starts_with(p),
386+
PathSet::Suite(p) => path.starts_with(&p.path),
328387
PathSet::Set(_) => false,
329388
})
330389
}
331390

332391
pub fn suite_path(mut self, suite: &str) -> Self {
333-
self.paths.insert(PathSet::Suite(PathBuf::from(suite)));
392+
self.paths
393+
.insert(PathSet::Suite(TaskPath { path: suite.into(), kind: Some(self.kind.into()) }));
334394
self
335395
}
336396

@@ -340,12 +400,12 @@ impl<'a> ShouldRun<'a> {
340400
self
341401
}
342402

343-
fn pathset_for_path(&self, path: &Path) -> Option<&PathSet> {
344-
self.paths.iter().find(|pathset| pathset.has(path))
403+
fn pathset_for_path(&self, path: &Path, kind: Kind) -> Option<&PathSet> {
404+
self.paths.iter().find(|pathset| pathset.has(path, Some(kind)))
345405
}
346406
}
347407

348-
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
408+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
349409
pub enum Kind {
350410
Build,
351411
Check,
@@ -359,11 +419,44 @@ pub enum Kind {
359419
Run,
360420
}
361421

422+
impl Kind {
423+
fn parse(string: &str) -> Kind {
424+
match string {
425+
"build" => Kind::Build,
426+
"check" => Kind::Check,
427+
"clippy" => Kind::Clippy,
428+
"fix" => Kind::Fix,
429+
"test" => Kind::Test,
430+
"bench" => Kind::Bench,
431+
"dist" => Kind::Dist,
432+
"doc" => Kind::Doc,
433+
"install" => Kind::Install,
434+
"run" => Kind::Run,
435+
other => panic!("unknown kind: {}", other),
436+
}
437+
}
438+
439+
fn as_str(&self) -> &'static str {
440+
match self {
441+
Kind::Build => "build",
442+
Kind::Check => "check",
443+
Kind::Clippy => "clippy",
444+
Kind::Fix => "fix",
445+
Kind::Test => "test",
446+
Kind::Bench => "bench",
447+
Kind::Dist => "dist",
448+
Kind::Doc => "doc",
449+
Kind::Install => "install",
450+
Kind::Run => "run",
451+
}
452+
}
453+
}
454+
362455
impl<'a> Builder<'a> {
363456
fn get_step_descriptions(kind: Kind) -> Vec<StepDescription> {
364457
macro_rules! describe {
365458
($($rule:ty),+ $(,)?) => {{
366-
vec![$(StepDescription::from::<$rule>()),+]
459+
vec![$(StepDescription::from::<$rule>(kind)),+]
367460
}};
368461
}
369462
match kind {
@@ -540,8 +633,11 @@ impl<'a> Builder<'a> {
540633

541634
let builder = Self::new_internal(build, kind, vec![]);
542635
let builder = &builder;
543-
let mut should_run = ShouldRun::new(builder);
636+
// The "build" kind here is just a placeholder, it will be replaced with something else in
637+
// the following statement.
638+
let mut should_run = ShouldRun::new(builder, Kind::Build);
544639
for desc in Builder::get_step_descriptions(builder.kind) {
640+
should_run.kind = desc.kind;
545641
should_run = (desc.should_run)(should_run);
546642
}
547643
let mut help = String::from("Available paths:\n");
@@ -552,11 +648,11 @@ impl<'a> Builder<'a> {
552648
match pathset {
553649
PathSet::Set(set) => {
554650
for path in set {
555-
add_path(&path);
651+
add_path(&path.path);
556652
}
557653
}
558654
PathSet::Suite(path) => {
559-
add_path(&path.join("..."));
655+
add_path(&path.path.join("..."));
560656
}
561657
}
562658
}
@@ -1626,9 +1722,10 @@ impl<'a> Builder<'a> {
16261722
pub(crate) fn ensure_if_default<T, S: Step<Output = Option<T>>>(
16271723
&'a self,
16281724
step: S,
1725+
kind: Kind,
16291726
) -> S::Output {
1630-
let desc = StepDescription::from::<S>();
1631-
let should_run = (desc.should_run)(ShouldRun::new(self));
1727+
let desc = StepDescription::from::<S>(kind);
1728+
let should_run = (desc.should_run)(ShouldRun::new(self, desc.kind));
16321729

16331730
// Avoid running steps contained in --exclude
16341731
for pathset in &should_run.paths {
@@ -1642,13 +1739,16 @@ impl<'a> Builder<'a> {
16421739
}
16431740

16441741
/// Checks if any of the "should_run" paths is in the `Builder` paths.
1645-
pub(crate) fn was_invoked_explicitly<S: Step>(&'a self) -> bool {
1646-
let desc = StepDescription::from::<S>();
1647-
let should_run = (desc.should_run)(ShouldRun::new(self));
1742+
pub(crate) fn was_invoked_explicitly<S: Step>(&'a self, kind: Kind) -> bool {
1743+
let desc = StepDescription::from::<S>(kind);
1744+
let should_run = (desc.should_run)(ShouldRun::new(self, desc.kind));
16481745

16491746
for path in &self.paths {
1650-
if should_run.paths.iter().any(|s| s.has(path))
1651-
&& !desc.is_excluded(self, &PathSet::Suite(path.clone()))
1747+
if should_run.paths.iter().any(|s| s.has(path, Some(desc.kind)))
1748+
&& !desc.is_excluded(
1749+
self,
1750+
&PathSet::Suite(TaskPath { path: path.clone(), kind: Some(desc.kind.into()) }),
1751+
)
16521752
{
16531753
return true;
16541754
}

src/bootstrap/builder/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ mod dist {
499499
let host = TargetSelection::from_user("A");
500500

501501
builder.run_step_descriptions(
502-
&[StepDescription::from::<test::Crate>()],
502+
&[StepDescription::from::<test::Crate>(Kind::Test)],
503503
&["library/std".into()],
504504
);
505505

@@ -520,7 +520,7 @@ mod dist {
520520
#[test]
521521
fn test_exclude() {
522522
let mut config = configure(&["A"], &["A"]);
523-
config.exclude = vec!["src/tools/tidy".into()];
523+
config.exclude = vec![TaskPath::parse("src/tools/tidy")];
524524
config.cmd = Subcommand::Test {
525525
paths: Vec::new(),
526526
test_args: Vec::new(),

src/bootstrap/config.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::fs;
1212
use std::path::{Path, PathBuf};
1313
use std::str::FromStr;
1414

15+
use crate::builder::TaskPath;
1516
use crate::cache::{Interned, INTERNER};
1617
use crate::channel::GitInfo;
1718
pub use crate::flags::Subcommand;
@@ -62,7 +63,7 @@ pub struct Config {
6263
pub sanitizers: bool,
6364
pub profiler: bool,
6465
pub ignore_git: bool,
65-
pub exclude: Vec<PathBuf>,
66+
pub exclude: Vec<TaskPath>,
6667
pub include_default_paths: bool,
6768
pub rustc_error_format: Option<String>,
6869
pub json_output: bool,
@@ -635,7 +636,7 @@ impl Config {
635636
let flags = Flags::parse(&args);
636637

637638
let mut config = Config::default_opts();
638-
config.exclude = flags.exclude;
639+
config.exclude = flags.exclude.into_iter().map(|path| TaskPath::parse(path)).collect();
639640
config.include_default_paths = flags.include_default_paths;
640641
config.rustc_error_format = flags.rustc_error_format;
641642
config.json_output = flags.json_output;

0 commit comments

Comments
 (0)