Skip to main content

bootstrap/core/builder/
cli_paths.rs

1//! Various pieces of code for dealing with "paths" passed to bootstrap on the
2//! command-line, extracted from `core/builder/mod.rs` because that file is
3//! large and hard to navigate.
4
5use std::fmt::{self, Debug};
6use std::path::PathBuf;
7
8use crate::core::builder::{Builder, Kind, PathSet, ShouldRun, StepDescription};
9
10#[cfg(test)]
11mod tests;
12
13pub(crate) const PATH_REMAP: &[(&str, &[&str])] = &[
14    // bootstrap.toml uses `rust-analyzer-proc-macro-srv`, but the
15    // actual path is `proc-macro-srv-cli`
16    ("rust-analyzer-proc-macro-srv", &["src/tools/rust-analyzer/crates/proc-macro-srv-cli"]),
17    // Make `x test tests` function the same as `x t tests/*`
18    (
19        "tests",
20        &[
21            // tidy-alphabetical-start
22            "tests/assembly-llvm",
23            "tests/build-std",
24            "tests/codegen-llvm",
25            "tests/codegen-units",
26            "tests/coverage",
27            "tests/coverage-run-rustdoc",
28            "tests/crashes",
29            "tests/debuginfo",
30            "tests/incremental",
31            "tests/mir-opt",
32            "tests/pretty",
33            "tests/run-make",
34            "tests/run-make-cargo",
35            "tests/rustdoc-gui",
36            "tests/rustdoc-html",
37            "tests/rustdoc-js",
38            "tests/rustdoc-js-std",
39            "tests/rustdoc-json",
40            "tests/rustdoc-ui",
41            "tests/ui",
42            "tests/ui-fulldeps",
43            // tidy-alphabetical-end
44        ],
45    ),
46];
47
48pub(crate) fn remap_paths(paths: &mut Vec<PathBuf>) {
49    let mut remove = vec![];
50    let mut add = vec![];
51    for (i, path) in paths.iter().enumerate().filter_map(|(i, path)| path.to_str().map(|s| (i, s)))
52    {
53        for &(search, replace) in PATH_REMAP {
54            // Remove leading and trailing slashes so `tests/` and `tests` are equivalent
55            if path.trim_matches(std::path::is_separator) == search {
56                remove.push(i);
57                add.extend(replace.iter().map(PathBuf::from));
58                break;
59            }
60        }
61    }
62    remove.sort();
63    remove.dedup();
64    for idx in remove.into_iter().rev() {
65        paths.remove(idx);
66    }
67    paths.append(&mut add);
68}
69
70#[derive(Clone, PartialEq)]
71pub(crate) struct CLIStepPath {
72    pub(crate) path: PathBuf,
73    pub(crate) will_be_executed: bool,
74}
75
76#[cfg(test)]
77impl CLIStepPath {
78    pub(crate) fn will_be_executed(mut self, will_be_executed: bool) -> Self {
79        self.will_be_executed = will_be_executed;
80        self
81    }
82}
83
84impl Debug for CLIStepPath {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        write!(f, "{}", self.path.display())
87    }
88}
89
90impl From<PathBuf> for CLIStepPath {
91    fn from(path: PathBuf) -> Self {
92        Self { path, will_be_executed: false }
93    }
94}
95
96/// Combines a `StepDescription` with its corresponding `ShouldRun`.
97struct StepExtra<'a> {
98    desc: &'a StepDescription,
99    should_run: ShouldRun<'a>,
100}
101
102struct StepToRun<'a> {
103    sort_index: usize,
104    desc: &'a StepDescription,
105    pathsets: Vec<PathSet>,
106}
107
108pub(crate) fn match_paths_to_steps_and_run(
109    builder: &Builder<'_>,
110    step_descs: &[StepDescription],
111    paths: &[PathBuf],
112) {
113    // Obtain `ShouldRun` information for each step, so that we know which
114    // paths to match it against.
115    let steps = step_descs
116        .iter()
117        .map(|desc| StepExtra {
118            desc,
119            should_run: (desc.should_run)(ShouldRun::new(builder, desc.kind)),
120        })
121        .collect::<Vec<_>>();
122
123    // FIXME(Zalathar): This particular check isn't related to path-to-step
124    // matching, and should probably be hoisted to somewhere much earlier.
125    if builder.download_rustc() && (builder.kind == Kind::Dist || builder.kind == Kind::Install) {
126        eprintln!(
127            "ERROR: '{}' subcommand is incompatible with `rust.download-rustc`.",
128            builder.kind.as_str()
129        );
130        crate::exit!(1);
131    }
132
133    // sanity checks on rules
134    for StepExtra { desc, should_run } in &steps {
135        assert!(!should_run.paths.is_empty(), "{:?} should have at least one pathset", desc.name);
136    }
137
138    if paths.is_empty() || builder.config.include_default_paths {
139        for StepExtra { desc, should_run } in &steps {
140            if (desc.is_default_step_fn)(builder) {
141                desc.maybe_run(builder, should_run.paths.iter().cloned().collect());
142            }
143        }
144    }
145
146    // Attempt to resolve paths to be relative to the builder source directory.
147    let mut paths: Vec<PathBuf> = paths
148        .iter()
149        .map(|original_path| {
150            let mut path = original_path.clone();
151
152            // Someone could run `x <cmd> <path>` from a different repository than the source
153            // directory.
154            // In that case, we should not try to resolve the paths relative to the working
155            // directory, but rather relative to the source directory.
156            // So we forcefully "relocate" the path to the source directory here.
157            if !path.is_absolute() {
158                path = builder.src.join(path);
159            }
160
161            // If the path does not exist, it may represent the name of a Step, such as `tidy` in `x test tidy`
162            if !path.exists() {
163                // Use the original path here
164                return original_path.clone();
165            }
166
167            // Make the path absolute, strip the prefix, and convert to a PathBuf.
168            match std::path::absolute(&path) {
169                Ok(p) => p.strip_prefix(&builder.src).unwrap_or(&p).to_path_buf(),
170                Err(e) => {
171                    eprintln!("ERROR: {e:?}");
172                    panic!("Due to the above error, failed to resolve path: {path:?}");
173                }
174            }
175        })
176        .collect();
177
178    remap_paths(&mut paths);
179
180    // Handle all test suite paths.
181    // (This is separate from the loop below to avoid having to handle multiple paths in `is_suite_path` somehow.)
182    paths.retain(|path| {
183        for StepExtra { desc, should_run } in &steps {
184            if let Some(suite) = should_run.is_suite_path(path) {
185                desc.maybe_run(builder, vec![suite.clone()]);
186                return false;
187            }
188        }
189        true
190    });
191
192    if paths.is_empty() {
193        return;
194    }
195
196    let mut paths: Vec<CLIStepPath> = paths.into_iter().map(|p| p.into()).collect();
197    let mut path_lookup: Vec<(CLIStepPath, bool)> =
198        paths.clone().into_iter().map(|p| (p, false)).collect();
199
200    // Before actually running (non-suite) steps, collect them into a list of structs
201    // so that we can then sort the list to preserve CLI order as much as possible.
202    let mut steps_to_run = vec![];
203
204    for StepExtra { desc, should_run } in &steps {
205        let pathsets = should_run.pathset_for_paths_removing_matches(&mut paths, desc.kind);
206
207        // This value is used for sorting the step execution order.
208        // By default, `usize::MAX` is used as the index for steps to assign them the lowest priority.
209        //
210        // If we resolve the step's path from the given CLI input, this value will be updated with
211        // the step's actual index.
212        let mut closest_index = usize::MAX;
213
214        // Find the closest index from the original list of paths given by the CLI input.
215        for (index, (path, is_used)) in path_lookup.iter_mut().enumerate() {
216            if !*is_used && !paths.contains(path) {
217                closest_index = index;
218                *is_used = true;
219                break;
220            }
221        }
222
223        steps_to_run.push(StepToRun { sort_index: closest_index, desc, pathsets });
224    }
225
226    // Sort the steps before running them to respect the CLI order.
227    steps_to_run.sort_by_key(|step| step.sort_index);
228
229    // Handle all PathSets.
230    for StepToRun { sort_index: _, desc, pathsets } in steps_to_run {
231        if !pathsets.is_empty() {
232            desc.maybe_run(builder, pathsets);
233        }
234    }
235
236    paths.retain(|p| !p.will_be_executed);
237
238    if !paths.is_empty() {
239        eprintln!("ERROR: no `{}` rules matched {:?}", builder.kind.as_str(), paths);
240        eprintln!(
241            "HELP: run `x.py {} --help --verbose` to show a list of available paths",
242            builder.kind.as_str()
243        );
244        eprintln!(
245            "NOTE: if you are adding a new Step to bootstrap itself, make sure you register it with `describe!`"
246        );
247        crate::exit!(1);
248    }
249}