blob: fef1979465e8b8c9d6762372bda787fee7c74d44 [file] [log] [blame]
//! Various pieces of code for dealing with "paths" passed to bootstrap on the
//! command-line, extracted from `core/builder/mod.rs` because that file is
//! large and hard to navigate.
use std::fmt::{self, Debug};
use std::path::PathBuf;
use crate::core::builder::{Builder, Kind, PathSet, ShouldRun, StepDescription};
#[cfg(test)]
mod tests;
pub(crate) const PATH_REMAP: &[(&str, &[&str])] = &[
// bootstrap.toml uses `rust-analyzer-proc-macro-srv`, but the
// actual path is `proc-macro-srv-cli`
("rust-analyzer-proc-macro-srv", &["src/tools/rust-analyzer/crates/proc-macro-srv-cli"]),
// Make `x test tests` function the same as `x t tests/*`
(
"tests",
&[
// tidy-alphabetical-start
"tests/assembly-llvm",
"tests/codegen-llvm",
"tests/codegen-units",
"tests/coverage",
"tests/coverage-run-rustdoc",
"tests/crashes",
"tests/debuginfo",
"tests/incremental",
"tests/mir-opt",
"tests/pretty",
"tests/run-make",
"tests/run-make-cargo",
"tests/rustdoc",
"tests/rustdoc-gui",
"tests/rustdoc-js",
"tests/rustdoc-js-std",
"tests/rustdoc-json",
"tests/rustdoc-ui",
"tests/ui",
"tests/ui-fulldeps",
// tidy-alphabetical-end
],
),
];
pub(crate) fn remap_paths(paths: &mut Vec<PathBuf>) {
let mut remove = vec![];
let mut add = vec![];
for (i, path) in paths.iter().enumerate().filter_map(|(i, path)| path.to_str().map(|s| (i, s)))
{
for &(search, replace) in PATH_REMAP {
// Remove leading and trailing slashes so `tests/` and `tests` are equivalent
if path.trim_matches(std::path::is_separator) == search {
remove.push(i);
add.extend(replace.iter().map(PathBuf::from));
break;
}
}
}
remove.sort();
remove.dedup();
for idx in remove.into_iter().rev() {
paths.remove(idx);
}
paths.append(&mut add);
}
#[derive(Clone, PartialEq)]
pub(crate) struct CLIStepPath {
pub(crate) path: PathBuf,
pub(crate) will_be_executed: bool,
}
#[cfg(test)]
impl CLIStepPath {
pub(crate) fn will_be_executed(mut self, will_be_executed: bool) -> Self {
self.will_be_executed = will_be_executed;
self
}
}
impl Debug for CLIStepPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.path.display())
}
}
impl From<PathBuf> for CLIStepPath {
fn from(path: PathBuf) -> Self {
Self { path, will_be_executed: false }
}
}
/// Combines a `StepDescription` with its corresponding `ShouldRun`.
struct StepExtra<'a> {
desc: &'a StepDescription,
should_run: ShouldRun<'a>,
}
struct StepToRun<'a> {
sort_index: usize,
desc: &'a StepDescription,
pathsets: Vec<PathSet>,
}
pub(crate) fn match_paths_to_steps_and_run(
builder: &Builder<'_>,
step_descs: &[StepDescription],
paths: &[PathBuf],
) {
// Obtain `ShouldRun` information for each step, so that we know which
// paths to match it against.
let steps = step_descs
.iter()
.map(|desc| StepExtra {
desc,
should_run: (desc.should_run)(ShouldRun::new(builder, desc.kind)),
})
.collect::<Vec<_>>();
// FIXME(Zalathar): This particular check isn't related to path-to-step
// matching, and should probably be hoisted to somewhere much earlier.
if builder.download_rustc() && (builder.kind == Kind::Dist || builder.kind == Kind::Install) {
eprintln!(
"ERROR: '{}' subcommand is incompatible with `rust.download-rustc`.",
builder.kind.as_str()
);
crate::exit!(1);
}
// sanity checks on rules
for StepExtra { desc, should_run } in &steps {
assert!(!should_run.paths.is_empty(), "{:?} should have at least one pathset", desc.name);
}
if paths.is_empty() || builder.config.include_default_paths {
for StepExtra { desc, should_run } in &steps {
if desc.default && should_run.is_really_default() {
desc.maybe_run(builder, should_run.paths.iter().cloned().collect());
}
}
}
// Attempt to resolve paths to be relative to the builder source directory.
let mut paths: Vec<PathBuf> = paths
.iter()
.map(|original_path| {
let mut path = original_path.clone();
// Someone could run `x <cmd> <path>` from a different repository than the source
// directory.
// In that case, we should not try to resolve the paths relative to the working
// directory, but rather relative to the source directory.
// So we forcefully "relocate" the path to the source directory here.
if !path.is_absolute() {
path = builder.src.join(path);
}
// If the path does not exist, it may represent the name of a Step, such as `tidy` in `x test tidy`
if !path.exists() {
// Use the original path here
return original_path.clone();
}
// Make the path absolute, strip the prefix, and convert to a PathBuf.
match std::path::absolute(&path) {
Ok(p) => p.strip_prefix(&builder.src).unwrap_or(&p).to_path_buf(),
Err(e) => {
eprintln!("ERROR: {e:?}");
panic!("Due to the above error, failed to resolve path: {path:?}");
}
}
})
.collect();
remap_paths(&mut paths);
// Handle all test suite paths.
// (This is separate from the loop below to avoid having to handle multiple paths in `is_suite_path` somehow.)
paths.retain(|path| {
for StepExtra { desc, should_run } in &steps {
if let Some(suite) = should_run.is_suite_path(path) {
desc.maybe_run(builder, vec![suite.clone()]);
return false;
}
}
true
});
if paths.is_empty() {
return;
}
let mut paths: Vec<CLIStepPath> = paths.into_iter().map(|p| p.into()).collect();
let mut path_lookup: Vec<(CLIStepPath, bool)> =
paths.clone().into_iter().map(|p| (p, false)).collect();
// Before actually running (non-suite) steps, collect them into a list of structs
// so that we can then sort the list to preserve CLI order as much as possible.
let mut steps_to_run = vec![];
for StepExtra { desc, should_run } in &steps {
let pathsets = should_run.pathset_for_paths_removing_matches(&mut paths, desc.kind);
// This value is used for sorting the step execution order.
// By default, `usize::MAX` is used as the index for steps to assign them the lowest priority.
//
// If we resolve the step's path from the given CLI input, this value will be updated with
// the step's actual index.
let mut closest_index = usize::MAX;
// Find the closest index from the original list of paths given by the CLI input.
for (index, (path, is_used)) in path_lookup.iter_mut().enumerate() {
if !*is_used && !paths.contains(path) {
closest_index = index;
*is_used = true;
break;
}
}
steps_to_run.push(StepToRun { sort_index: closest_index, desc, pathsets });
}
// Sort the steps before running them to respect the CLI order.
steps_to_run.sort_by_key(|step| step.sort_index);
// Handle all PathSets.
for StepToRun { sort_index: _, desc, pathsets } in steps_to_run {
if !pathsets.is_empty() {
desc.maybe_run(builder, pathsets);
}
}
paths.retain(|p| !p.will_be_executed);
if !paths.is_empty() {
eprintln!("ERROR: no `{}` rules matched {:?}", builder.kind.as_str(), paths);
eprintln!(
"HELP: run `x.py {} --help --verbose` to show a list of available paths",
builder.kind.as_str()
);
eprintln!(
"NOTE: if you are adding a new Step to bootstrap itself, make sure you register it with `describe!`"
);
crate::exit!(1);
}
}