Skip to content

Commit 472c7cd

Browse files
authored
Rollup merge of rust-lang#148317 - Zalathar:cli-paths-mod, r=Kobzol
bootstrap: Extract parts of `bootstrap::core::builder` into a `cli_paths` module One of the things that makes bootstrap's CLI path handling hard to work with is the fact that it's in the middle of a ~2000 line file full of all sorts of other things. And the primary code sequence is in an unhelpfully-named `StepDescription::run` function. This PR therefore pulls some key chunks of code out into a `cli_paths` submodule. This should be a purely non-functional change.
2 parents 43cb58d + 250ee47 commit 472c7cd

File tree

3 files changed

+250
-223
lines changed

3 files changed

+250
-223
lines changed
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
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

Comments
 (0)