Skip to content

Commit 21e9336

Browse files
committed
Auto merge of #96501 - jyn514:individual-paths, r=Mark-Simulacrum
Pass all paths to `Step::run` at once when using `ShouldRun::krate` Helps with #95503. The goal is to run `cargo test -p rustc_data_structures -p rustc_lint_defs` instead of `cargo test -p rustc_data_structures; cargo test -p rustc_lint_defs`, which should both recompile less and avoid replaying cached warnings. This was surprisingly complicated. The main changes are: 1. Invert the order of iteration in `StepDescription::run`. Previously, it did something like: ```python for path in paths: for (step, should_run) in should_runs: if let Some(set) = should_run.pathset_for_path(path): step.run(builder, set) ``` That worked ok for individual paths, but didn't allow passing more than one path at a time to `Step::run` (since `pathset_for_paths` only had one path available to it). Change it to instead look at the intersection of `paths` and `should_run.paths`: ```python for (step, should_run) in should_runs: if let Some(set) = should_run.pathset_for_paths(paths): step.run(builder, set) ``` 2. Change `pathset_for_path` to take multiple pathsets. The goal is to avoid `x test library/alloc` testing *all* library crates, instead of just alloc. The changes here are similarly subtle, to use the intersection between the paths rather than all paths in `should_run.paths`. I added a test for the behavior to try and make it more clear. Note that we use pathsets instead of just paths to allow for sets with multiple aliases (*cough* `all_krates` *cough*). See the documentation added in the next commit for more detail. 3. Change `StepDescription::run` to explicitly handle 0 paths. Before this was implicitly handled by the `for` loop, which just didn't excute when there were no paths. Now it needs a check, to avoid trying to run all steps (this is a problem for steps that use `default_condition`). 4. Change `RunDescription` to have a list of pathsets, rather than a single path. 5. Remove paths as they're matched This allows checking at the end that no invalid paths are left over. Note that if two steps matched the same path, this will no longer run both; but that's a bug anyway. 6. Handle suite paths separately from regular sets. Running multiple suite paths at once instead of in separate `make_run` invocations is both tricky and not particularly useful. The respective test Steps already handle this by introspecting the original paths. Avoid having to deal with it by moving suite handling into a seperate loop than `PathSet::Set` checks. `@rustbot` label +A-rustbuild
2 parents ec21d7e + fca6dbd commit 21e9336

File tree

5 files changed

+190
-67
lines changed

5 files changed

+190
-67
lines changed

src/bootstrap/builder.rs

+124-50
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub trait Step: 'static + Clone + Debug + PartialEq + Eq + Hash {
9191
pub struct RunConfig<'a> {
9292
pub builder: &'a Builder<'a>,
9393
pub target: TargetSelection,
94-
pub path: PathBuf,
94+
pub paths: Vec<PathSet>,
9595
}
9696

9797
impl RunConfig<'_> {
@@ -150,11 +150,16 @@ impl Debug for TaskPath {
150150
/// Collection of paths used to match a task rule.
151151
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
152152
pub enum PathSet {
153-
/// A collection of individual paths.
153+
/// A collection of individual paths or aliases.
154154
///
155155
/// These are generally matched as a path suffix. For example, a
156-
/// command-line value of `libstd` will match if `src/libstd` is in the
156+
/// command-line value of `std` will match if `library/std` is in the
157157
/// set.
158+
///
159+
/// NOTE: the paths within a set should always be aliases of one another.
160+
/// For example, `src/librustdoc` and `src/tools/rustdoc` should be in the same set,
161+
/// but `library/core` and `library/std` generally should not, unless there's no way (for that Step)
162+
/// to build them separately.
158163
Set(BTreeSet<TaskPath>),
159164
/// A "suite" of paths.
160165
///
@@ -177,26 +182,65 @@ impl PathSet {
177182
}
178183

179184
fn has(&self, needle: &Path, module: Option<Kind>) -> bool {
180-
let check = |p: &TaskPath| {
181-
if let (Some(p_kind), Some(kind)) = (&p.kind, module) {
182-
p.path.ends_with(needle) && *p_kind == kind
183-
} else {
184-
p.path.ends_with(needle)
185+
match self {
186+
PathSet::Set(set) => set.iter().any(|p| Self::check(p, needle, module)),
187+
PathSet::Suite(suite) => Self::check(suite, needle, module),
188+
}
189+
}
190+
191+
// internal use only
192+
fn check(p: &TaskPath, needle: &Path, module: Option<Kind>) -> bool {
193+
if let (Some(p_kind), Some(kind)) = (&p.kind, module) {
194+
p.path.ends_with(needle) && *p_kind == kind
195+
} else {
196+
p.path.ends_with(needle)
197+
}
198+
}
199+
200+
/// Return all `TaskPath`s in `Self` that contain any of the `needles`, removing the
201+
/// matched needles.
202+
///
203+
/// This is used for `StepDescription::krate`, which passes all matching crates at once to
204+
/// `Step::make_run`, rather than calling it many times with a single crate.
205+
/// See `tests.rs` for examples.
206+
fn intersection_removing_matches(
207+
&self,
208+
needles: &mut Vec<&Path>,
209+
module: Option<Kind>,
210+
) -> PathSet {
211+
let mut check = |p| {
212+
for (i, n) in needles.iter().enumerate() {
213+
let matched = Self::check(p, n, module);
214+
if matched {
215+
needles.remove(i);
216+
return true;
217+
}
185218
}
219+
false
186220
};
187-
188221
match self {
189-
PathSet::Set(set) => set.iter().any(check),
190-
PathSet::Suite(suite) => check(suite),
222+
PathSet::Set(set) => PathSet::Set(set.iter().filter(|&p| check(p)).cloned().collect()),
223+
PathSet::Suite(suite) => {
224+
if check(suite) {
225+
self.clone()
226+
} else {
227+
PathSet::empty()
228+
}
229+
}
191230
}
192231
}
193232

194-
fn path(&self, builder: &Builder<'_>) -> PathBuf {
233+
/// A convenience wrapper for Steps which know they have no aliases and all their sets contain only a single path.
234+
///
235+
/// This can be used with [`ShouldRun::krate`], [`ShouldRun::path`], or [`ShouldRun::alias`].
236+
#[track_caller]
237+
pub fn assert_single_path(&self) -> &TaskPath {
195238
match self {
196239
PathSet::Set(set) => {
197-
set.iter().next().map(|p| &p.path).unwrap_or(&builder.build.src).clone()
240+
assert_eq!(set.len(), 1, "called assert_single_path on multiple paths");
241+
set.iter().next().unwrap()
198242
}
199-
PathSet::Suite(path) => path.path.clone(),
243+
PathSet::Suite(_) => unreachable!("called assert_single_path on a Suite path"),
200244
}
201245
}
202246
}
@@ -213,16 +257,16 @@ impl StepDescription {
213257
}
214258
}
215259

216-
fn maybe_run(&self, builder: &Builder<'_>, pathset: &PathSet) {
217-
if self.is_excluded(builder, pathset) {
260+
fn maybe_run(&self, builder: &Builder<'_>, pathsets: Vec<PathSet>) {
261+
if pathsets.iter().any(|set| self.is_excluded(builder, set)) {
218262
return;
219263
}
220264

221265
// Determine the targets participating in this rule.
222266
let targets = if self.only_hosts { &builder.hosts } else { &builder.targets };
223267

224268
for target in targets {
225-
let run = RunConfig { builder, path: pathset.path(builder), target: *target };
269+
let run = RunConfig { builder, paths: pathsets.clone(), target: *target };
226270
(self.make_run)(run);
227271
}
228272
}
@@ -261,46 +305,55 @@ impl StepDescription {
261305
for (desc, should_run) in v.iter().zip(&should_runs) {
262306
if desc.default && should_run.is_really_default() {
263307
for pathset in &should_run.paths {
264-
desc.maybe_run(builder, pathset);
308+
desc.maybe_run(builder, vec![pathset.clone()]);
265309
}
266310
}
267311
}
268312
}
269313

270-
for path in paths {
271-
// strip CurDir prefix if present
272-
let path = match path.strip_prefix(".") {
273-
Ok(p) => p,
274-
Err(_) => path,
275-
};
314+
// strip CurDir prefix if present
315+
let mut paths: Vec<_> =
316+
paths.into_iter().map(|p| p.strip_prefix(".").unwrap_or(p)).collect();
276317

277-
let mut attempted_run = false;
318+
// Handle all test suite paths.
319+
// (This is separate from the loop below to avoid having to handle multiple paths in `is_suite_path` somehow.)
320+
paths.retain(|path| {
278321
for (desc, should_run) in v.iter().zip(&should_runs) {
279-
if let Some(suite) = should_run.is_suite_path(path) {
280-
attempted_run = true;
281-
desc.maybe_run(builder, suite);
282-
} else if let Some(pathset) = should_run.pathset_for_path(path, desc.kind) {
283-
attempted_run = true;
284-
desc.maybe_run(builder, pathset);
322+
if let Some(suite) = should_run.is_suite_path(&path) {
323+
desc.maybe_run(builder, vec![suite.clone()]);
324+
return false;
285325
}
286326
}
327+
true
328+
});
287329

288-
if !attempted_run {
289-
eprintln!(
290-
"error: no `{}` rules matched '{}'",
291-
builder.kind.as_str(),
292-
path.display()
293-
);
294-
eprintln!(
295-
"help: run `x.py {} --help --verbose` to show a list of available paths",
296-
builder.kind.as_str()
297-
);
298-
eprintln!(
299-
"note: if you are adding a new Step to bootstrap itself, make sure you register it with `describe!`"
300-
);
301-
std::process::exit(1);
330+
if paths.is_empty() {
331+
return;
332+
}
333+
334+
// Handle all PathSets.
335+
for (desc, should_run) in v.iter().zip(&should_runs) {
336+
let pathsets = should_run.pathset_for_paths_removing_matches(&mut paths, desc.kind);
337+
if !pathsets.is_empty() {
338+
desc.maybe_run(builder, pathsets);
302339
}
303340
}
341+
342+
if !paths.is_empty() {
343+
eprintln!("error: no `{}` rules matched {:?}", builder.kind.as_str(), paths,);
344+
eprintln!(
345+
"help: run `x.py {} --help --verbose` to show a list of available paths",
346+
builder.kind.as_str()
347+
);
348+
eprintln!(
349+
"note: if you are adding a new Step to bootstrap itself, make sure you register it with `describe!`"
350+
);
351+
#[cfg(not(test))]
352+
std::process::exit(1);
353+
#[cfg(test)]
354+
// so we can use #[should_panic]
355+
panic!()
356+
}
304357
}
305358
}
306359

@@ -370,7 +423,7 @@ impl<'a> ShouldRun<'a> {
370423
/// Indicates it should run if the command-line selects the given crate or
371424
/// any of its (local) dependencies.
372425
///
373-
/// `make_run` will be called separately for each matching command-line path.
426+
/// `make_run` will be called a single time with all matching command-line paths.
374427
pub fn krate(mut self, name: &str) -> Self {
375428
for krate in self.builder.in_tree_crates(name, None) {
376429
let path = krate.local_path(self.builder);
@@ -417,9 +470,10 @@ impl<'a> ShouldRun<'a> {
417470
self
418471
}
419472

420-
pub fn is_suite_path(&self, path: &Path) -> Option<&PathSet> {
473+
/// Handles individual files (not directories) within a test suite.
474+
fn is_suite_path(&self, requested_path: &Path) -> Option<&PathSet> {
421475
self.paths.iter().find(|pathset| match pathset {
422-
PathSet::Suite(p) => path.starts_with(&p.path),
476+
PathSet::Suite(suite) => requested_path.starts_with(&suite.path),
423477
PathSet::Set(_) => false,
424478
})
425479
}
@@ -435,8 +489,28 @@ impl<'a> ShouldRun<'a> {
435489
self
436490
}
437491

438-
fn pathset_for_path(&self, path: &Path, kind: Kind) -> Option<&PathSet> {
439-
self.paths.iter().find(|pathset| pathset.has(path, Some(kind)))
492+
/// Given a set of requested paths, return the subset which match the Step for this `ShouldRun`,
493+
/// removing the matches from `paths`.
494+
///
495+
/// NOTE: this returns multiple PathSets to allow for the possibility of multiple units of work
496+
/// within the same step. For example, `test::Crate` allows testing multiple crates in the same
497+
/// cargo invocation, which are put into separate sets because they aren't aliases.
498+
///
499+
/// The reason we return PathSet instead of PathBuf is to allow for aliases that mean the same thing
500+
/// (for now, just `all_krates` and `paths`, but we may want to add an `aliases` function in the future?)
501+
fn pathset_for_paths_removing_matches(
502+
&self,
503+
paths: &mut Vec<&Path>,
504+
kind: Kind,
505+
) -> Vec<PathSet> {
506+
let mut sets = vec![];
507+
for pathset in &self.paths {
508+
let subset = pathset.intersection_removing_matches(paths, Some(kind));
509+
if subset != PathSet::empty() {
510+
sets.push(subset);
511+
}
512+
}
513+
sets
440514
}
441515
}
442516

src/bootstrap/builder/tests.rs

+41-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ use crate::config::{Config, TargetSelection};
33
use std::thread;
44

55
fn configure(cmd: &str, host: &[&str], target: &[&str]) -> Config {
6-
let mut config = Config::parse(&[cmd.to_owned()]);
6+
configure_with_args(&[cmd.to_owned()], host, target)
7+
}
8+
9+
fn configure_with_args(cmd: &[String], host: &[&str], target: &[&str]) -> Config {
10+
let mut config = Config::parse(cmd);
711
// don't save toolstates
812
config.save_toolstates = None;
913
config.dry_run = true;
@@ -46,6 +50,41 @@ fn run_build(paths: &[PathBuf], config: Config) -> Cache {
4650
builder.cache
4751
}
4852

53+
fn check_cli<const N: usize>(paths: [&str; N]) {
54+
run_build(
55+
&paths.map(PathBuf::from),
56+
configure_with_args(&paths.map(String::from), &["A"], &["A"]),
57+
);
58+
}
59+
60+
#[test]
61+
fn test_valid() {
62+
// make sure multi suite paths are accepted
63+
check_cli(["test", "src/test/ui/attr-start.rs", "src/test/ui/attr-shebang.rs"]);
64+
}
65+
66+
#[test]
67+
#[should_panic]
68+
fn test_invalid() {
69+
// make sure that invalid paths are caught, even when combined with valid paths
70+
check_cli(["test", "library/std", "x"]);
71+
}
72+
73+
#[test]
74+
fn test_intersection() {
75+
let set = PathSet::Set(
76+
["library/core", "library/alloc", "library/std"].into_iter().map(TaskPath::parse).collect(),
77+
);
78+
let mut command_paths =
79+
vec![Path::new("library/core"), Path::new("library/alloc"), Path::new("library/stdarch")];
80+
let subset = set.intersection_removing_matches(&mut command_paths, None);
81+
assert_eq!(
82+
subset,
83+
PathSet::Set(["library/core", "library/alloc"].into_iter().map(TaskPath::parse).collect())
84+
);
85+
assert_eq!(command_paths, vec![Path::new("library/stdarch")]);
86+
}
87+
4988
#[test]
5089
fn test_exclude() {
5190
let mut config = configure("test", &["A"], &["A"]);
@@ -539,7 +578,7 @@ mod dist {
539578
target: host,
540579
mode: Mode::Std,
541580
test_kind: test::TestKind::Test,
542-
krate: INTERNER.intern_str("std"),
581+
crates: vec![INTERNER.intern_str("std")],
543582
},]
544583
);
545584
}

src/bootstrap/cache.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,15 @@ impl Cache {
270270

271271
#[cfg(test)]
272272
impl Cache {
273-
pub fn all<S: Ord + Copy + Step>(&mut self) -> Vec<(S, S::Output)> {
273+
pub fn all<S: Ord + Clone + Step>(&mut self) -> Vec<(S, S::Output)> {
274274
let cache = self.0.get_mut();
275275
let type_id = TypeId::of::<S>();
276276
let mut v = cache
277277
.remove(&type_id)
278278
.map(|b| b.downcast::<HashMap<S, S::Output>>().expect("correct type"))
279279
.map(|m| m.into_iter().collect::<Vec<_>>())
280280
.unwrap_or_default();
281-
v.sort_by_key(|&(a, _)| a);
281+
v.sort_by_key(|(s, _)| s.clone());
282282
v
283283
}
284284

src/bootstrap/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ mod job {
170170
pub unsafe fn setup(_build: &mut crate::Build) {}
171171
}
172172

173+
pub use crate::builder::PathSet;
173174
use crate::cache::{Interned, INTERNER};
174175
pub use crate::config::Config;
175176
pub use crate::flags::Subcommand;

0 commit comments

Comments
 (0)