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