|
| 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 | +
|
| 5 | +use std::fmt::{self, Debug}; |
| 6 | +use std::path::PathBuf; |
| 7 | + |
| 8 | +use crate::core::builder::{Builder, Kind, PathSet, ShouldRun, StepDescription}; |
| 9 | + |
| 10 | +pub(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 | + |
| 44 | +pub(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)] |
| 67 | +pub(crate) struct CLIStepPath { |
| 68 | + pub(crate) path: PathBuf, |
| 69 | + pub(crate) will_be_executed: bool, |
| 70 | +} |
| 71 | + |
| 72 | +#[cfg(test)] |
| 73 | +impl 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 | + |
| 80 | +impl Debug for CLIStepPath { |
| 81 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 82 | + write!(f, "{}", self.path.display()) |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +impl 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`. |
| 93 | +struct StepExtra<'a> { |
| 94 | + desc: &'a StepDescription, |
| 95 | + should_run: ShouldRun<'a>, |
| 96 | +} |
| 97 | + |
| 98 | +struct StepToRun<'a> { |
| 99 | + sort_index: usize, |
| 100 | + desc: &'a StepDescription, |
| 101 | + pathsets: Vec<PathSet>, |
| 102 | +} |
| 103 | + |
| 104 | +pub(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 | +} |
0 commit comments