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, CommandLineStepDescription, Kind, PathSet, ShouldRun};
9
10#[cfg(test)]
11mod tests;
12
13#[derive(Clone, PartialEq)]
14pub(crate) struct CLIStepPath {
15    pub(crate) path: PathBuf,
16    pub(crate) will_be_executed: bool,
17}
18
19impl Debug for CLIStepPath {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(f, "{}", self.path.display())
22    }
23}
24
25impl From<PathBuf> for CLIStepPath {
26    fn from(path: PathBuf) -> Self {
27        Self { path, will_be_executed: false }
28    }
29}
30
31/// Combines a [`CommandLineStepDescription`] with its corresponding [`ShouldRun`].
32struct StepExtra<'a> {
33    desc: &'a CommandLineStepDescription,
34    should_run: ShouldRun<'a>,
35}
36
37struct StepToRun<'a> {
38    sort_index: usize,
39    desc: &'a CommandLineStepDescription,
40    pathsets: Vec<PathSet>,
41}
42
43pub(crate) fn match_paths_to_steps_and_run(
44    builder: &Builder<'_>,
45    step_descs: &[CommandLineStepDescription],
46    paths: &[PathBuf],
47) {
48    // Obtain `ShouldRun` information for each step, so that we know which
49    // paths to match it against.
50    let steps = step_descs
51        .iter()
52        .map(|desc| StepExtra { desc, should_run: (desc.should_run)(ShouldRun::new(builder)) })
53        .collect::<Vec<_>>();
54
55    // FIXME(Zalathar): This particular check isn't related to path-to-step
56    // matching, and should probably be hoisted to somewhere much earlier.
57    if builder.download_rustc() && (builder.kind == Kind::Dist || builder.kind == Kind::Install) {
58        eprintln!(
59            "ERROR: '{}' subcommand is incompatible with `rust.download-rustc`.",
60            builder.kind.as_str()
61        );
62        crate::exit!(1);
63    }
64
65    // sanity checks on rules
66    for StepExtra { desc, should_run } in &steps {
67        assert!(!should_run.paths.is_empty(), "{:?} should have at least one pathset", desc.name);
68    }
69
70    if paths.is_empty() || builder.config.include_default_paths {
71        for StepExtra { desc, should_run } in &steps {
72            if (desc.is_default_step_fn)(builder) {
73                let default_pathsets = should_run.default_pathsets();
74                desc.maybe_run(builder, default_pathsets);
75            }
76        }
77    }
78
79    // Command-line paths are interpreted relative to the repository root
80    // (not the current working directory).
81    //
82    // If the user or shell passed an absolute path, try to strip off the
83    // repository root, to match the paths registered by command-line steps.
84    //
85    // E.g. `/home/ferris/rust/tests/ui/asm/cfg.rs` => `tests/ui/asm/cfg.rs`
86    let mut paths = paths
87        .iter()
88        .map(|path| {
89            if path.is_absolute()
90                && path.exists()
91                && let Ok(relative) = path.strip_prefix(&builder.src)
92            {
93                relative
94            } else {
95                path
96            }
97        })
98        .map(|p| p.to_owned())
99        .collect::<Vec<_>>();
100
101    // If any absolute paths couldn't be made relative, stop now and report them.
102    let bad_abs_paths = paths.iter().filter(|path| path.is_absolute()).collect::<Vec<_>>();
103    if !bad_abs_paths.is_empty() {
104        eprintln!("ERROR: failed to resolve absolute paths: {bad_abs_paths:#?}");
105        crate::exit!(1);
106    }
107
108    // Handle all test suite paths.
109    // (This is separate from the loop below to avoid having to handle multiple paths in `is_suite_path` somehow.)
110    paths.retain(|path| {
111        for StepExtra { desc, should_run } in &steps {
112            if let Some(suite) = should_run.is_suite_path(path) {
113                desc.maybe_run(builder, vec![suite.clone()]);
114                return false;
115            }
116        }
117        true
118    });
119
120    if paths.is_empty() {
121        return;
122    }
123
124    let mut paths: Vec<CLIStepPath> = paths.into_iter().map(|p| p.into()).collect();
125    let mut path_lookup: Vec<(CLIStepPath, bool)> =
126        paths.clone().into_iter().map(|p| (p, false)).collect();
127
128    // Before actually running (non-suite) steps, collect them into a list of structs
129    // so that we can then sort the list to preserve CLI order as much as possible.
130    let mut steps_to_run = vec![];
131
132    for StepExtra { desc, should_run } in &steps {
133        let pathsets = should_run.pathsets_for_paths_flagging_matches(&mut paths);
134
135        // This value is used for sorting the step execution order.
136        // By default, `usize::MAX` is used as the index for steps to assign them the lowest priority.
137        //
138        // If we resolve the step's path from the given CLI input, this value will be updated with
139        // the step's actual index.
140        let mut closest_index = usize::MAX;
141
142        // Find the closest index from the original list of paths given by the CLI input.
143        for (index, (path, is_used)) in path_lookup.iter_mut().enumerate() {
144            if !*is_used && !paths.contains(path) {
145                closest_index = index;
146                *is_used = true;
147                break;
148            }
149        }
150
151        steps_to_run.push(StepToRun { sort_index: closest_index, desc, pathsets });
152    }
153
154    // Sort the steps before running them to respect the CLI order.
155    steps_to_run.sort_by_key(|step| step.sort_index);
156
157    // Handle all PathSets.
158    for StepToRun { sort_index: _, desc, pathsets } in steps_to_run {
159        if !pathsets.is_empty() {
160            desc.maybe_run(builder, pathsets);
161        }
162    }
163
164    paths.retain(|p| !p.will_be_executed);
165
166    if !paths.is_empty() {
167        eprintln!("ERROR: no `{}` rules matched {:?}", builder.kind.as_str(), paths);
168        eprintln!(
169            "HELP: run `x.py {} --help --verbose` to show a list of available paths",
170            builder.kind.as_str()
171        );
172        eprintln!(
173            "NOTE: if you are adding a new Step to bootstrap itself, make sure you register it with `describe!`"
174        );
175        crate::exit!(1);
176    }
177}