bootstrap/core/builder/
cli_paths.rs1use std::collections::{HashMap, HashSet};
6use std::hash::Hash;
7use std::path::{Path, PathBuf};
8
9use crate::core::builder::{Builder, CommandLineStepDescription, Kind, PathSet, ShouldRun};
10use crate::utils::helpers;
11
12#[cfg(test)]
13mod tests;
14
15struct StepExtra<'a> {
17 desc: &'a CommandLineStepDescription,
18 should_run: ShouldRun<'a>,
19}
20
21pub(crate) fn match_paths_to_steps_and_run(
22 builder: &Builder<'_>,
23 step_descs: &[CommandLineStepDescription],
24 paths: &[PathBuf],
25) {
26 let steps = step_descs
29 .iter()
30 .map(|desc| StepExtra { desc, should_run: (desc.should_run)(ShouldRun::new(builder)) })
31 .collect::<Vec<_>>();
32
33 if builder.download_rustc() && (builder.kind == Kind::Dist || builder.kind == Kind::Install) {
36 eprintln!(
37 "ERROR: '{}' subcommand is incompatible with `rust.download-rustc`.",
38 builder.kind.as_str()
39 );
40 helpers::exit_process(1);
41 }
42
43 for StepExtra { desc, should_run } in &steps {
45 assert!(!should_run.paths.is_empty(), "{:?} should have at least one pathset", desc.name);
46 }
47
48 if paths.is_empty() || builder.config.include_default_paths {
50 for StepExtra { desc, should_run } in &steps {
51 if (desc.is_default_step_fn)(builder) {
52 let default_pathsets = should_run.default_pathsets();
53 desc.maybe_run(builder, default_pathsets);
54 }
55 }
56 }
57
58 let paths = paths.iter().map(|path| normalize_selector(builder, path)).collect::<Vec<_>>();
60
61 let bad_abs_paths = paths.iter().filter(|path| path.is_absolute()).collect::<Vec<_>>();
63 if !bad_abs_paths.is_empty() {
64 eprintln!(
65 "ERROR: the following paths do not exist on disk or point outside the source directory: {bad_abs_paths:#?}"
66 );
67 helpers::exit_process(1);
68 }
69
70 let mut step_queue = Vec::<usize>::with_capacity(paths.len());
78 let mut step_anchors = HashMap::<usize, Vec<&PathSet>>::with_capacity(steps.len());
79 let mut unmatched_paths = vec![];
80
81 for path in &paths {
83 let mut path_matched = false;
84
85 for (step_ix, step) in steps.iter().enumerate() {
86 let matched_anchors = step
87 .should_run
88 .paths
89 .iter()
90 .filter(|anchor| {
91 anchor.has(path)
94 || matches!(anchor, PathSet::Suite(suite) if path.starts_with(&suite.path))
95 })
96 .collect::<Vec<_>>();
97
98 if !matched_anchors.is_empty() {
99 step_queue.push(step_ix);
100 step_anchors.entry(step_ix).or_default().extend(matched_anchors);
101 path_matched = true;
102 }
103 }
104
105 if !path_matched {
106 unmatched_paths.push(path);
107 }
108 }
109
110 if !unmatched_paths.is_empty() {
111 eprintln!("ERROR: no `{}` rules matched {unmatched_paths:?}", builder.kind.as_str());
112 eprintln!(
113 "HELP: run `x.py {} --help --verbose` to show a list of available paths",
114 builder.kind.as_str()
115 );
116 eprintln!(
117 "NOTE: if you are adding a new Step to bootstrap itself, make sure you register it with `describe!`"
118 );
119 helpers::exit_process(1);
120 }
121
122 fn dedup_vec<T: Copy + Eq + Hash>(vec: &mut Vec<T>) {
123 let mut seen = HashSet::<T>::with_capacity(vec.len());
124 vec.retain(|&x| seen.insert(x));
125 }
126
127 dedup_vec(&mut step_queue);
129 for anchors in step_anchors.values_mut() {
130 dedup_vec(anchors);
131 }
132
133 for &step_ix in &step_queue {
136 let step = &steps[step_ix];
137 let anchors = step_anchors[&step_ix].iter().map(|p| PathSet::clone(p)).collect::<Vec<_>>();
138 step.desc.maybe_run(builder, anchors);
139 }
140}
141
142fn normalize_selector<'a>(builder: &Builder<'_>, path: &'a Path) -> &'a Path {
155 if let Ok(without_dot) = Path::strip_prefix(path, ".")
158 && builder.src.join(path).exists()
159 {
160 without_dot
161 } else if path.is_absolute()
162 && path.exists()
163 && let Ok(relative) = path.strip_prefix(&builder.src)
164 {
165 relative
166 } else {
167 path
168 }
169}