blob: 4008ede6c0d4c2ab7eddbec774579972149bef00 [file] [log] [blame]
//! Build-and-run steps for `./x.py test` test fixtures
//!
//! `./x.py test` (aka [`Kind::Test`]) is currently allowed to reach build steps in other modules.
//! However, this contains ~all test parts we expect people to be able to build and run locally.
// (This file should be split up, but having tidy block all changes is not helpful.)
// ignore-tidy-filelength
use std::collections::HashSet;
use std::env::split_paths;
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::{env, fs, iter};
use build_helper::exit;
use crate::core::build_steps::compile::{Std, run_cargo};
use crate::core::build_steps::doc::{DocumentationFormat, prepare_doc_compiler};
use crate::core::build_steps::gcc::{Gcc, GccTargetPair, add_cg_gcc_cargo_flags};
use crate::core::build_steps::llvm::get_llvm_version;
use crate::core::build_steps::run::{get_completion_paths, get_help_path};
use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget;
use crate::core::build_steps::test::compiletest::CompiletestMode;
use crate::core::build_steps::tool::{
self, RustcPrivateCompilers, SourceType, TEST_FLOAT_PARSE_ALLOW_FEATURES, Tool,
ToolTargetBuildMode, get_tool_target_compiler,
};
use crate::core::build_steps::toolstate::ToolState;
use crate::core::build_steps::{compile, dist, llvm};
use crate::core::builder::{
self, Alias, Builder, Compiler, Kind, RunConfig, ShouldRun, Step, StepMetadata,
crate_description,
};
use crate::core::config::TargetSelection;
use crate::core::config::flags::{Subcommand, get_completion, top_level_help};
use crate::core::{android, debuggers};
use crate::utils::build_stamp::{self, BuildStamp};
use crate::utils::exec::{BootstrapCommand, command};
use crate::utils::helpers::{
self, LldThreads, add_dylib_path, add_rustdoc_cargo_linker_args, dylib_path, dylib_path_var,
linker_args, linker_flags, t, target_supports_cranelift_backend, up_to_date,
};
use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests};
use crate::{CLang, CodegenBackendKind, DocTests, GitRepo, Mode, PathSet, envify};
mod compiletest;
/// Runs `cargo test` on various internal tools used by bootstrap.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CrateBootstrap {
path: PathBuf,
host: TargetSelection,
}
impl Step for CrateBootstrap {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
// This step is responsible for several different tool paths.
//
// By default, it will test all of them, but requesting specific tools on the command-line
// (e.g. `./x test src/tools/coverage-dump`) will test only the specified tools.
run.path("src/tools/jsondoclint")
.path("src/tools/replace-version-placeholder")
.path("src/tools/coverage-dump")
// We want `./x test tidy` to _run_ the tidy tool, not its tests.
// So we need a separate alias to test the tidy tool itself.
.alias("tidyselftest")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
// Create and ensure a separate instance of this step for each path
// that was selected on the command-line (or selected by default).
for path in run.paths {
let path = path.assert_single_path().path.clone();
run.builder.ensure(CrateBootstrap { host: run.target, path });
}
}
fn run(self, builder: &Builder<'_>) {
let bootstrap_host = builder.config.host_target;
let compiler = builder.compiler(0, bootstrap_host);
let mut path = self.path.to_str().unwrap();
// Map alias `tidyselftest` back to the actual crate path of tidy.
if path == "tidyselftest" {
path = "src/tools/tidy";
}
let cargo = tool::prepare_tool_cargo(
builder,
compiler,
Mode::ToolBootstrap,
bootstrap_host,
Kind::Test,
path,
SourceType::InTree,
&[],
);
let crate_name = path.rsplit_once('/').unwrap().1;
run_cargo_test(cargo, &[], &[], crate_name, bootstrap_host, builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(
StepMetadata::test("crate-bootstrap", self.host)
.with_metadata(self.path.as_path().to_string_lossy().to_string()),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Linkcheck {
host: TargetSelection,
}
impl Step for Linkcheck {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/linkchecker")
}
fn is_default_step(builder: &Builder<'_>) -> bool {
builder.config.docs
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Linkcheck { host: run.target });
}
/// Runs the `linkchecker` tool as compiled in `stage` by the `host` compiler.
///
/// This tool in `src/tools` will verify the validity of all our links in the
/// documentation to ensure we don't have a bunch of dead ones.
fn run(self, builder: &Builder<'_>) {
let host = self.host;
let hosts = &builder.hosts;
let targets = &builder.targets;
// if we have different hosts and targets, some things may be built for
// the host (e.g. rustc) and others for the target (e.g. std). The
// documentation built for each will contain broken links to
// docs built for the other platform (e.g. rustc linking to cargo)
if (hosts != targets) && !hosts.is_empty() && !targets.is_empty() {
panic!(
"Linkcheck currently does not support builds with different hosts and targets.
You can skip linkcheck with --skip src/tools/linkchecker"
);
}
builder.info(&format!("Linkcheck ({host})"));
// Test the linkchecker itself.
let bootstrap_host = builder.config.host_target;
let compiler = builder.compiler(0, bootstrap_host);
let cargo = tool::prepare_tool_cargo(
builder,
compiler,
Mode::ToolBootstrap,
bootstrap_host,
Kind::Test,
"src/tools/linkchecker",
SourceType::InTree,
&[],
);
run_cargo_test(cargo, &[], &[], "linkchecker self tests", bootstrap_host, builder);
if builder.doc_tests == DocTests::No {
return;
}
// Build all the default documentation.
builder.run_default_doc_steps();
// Build the linkchecker before calling `msg`, since GHA doesn't support nested groups.
let linkchecker = builder.tool_cmd(Tool::Linkchecker);
// Run the linkchecker.
let _guard = builder.msg_test("Linkcheck", bootstrap_host, 1);
let _time = helpers::timeit(builder);
linkchecker.delay_failure().arg(builder.out.join(host).join("doc")).run(builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("link-check", self.host))
}
}
fn check_if_tidy_is_installed(builder: &Builder<'_>) -> bool {
command("tidy")
.allow_failure()
.arg("--version")
// Cache the output to avoid running this command more than once (per builder).
.cached()
.run_capture_stdout(builder)
.is_success()
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HtmlCheck {
target: TargetSelection,
}
impl Step for HtmlCheck {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/html-checker")
}
fn is_default_step(builder: &Builder<'_>) -> bool {
check_if_tidy_is_installed(builder)
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(HtmlCheck { target: run.target });
}
fn run(self, builder: &Builder<'_>) {
if !check_if_tidy_is_installed(builder) {
eprintln!("not running HTML-check tool because `tidy` is missing");
eprintln!(
"You need the HTML tidy tool https://www.html-tidy.org/, this tool is *not* part of the rust project and needs to be installed separately, for example via your package manager."
);
panic!("Cannot run html-check tests");
}
// Ensure that a few different kinds of documentation are available.
builder.run_default_doc_steps();
builder.ensure(crate::core::build_steps::doc::Rustc::for_stage(
builder,
builder.top_stage,
self.target,
));
builder
.tool_cmd(Tool::HtmlChecker)
.delay_failure()
.arg(builder.doc_out(self.target))
.run(builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("html-check", self.target))
}
}
/// Builds cargo and then runs the `src/tools/cargotest` tool, which checks out
/// some representative crate repositories and runs `cargo test` on them, in
/// order to test cargo.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Cargotest {
build_compiler: Compiler,
host: TargetSelection,
}
impl Step for Cargotest {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/cargotest")
}
fn make_run(run: RunConfig<'_>) {
if run.builder.top_stage == 0 {
eprintln!(
"ERROR: running cargotest with stage 0 is currently unsupported. Use at least stage 1."
);
exit!(1);
}
// We want to build cargo stage N (where N == top_stage), and rustc stage N,
// and test both of these together.
// So we need to get a build compiler stage N-1 to build the stage N components.
run.builder.ensure(Cargotest {
build_compiler: run.builder.compiler(run.builder.top_stage - 1, run.target),
host: run.target,
});
}
/// Runs the `cargotest` tool as compiled in `stage` by the `host` compiler.
///
/// This tool in `src/tools` will check out a few Rust projects and run `cargo
/// test` to ensure that we don't regress the test suites there.
fn run(self, builder: &Builder<'_>) {
// cargotest's staging has several pieces:
// consider ./x test cargotest --stage=2.
//
// The test goal is to exercise a (stage 2 cargo, stage 2 rustc) pair through a stage 2
// cargotest tool.
// To produce the stage 2 cargo and cargotest, we need to do so with the stage 1 rustc and std.
// Importantly, the stage 2 rustc being tested (`tested_compiler`) via stage 2 cargotest is
// the rustc built by an earlier stage 1 rustc (the build_compiler). These are two different
// compilers!
let cargo =
builder.ensure(tool::Cargo::from_build_compiler(self.build_compiler, self.host));
let tested_compiler = builder.compiler(self.build_compiler.stage + 1, self.host);
builder.std(tested_compiler, self.host);
// Note that this is a short, cryptic, and not scoped directory name. This
// is currently to minimize the length of path on Windows where we otherwise
// quickly run into path name limit constraints.
let out_dir = builder.out.join("ct");
t!(fs::create_dir_all(&out_dir));
let _time = helpers::timeit(builder);
let mut cmd = builder.tool_cmd(Tool::CargoTest);
cmd.arg(&cargo.tool_path)
.arg(&out_dir)
.args(builder.config.test_args())
.env("RUSTC", builder.rustc(tested_compiler))
.env("RUSTDOC", builder.rustdoc_for_compiler(tested_compiler));
add_rustdoc_cargo_linker_args(&mut cmd, builder, tested_compiler.host, LldThreads::No);
cmd.delay_failure().run(builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("cargotest", self.host).stage(self.build_compiler.stage + 1))
}
}
/// Runs `cargo test` for cargo itself.
/// We label these tests as "cargo self-tests".
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Cargo {
build_compiler: Compiler,
host: TargetSelection,
}
impl Cargo {
const CRATE_PATH: &str = "src/tools/cargo";
}
impl Step for Cargo {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path(Self::CRATE_PATH)
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Cargo {
build_compiler: get_tool_target_compiler(
run.builder,
ToolTargetBuildMode::Build(run.target),
),
host: run.target,
});
}
/// Runs `cargo test` for `cargo` packaged with Rust.
fn run(self, builder: &Builder<'_>) {
// When we do a "stage 1 cargo self-test", it means that we test the stage 1 rustc
// using stage 1 cargo. So we actually build cargo using the stage 0 compiler, and then
// run its tests against the stage 1 compiler (called `tested_compiler` below).
builder.ensure(tool::Cargo::from_build_compiler(self.build_compiler, self.host));
let tested_compiler = builder.compiler(self.build_compiler.stage + 1, self.host);
builder.std(tested_compiler, self.host);
// We also need to build rustdoc for cargo tests
// It will be located in the bindir of `tested_compiler`, so we don't need to explicitly
// pass its path to Cargo.
builder.rustdoc_for_compiler(tested_compiler);
let cargo = tool::prepare_tool_cargo(
builder,
self.build_compiler,
Mode::ToolTarget,
self.host,
Kind::Test,
Self::CRATE_PATH,
SourceType::Submodule,
&[],
);
// NOTE: can't use `run_cargo_test` because we need to overwrite `PATH`
let mut cargo = prepare_cargo_test(cargo, &[], &[], self.host, builder);
// Don't run cross-compile tests, we may not have cross-compiled libstd libs
// available.
cargo.env("CFG_DISABLE_CROSS_TESTS", "1");
// Forcibly disable tests using nightly features since any changes to
// those features won't be able to land.
cargo.env("CARGO_TEST_DISABLE_NIGHTLY", "1");
// Configure PATH to find the right rustc. NB. we have to use PATH
// and not RUSTC because the Cargo test suite has tests that will
// fail if rustc is not spelled `rustc`.
cargo.env("PATH", bin_path_for_cargo(builder, tested_compiler));
// The `cargo` command configured above has dylib dir path set to the `build_compiler`'s
// libdir. That causes issues in cargo test, because the programs that cargo compiles are
// incorrectly picking that libdir, even though they should be picking the
// `tested_compiler`'s libdir. We thus have to override the precedence here.
let mut existing_dylib_paths = cargo
.get_envs()
.find(|(k, _)| *k == OsStr::new(dylib_path_var()))
.and_then(|(_, v)| v)
.map(|value| split_paths(value).collect::<Vec<PathBuf>>())
.unwrap_or_default();
existing_dylib_paths.insert(0, builder.rustc_libdir(tested_compiler));
add_dylib_path(existing_dylib_paths, &mut cargo);
// Cargo's test suite uses `CARGO_RUSTC_CURRENT_DIR` to determine the path that `file!` is
// relative to. Cargo no longer sets this env var, so we have to do that. This has to be the
// same value as `-Zroot-dir`.
cargo.env("CARGO_RUSTC_CURRENT_DIR", builder.src.display().to_string());
#[cfg(feature = "build-metrics")]
builder.metrics.begin_test_suite(
build_helper::metrics::TestSuiteMetadata::CargoPackage {
crates: vec!["cargo".into()],
target: self.host.triple.to_string(),
host: self.host.triple.to_string(),
stage: self.build_compiler.stage + 1,
},
builder,
);
let _time = helpers::timeit(builder);
add_flags_and_try_run_tests(builder, &mut cargo);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("cargo", self.host).built_by(self.build_compiler))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RustAnalyzer {
compilers: RustcPrivateCompilers,
}
impl Step for RustAnalyzer {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/rust-analyzer")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Self {
compilers: RustcPrivateCompilers::new(
run.builder,
run.builder.top_stage,
run.builder.host_target,
),
});
}
/// Runs `cargo test` for rust-analyzer
fn run(self, builder: &Builder<'_>) {
let build_compiler = self.compilers.build_compiler();
let target = self.compilers.target();
// NOTE: rust-analyzer repo currently (as of 2025-12-11) does not run tests against 32-bit
// targets, so we also don't run them in rust-lang/rust CI (because that will just mean that
// subtree syncs will keep getting 32-bit-specific failures that are not observed in
// rust-analyzer repo CI).
//
// Some 32-bit specific failures include e.g. target pointer width specific hashes.
// FIXME: eventually, we should probably reduce the amount of target tuple substring
// matching in bootstrap.
if target.starts_with("i686") {
return;
}
let mut cargo = tool::prepare_tool_cargo(
builder,
build_compiler,
Mode::ToolRustcPrivate,
target,
Kind::Test,
"src/tools/rust-analyzer",
SourceType::InTree,
&["in-rust-tree".to_owned()],
);
cargo.allow_features(tool::RustAnalyzer::ALLOW_FEATURES);
// N.B. it turns out _setting_ `CARGO_WORKSPACE_DIR` actually somehow breaks `expect-test`,
// even though previously we actually needed to set that hack to allow `expect-test` to
// correctly discover the r-a workspace instead of the outer r-l/r workspace.
// FIXME: RA's test suite tries to write to the source directory, that can't work in Rust CI
// without properly wiring up the writable test dir.
cargo.env("SKIP_SLOW_TESTS", "1");
// NOTE: we need to skip `src/tools/rust-analyzer/xtask` as they seem to exercise rustup /
// stable rustfmt.
//
// NOTE: you can only skip a specific workspace package via `--exclude=...` if you *also*
// specify `--workspace`.
cargo.arg("--workspace");
cargo.arg("--exclude=xtask");
let mut skip_tests = vec![];
// NOTE: the following test skips is a bit cheeky in that it assumes there are no
// identically named tests across different r-a packages, where we want to run the
// identically named test in one package but not another. If we want to support that use
// case, we'd have to run the r-a tests in two batches (with one excluding the package that
// we *don't* want to run the test for, and the other batch including).
// Across all platforms.
skip_tests.extend_from_slice(&[
// FIXME: this test wants to find a `rustc`. We need to provide it with a path to staged
// in-tree `rustc`, but setting `RUSTC` env var requires some reworking of bootstrap.
"tests::smoke_test_real_sysroot_cargo",
// NOTE: part of `smol-str` test suite; this tries to access a stable rustfmt from the
// environment, which is not something we want to do.
"check_code_formatting",
]);
let skip_tests = skip_tests.iter().map(|name| format!("--skip={name}")).collect::<Vec<_>>();
let skip_tests = skip_tests.iter().map(|s| s.as_str()).collect::<Vec<_>>();
cargo.add_rustc_lib_path(builder);
run_cargo_test(cargo, skip_tests.as_slice(), &[], "rust-analyzer", target, builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(
StepMetadata::test("rust-analyzer", self.compilers.target())
.built_by(self.compilers.build_compiler()),
)
}
}
/// Runs `cargo test` for rustfmt.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Rustfmt {
compilers: RustcPrivateCompilers,
}
impl Step for Rustfmt {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/rustfmt")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Rustfmt {
compilers: RustcPrivateCompilers::new(
run.builder,
run.builder.top_stage,
run.builder.host_target,
),
});
}
/// Runs `cargo test` for rustfmt.
fn run(self, builder: &Builder<'_>) {
let build_compiler = self.compilers.build_compiler();
let target = self.compilers.target();
let mut cargo = tool::prepare_tool_cargo(
builder,
build_compiler,
Mode::ToolRustcPrivate,
target,
Kind::Test,
"src/tools/rustfmt",
SourceType::InTree,
&[],
);
let dir = testdir(builder, target);
t!(fs::create_dir_all(&dir));
cargo.env("RUSTFMT_TEST_DIR", dir);
cargo.add_rustc_lib_path(builder);
run_cargo_test(cargo, &[], &[], "rustfmt", target, builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(
StepMetadata::test("rustfmt", self.compilers.target())
.built_by(self.compilers.build_compiler()),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Miri {
target: TargetSelection,
}
impl Miri {
/// Run `cargo miri setup` for the given target, return where the Miri sysroot was put.
pub fn build_miri_sysroot(
builder: &Builder<'_>,
compiler: Compiler,
target: TargetSelection,
) -> PathBuf {
let miri_sysroot = builder.out.join(compiler.host).join("miri-sysroot");
let mut cargo = builder::Cargo::new(
builder,
compiler,
Mode::Std,
SourceType::Submodule,
target,
Kind::MiriSetup,
);
// Tell `cargo miri setup` where to find the sources.
cargo.env("MIRI_LIB_SRC", builder.src.join("library"));
// Tell it where to put the sysroot.
cargo.env("MIRI_SYSROOT", &miri_sysroot);
let mut cargo = BootstrapCommand::from(cargo);
let _guard =
builder.msg(Kind::Build, "miri sysroot", Mode::ToolRustcPrivate, compiler, target);
cargo.run(builder);
// # Determine where Miri put its sysroot.
// To this end, we run `cargo miri setup --print-sysroot` and capture the output.
// (We do this separately from the above so that when the setup actually
// happens we get some output.)
// We re-use the `cargo` from above.
cargo.arg("--print-sysroot");
builder.do_if_verbose(|| println!("running: {cargo:?}"));
let stdout = cargo.run_capture_stdout(builder).stdout();
// Output is "<sysroot>\n".
let sysroot = stdout.trim_end();
builder.do_if_verbose(|| println!("`cargo miri setup --print-sysroot` said: {sysroot:?}"));
PathBuf::from(sysroot)
}
}
impl Step for Miri {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/miri")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Miri { target: run.target });
}
/// Runs `cargo test` for miri.
fn run(self, builder: &Builder<'_>) {
let host = builder.build.host_target;
let target = self.target;
let stage = builder.top_stage;
if stage == 0 {
eprintln!("miri cannot be tested at stage 0");
std::process::exit(1);
}
// This compiler runs on the host, we'll just use it for the target.
let compilers = RustcPrivateCompilers::new(builder, stage, host);
// Build our tools.
let miri = builder.ensure(tool::Miri::from_compilers(compilers));
// the ui tests also assume cargo-miri has been built
builder.ensure(tool::CargoMiri::from_compilers(compilers));
let target_compiler = compilers.target_compiler();
// We also need sysroots, for Miri and for the host (the latter for build scripts).
// This is for the tests so everything is done with the target compiler.
let miri_sysroot = Miri::build_miri_sysroot(builder, target_compiler, target);
builder.std(target_compiler, host);
let host_sysroot = builder.sysroot(target_compiler);
// Miri has its own "target dir" for ui test dependencies. Make sure it gets cleared when
// the sysroot gets rebuilt, to avoid "found possibly newer version of crate `std`" errors.
if !builder.config.dry_run() {
// This has to match `CARGO_TARGET_TMPDIR` in Miri's `ui.rs`.
// This means we need `host` here as that's the target `ui.rs` is built for.
let ui_test_dep_dir = builder
.stage_out(miri.build_compiler, Mode::ToolStd)
.join(host)
.join("tmp")
.join("miri_ui");
// The mtime of `miri_sysroot` changes when the sysroot gets rebuilt (also see
// <https://github.com/RalfJung/rustc-build-sysroot/commit/10ebcf60b80fe2c3dc765af0ff19fdc0da4b7466>).
// We can hence use that directly as a signal to clear the ui test dir.
build_stamp::clear_if_dirty(builder, &ui_test_dep_dir, &miri_sysroot);
}
// Run `cargo test`.
// This is with the Miri crate, so it uses the host compiler.
let mut cargo = tool::prepare_tool_cargo(
builder,
miri.build_compiler,
Mode::ToolRustcPrivate,
host,
Kind::Test,
"src/tools/miri",
SourceType::InTree,
&[],
);
cargo.add_rustc_lib_path(builder);
// We can NOT use `run_cargo_test` since Miri's integration tests do not use the usual test
// harness and therefore do not understand the flags added by `add_flags_and_try_run_test`.
let mut cargo = prepare_cargo_test(cargo, &[], &[], host, builder);
// miri tests need to know about the stage sysroot
cargo.env("MIRI_SYSROOT", &miri_sysroot);
cargo.env("MIRI_HOST_SYSROOT", &host_sysroot);
// Set the target.
cargo.env("MIRI_TEST_TARGET", target.rustc_target_arg());
{
let _guard = builder.msg_test("miri", target, target_compiler.stage);
let _time = helpers::timeit(builder);
cargo.run(builder);
}
// Run it again for mir-opt-level 4 to catch some miscompilations.
if builder.config.test_args().is_empty() {
cargo.env("MIRIFLAGS", "-O -Zmir-opt-level=4 -Cdebug-assertions=yes");
// Optimizations can change backtraces
cargo.env("MIRI_SKIP_UI_CHECKS", "1");
// `MIRI_SKIP_UI_CHECKS` and `RUSTC_BLESS` are incompatible
cargo.env_remove("RUSTC_BLESS");
// Optimizations can change error locations and remove UB so don't run `fail` tests.
cargo.args(["tests/pass", "tests/panic"]);
{
let _guard =
builder.msg_test("miri (mir-opt-level 4)", target, target_compiler.stage);
let _time = helpers::timeit(builder);
cargo.run(builder);
}
}
}
}
/// Runs `cargo miri test` to demonstrate that `src/tools/miri/cargo-miri`
/// works and that libtest works under miri.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CargoMiri {
target: TargetSelection,
}
impl Step for CargoMiri {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/miri/cargo-miri")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(CargoMiri { target: run.target });
}
/// Tests `cargo miri test`.
fn run(self, builder: &Builder<'_>) {
let host = builder.build.host_target;
let target = self.target;
let stage = builder.top_stage;
if stage == 0 {
eprintln!("cargo-miri cannot be tested at stage 0");
std::process::exit(1);
}
// This compiler runs on the host, we'll just use it for the target.
let build_compiler = builder.compiler(stage, host);
// Run `cargo miri test`.
// This is just a smoke test (Miri's own CI invokes this in a bunch of different ways and ensures
// that we get the desired output), but that is sufficient to make sure that the libtest harness
// itself executes properly under Miri, and that all the logic in `cargo-miri` does not explode.
let mut cargo = tool::prepare_tool_cargo(
builder,
build_compiler,
Mode::ToolStd, // it's unclear what to use here, we're not building anything just doing a smoke test!
target,
Kind::MiriTest,
"src/tools/miri/test-cargo-miri",
SourceType::Submodule,
&[],
);
// We're not using `prepare_cargo_test` so we have to do this ourselves.
// (We're not using that as the test-cargo-miri crate is not known to bootstrap.)
match builder.doc_tests {
DocTests::Yes => {}
DocTests::No => {
cargo.args(["--lib", "--bins", "--examples", "--tests", "--benches"]);
}
DocTests::Only => {
cargo.arg("--doc");
}
}
cargo.arg("--").args(builder.config.test_args());
// Finally, run everything.
let mut cargo = BootstrapCommand::from(cargo);
{
let _guard = builder.msg_test("cargo-miri", target, stage);
let _time = helpers::timeit(builder);
cargo.run(builder);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CompiletestTest {
host: TargetSelection,
}
impl Step for CompiletestTest {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/compiletest")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(CompiletestTest { host: run.target });
}
/// Runs `cargo test` for compiletest.
fn run(self, builder: &Builder<'_>) {
let host = self.host;
// Now that compiletest uses only stable Rust, building it always uses
// the stage 0 compiler. However, some of its unit tests need to be able
// to query information from an in-tree compiler, so we treat `--stage`
// as selecting the stage of that secondary compiler.
if builder.top_stage == 0 && !builder.config.compiletest_allow_stage0 {
eprintln!("\
ERROR: `--stage 0` causes compiletest to query information from the stage0 (precompiled) compiler, instead of the in-tree compiler, which can cause some tests to fail inappropriately
NOTE: if you're sure you want to do this, please open an issue as to why. In the meantime, you can override this with `--set build.compiletest-allow-stage0=true`."
);
crate::exit!(1);
}
let bootstrap_compiler = builder.compiler(0, host);
let staged_compiler = builder.compiler(builder.top_stage, host);
let mut cargo = tool::prepare_tool_cargo(
builder,
bootstrap_compiler,
Mode::ToolBootstrap,
host,
Kind::Test,
"src/tools/compiletest",
SourceType::InTree,
&[],
);
// Used for `compiletest` self-tests to have the path to the *staged* compiler. Getting this
// right is important, as `compiletest` is intended to only support one target spec JSON
// format, namely that of the staged compiler.
cargo.env("TEST_RUSTC", builder.rustc(staged_compiler));
run_cargo_test(cargo, &[], &[], "compiletest self test", host, builder);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Clippy {
compilers: RustcPrivateCompilers,
}
impl Step for Clippy {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.suite_path("src/tools/clippy/tests").path("src/tools/clippy")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
false
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Clippy {
compilers: RustcPrivateCompilers::new(
run.builder,
run.builder.top_stage,
run.builder.host_target,
),
});
}
/// Runs `cargo test` for clippy.
fn run(self, builder: &Builder<'_>) {
let target = self.compilers.target();
// We need to carefully distinguish the compiler that builds clippy, and the compiler
// that is linked into the clippy being tested. `target_compiler` is the latter,
// and it must also be used by clippy's test runner to build tests and their dependencies.
let target_compiler = self.compilers.target_compiler();
let build_compiler = self.compilers.build_compiler();
let mut cargo = tool::prepare_tool_cargo(
builder,
build_compiler,
Mode::ToolRustcPrivate,
target,
Kind::Test,
"src/tools/clippy",
SourceType::InTree,
&[],
);
cargo.env("RUSTC_TEST_SUITE", builder.rustc(build_compiler));
cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(build_compiler));
let host_libs =
builder.stage_out(build_compiler, Mode::ToolRustcPrivate).join(builder.cargo_dir());
cargo.env("HOST_LIBS", host_libs);
// Build the standard library that the tests can use.
builder.std(target_compiler, target);
cargo.env("TEST_SYSROOT", builder.sysroot(target_compiler));
cargo.env("TEST_RUSTC", builder.rustc(target_compiler));
cargo.env("TEST_RUSTC_LIB", builder.rustc_libdir(target_compiler));
// Collect paths of tests to run
'partially_test: {
let paths = &builder.config.paths[..];
let mut test_names = Vec::new();
for path in paths {
if let Some(path) =
helpers::is_valid_test_suite_arg(path, "src/tools/clippy/tests", builder)
{
test_names.push(path);
} else if path.ends_with("src/tools/clippy") {
// When src/tools/clippy is called directly, all tests should be run.
break 'partially_test;
}
}
cargo.env("TESTNAME", test_names.join(","));
}
cargo.add_rustc_lib_path(builder);
let cargo = prepare_cargo_test(cargo, &[], &[], target, builder);
let _guard = builder.msg_test("clippy", target, target_compiler.stage);
// Clippy reports errors if it blessed the outputs
if cargo.allow_failure().run(builder) {
// The tests succeeded; nothing to do.
return;
}
if !builder.config.cmd.bless() {
crate::exit!(1);
}
}
fn metadata(&self) -> Option<StepMetadata> {
Some(
StepMetadata::test("clippy", self.compilers.target())
.built_by(self.compilers.build_compiler()),
)
}
}
fn bin_path_for_cargo(builder: &Builder<'_>, compiler: Compiler) -> OsString {
let path = builder.sysroot(compiler).join("bin");
let old_path = env::var_os("PATH").unwrap_or_default();
env::join_paths(iter::once(path).chain(env::split_paths(&old_path))).expect("")
}
/// Run the rustdoc-themes tool to test a given compiler.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct RustdocTheme {
/// The compiler (more accurately, its rustdoc) that we test.
test_compiler: Compiler,
}
impl Step for RustdocTheme {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/rustdoc-themes")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
let test_compiler = run.builder.compiler(run.builder.top_stage, run.target);
run.builder.ensure(RustdocTheme { test_compiler });
}
fn run(self, builder: &Builder<'_>) {
let rustdoc = builder.bootstrap_out.join("rustdoc");
let mut cmd = builder.tool_cmd(Tool::RustdocTheme);
cmd.arg(rustdoc.to_str().unwrap())
.arg(builder.src.join("src/librustdoc/html/static/css/rustdoc.css").to_str().unwrap())
.env("RUSTC_STAGE", self.test_compiler.stage.to_string())
.env("RUSTC_SYSROOT", builder.sysroot(self.test_compiler))
.env(
"RUSTDOC_LIBDIR",
builder.sysroot_target_libdir(self.test_compiler, self.test_compiler.host),
)
.env("CFG_RELEASE_CHANNEL", &builder.config.channel)
.env("RUSTDOC_REAL", builder.rustdoc_for_compiler(self.test_compiler))
.env("RUSTC_BOOTSTRAP", "1");
cmd.args(linker_args(builder, self.test_compiler.host, LldThreads::No));
cmd.delay_failure().run(builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(
StepMetadata::test("rustdoc-theme", self.test_compiler.host)
.stage(self.test_compiler.stage),
)
}
}
/// Test rustdoc JS for the standard library.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct RustdocJSStd {
/// Compiler that will build the standary library.
build_compiler: Compiler,
target: TargetSelection,
}
impl Step for RustdocJSStd {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.suite_path("tests/rustdoc-js-std")
}
fn is_default_step(builder: &Builder<'_>) -> bool {
builder.config.nodejs.is_some()
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(RustdocJSStd {
build_compiler: run.builder.compiler(run.builder.top_stage, run.builder.host_target),
target: run.target,
});
}
fn run(self, builder: &Builder<'_>) {
let nodejs =
builder.config.nodejs.as_ref().expect("need nodejs to run rustdoc-js-std tests");
let mut command = command(nodejs);
command
.arg(builder.src.join("src/tools/rustdoc-js/tester.js"))
.arg("--crate-name")
.arg("std")
.arg("--resource-suffix")
.arg(&builder.version)
.arg("--doc-folder")
.arg(builder.doc_out(self.target))
.arg("--test-folder")
.arg(builder.src.join("tests/rustdoc-js-std"));
for path in &builder.paths {
if let Some(p) = helpers::is_valid_test_suite_arg(path, "tests/rustdoc-js-std", builder)
{
if !p.ends_with(".js") {
eprintln!("A non-js file was given: `{}`", path.display());
panic!("Cannot run rustdoc-js-std tests");
}
command.arg("--test-file").arg(path);
}
}
builder.ensure(crate::core::build_steps::doc::Std::from_build_compiler(
self.build_compiler,
self.target,
DocumentationFormat::Html,
));
let _guard = builder.msg_test("rustdoc-js-std", self.target, self.build_compiler.stage);
command.run(builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("rustdoc-js-std", self.target).stage(self.build_compiler.stage))
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct RustdocJSNotStd {
pub target: TargetSelection,
pub compiler: Compiler,
}
impl Step for RustdocJSNotStd {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.suite_path("tests/rustdoc-js")
}
fn is_default_step(builder: &Builder<'_>) -> bool {
builder.config.nodejs.is_some()
}
fn make_run(run: RunConfig<'_>) {
let compiler = run.builder.compiler(run.builder.top_stage, run.build_triple());
run.builder.ensure(RustdocJSNotStd { target: run.target, compiler });
}
fn run(self, builder: &Builder<'_>) {
builder.ensure(Compiletest {
test_compiler: self.compiler,
target: self.target,
mode: CompiletestMode::RustdocJs,
suite: "rustdoc-js",
path: "tests/rustdoc-js",
compare_mode: None,
});
}
}
fn get_browser_ui_test_version_inner(
builder: &Builder<'_>,
yarn: &Path,
global: bool,
) -> Option<String> {
let mut command = command(yarn);
command
.arg("--cwd")
.arg(&builder.build.out)
.arg("list")
.arg("--parseable")
.arg("--long")
.arg("--depth=0");
if global {
command.arg("--global");
}
// Cache the command output so that `test::RustdocGUI` only performs these
// command-line probes once.
let lines = command.allow_failure().cached().run_capture(builder).stdout();
lines
.lines()
.find_map(|l| l.split(':').nth(1)?.strip_prefix("browser-ui-test@"))
.map(|v| v.to_owned())
}
fn get_browser_ui_test_version(builder: &Builder<'_>) -> Option<String> {
let yarn = builder.config.yarn.as_deref()?;
get_browser_ui_test_version_inner(builder, yarn, false)
.or_else(|| get_browser_ui_test_version_inner(builder, yarn, true))
}
/// Run GUI tests on a given rustdoc.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct RustdocGUI {
/// The compiler whose rustdoc we are testing.
test_compiler: Compiler,
target: TargetSelection,
}
impl Step for RustdocGUI {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.suite_path("tests/rustdoc-gui")
}
fn is_default_step(builder: &Builder<'_>) -> bool {
builder.config.nodejs.is_some()
&& builder.doc_tests != DocTests::Only
&& get_browser_ui_test_version(builder).is_some()
}
fn make_run(run: RunConfig<'_>) {
let test_compiler = run.builder.compiler(run.builder.top_stage, run.build_triple());
run.builder.ensure(RustdocGUI { test_compiler, target: run.target });
}
fn run(self, builder: &Builder<'_>) {
builder.std(self.test_compiler, self.target);
let mut cmd = builder.tool_cmd(Tool::RustdocGUITest);
let out_dir = builder.test_out(self.target).join("rustdoc-gui");
build_stamp::clear_if_dirty(
builder,
&out_dir,
&builder.rustdoc_for_compiler(self.test_compiler),
);
if let Some(src) = builder.config.src.to_str() {
cmd.arg("--rust-src").arg(src);
}
if let Some(out_dir) = out_dir.to_str() {
cmd.arg("--out-dir").arg(out_dir);
}
if let Some(initial_cargo) = builder.config.initial_cargo.to_str() {
cmd.arg("--initial-cargo").arg(initial_cargo);
}
cmd.arg("--jobs").arg(builder.jobs().to_string());
cmd.env("RUSTDOC", builder.rustdoc_for_compiler(self.test_compiler))
.env("RUSTC", builder.rustc(self.test_compiler));
add_rustdoc_cargo_linker_args(&mut cmd, builder, self.test_compiler.host, LldThreads::No);
for path in &builder.paths {
if let Some(p) = helpers::is_valid_test_suite_arg(path, "tests/rustdoc-gui", builder) {
if !p.ends_with(".goml") {
eprintln!("A non-goml file was given: `{}`", path.display());
panic!("Cannot run rustdoc-gui tests");
}
if let Some(name) = path.file_name().and_then(|f| f.to_str()) {
cmd.arg("--goml-file").arg(name);
}
}
}
for test_arg in builder.config.test_args() {
cmd.arg("--test-arg").arg(test_arg);
}
if let Some(ref nodejs) = builder.config.nodejs {
cmd.arg("--nodejs").arg(nodejs);
}
if let Some(ref yarn) = builder.config.yarn {
cmd.arg("--yarn").arg(yarn);
}
let _time = helpers::timeit(builder);
let _guard = builder.msg_test("rustdoc-gui", self.target, self.test_compiler.stage);
try_run_tests(builder, &mut cmd, true);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("rustdoc-gui", self.target).stage(self.test_compiler.stage))
}
}
/// Runs `src/tools/tidy` and `cargo fmt --check` to detect various style
/// problems in the repository.
///
/// (To run the tidy tool's internal tests, use the alias "tidyselftest" instead.)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Tidy;
impl Step for Tidy {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/tidy")
}
fn is_default_step(builder: &Builder<'_>) -> bool {
builder.doc_tests != DocTests::Only
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Tidy);
}
/// Runs the `tidy` tool.
///
/// This tool in `src/tools` checks up on various bits and pieces of style and
/// otherwise just implements a few lint-like checks that are specific to the
/// compiler itself.
///
/// Once tidy passes, this step also runs `fmt --check` if tests are being run
/// for the `dev` or `nightly` channels.
fn run(self, builder: &Builder<'_>) {
let mut cmd = builder.tool_cmd(Tool::Tidy);
cmd.arg(&builder.src);
cmd.arg(&builder.initial_cargo);
cmd.arg(&builder.out);
// Tidy is heavily IO constrained. Still respect `-j`, but use a higher limit if `jobs` hasn't been configured.
let jobs = builder.config.jobs.unwrap_or_else(|| {
8 * std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32
});
cmd.arg(jobs.to_string());
// pass the path to the yarn command used for installing js deps.
if let Some(yarn) = &builder.config.yarn {
cmd.arg(yarn);
} else {
cmd.arg("yarn");
}
if builder.is_verbose() {
cmd.arg("--verbose");
}
if builder.config.cmd.bless() {
cmd.arg("--bless");
}
if let Some(s) =
builder.config.cmd.extra_checks().or(builder.config.tidy_extra_checks.as_deref())
{
cmd.arg(format!("--extra-checks={s}"));
}
let mut args = std::env::args_os();
if args.any(|arg| arg == OsStr::new("--")) {
cmd.arg("--");
cmd.args(args);
}
if builder.config.channel == "dev" || builder.config.channel == "nightly" {
if !builder.config.json_output {
builder.info("fmt check");
if builder.config.initial_rustfmt.is_none() {
let inferred_rustfmt_dir = builder.initial_sysroot.join("bin");
eprintln!(
"\
ERROR: no `rustfmt` binary found in {PATH}
INFO: `rust.channel` is currently set to \"{CHAN}\"
HELP: if you are testing a beta branch, set `rust.channel` to \"beta\" in the `bootstrap.toml` file
HELP: to skip test's attempt to check tidiness, pass `--skip src/tools/tidy` to `x.py test`",
PATH = inferred_rustfmt_dir.display(),
CHAN = builder.config.channel,
);
crate::exit!(1);
}
let all = false;
crate::core::build_steps::format::format(
builder,
!builder.config.cmd.bless(),
all,
&[],
);
} else {
eprintln!(
"WARNING: `--json-output` is not supported on rustfmt, formatting will be skipped"
);
}
}
builder.info("tidy check");
cmd.delay_failure().run(builder);
builder.info("x.py completions check");
let completion_paths = get_completion_paths(builder);
if builder.config.cmd.bless() {
builder.ensure(crate::core::build_steps::run::GenerateCompletions);
} else if completion_paths
.into_iter()
.any(|(shell, path)| get_completion(shell, &path).is_some())
{
eprintln!(
"x.py completions were changed; run `x.py run generate-completions` to update them"
);
crate::exit!(1);
}
builder.info("x.py help check");
if builder.config.cmd.bless() {
builder.ensure(crate::core::build_steps::run::GenerateHelp);
} else {
let help_path = get_help_path(builder);
let cur_help = std::fs::read_to_string(&help_path).unwrap_or_else(|err| {
eprintln!("couldn't read {}: {}", help_path.display(), err);
crate::exit!(1);
});
let new_help = top_level_help();
if new_help != cur_help {
eprintln!("x.py help was changed; run `x.py run generate-help` to update it");
crate::exit!(1);
}
}
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("tidy", TargetSelection::default()))
}
}
/// Runs `cargo test` on the `src/tools/run-make-support` crate.
/// That crate is used by run-make tests.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CrateRunMakeSupport {
host: TargetSelection,
}
impl Step for CrateRunMakeSupport {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/run-make-support")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(CrateRunMakeSupport { host: run.target });
}
/// Runs `cargo test` for run-make-support.
fn run(self, builder: &Builder<'_>) {
let host = self.host;
let compiler = builder.compiler(0, host);
let mut cargo = tool::prepare_tool_cargo(
builder,
compiler,
Mode::ToolBootstrap,
host,
Kind::Test,
"src/tools/run-make-support",
SourceType::InTree,
&[],
);
cargo.allow_features("test");
run_cargo_test(cargo, &[], &[], "run-make-support self test", host, builder);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CrateBuildHelper {
host: TargetSelection,
}
impl Step for CrateBuildHelper {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/build_helper")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(CrateBuildHelper { host: run.target });
}
/// Runs `cargo test` for build_helper.
fn run(self, builder: &Builder<'_>) {
let host = self.host;
let compiler = builder.compiler(0, host);
let mut cargo = tool::prepare_tool_cargo(
builder,
compiler,
Mode::ToolBootstrap,
host,
Kind::Test,
"src/build_helper",
SourceType::InTree,
&[],
);
cargo.allow_features("test");
run_cargo_test(cargo, &[], &[], "build_helper self test", host, builder);
}
}
fn testdir(builder: &Builder<'_>, host: TargetSelection) -> PathBuf {
builder.out.join(host).join("test")
}
/// Declares a test step that invokes compiletest on a particular test suite.
macro_rules! test {
(
$( #[$attr:meta] )* // allow docstrings and attributes
$name:ident {
path: $path:expr,
mode: $mode:expr,
suite: $suite:expr,
default: $default:expr
$( , IS_HOST: $IS_HOST:expr )? // default: false
$( , compare_mode: $compare_mode:expr )? // default: None
$( , )? // optional trailing comma
}
) => {
$( #[$attr] )*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name {
test_compiler: Compiler,
target: TargetSelection,
}
impl Step for $name {
type Output = ();
const IS_HOST: bool = (const {
#[allow(unused_assignments, unused_mut)]
let mut value = false;
$( value = $IS_HOST; )?
value
});
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.suite_path($path)
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
const { $default }
}
fn make_run(run: RunConfig<'_>) {
let test_compiler = run.builder.compiler(run.builder.top_stage, run.build_triple());
run.builder.ensure($name { test_compiler, target: run.target });
}
fn run(self, builder: &Builder<'_>) {
builder.ensure(Compiletest {
test_compiler: self.test_compiler,
target: self.target,
mode: const { $mode },
suite: $suite,
path: $path,
compare_mode: (const {
#[allow(unused_assignments, unused_mut)]
let mut value = None;
$( value = $compare_mode; )?
value
}),
})
}
}
};
}
test!(Ui { path: "tests/ui", mode: CompiletestMode::Ui, suite: "ui", default: true });
test!(Crashes {
path: "tests/crashes",
mode: CompiletestMode::Crashes,
suite: "crashes",
default: true,
});
test!(CodegenLlvm {
path: "tests/codegen-llvm",
mode: CompiletestMode::Codegen,
suite: "codegen-llvm",
default: true
});
test!(CodegenUnits {
path: "tests/codegen-units",
mode: CompiletestMode::CodegenUnits,
suite: "codegen-units",
default: true,
});
test!(Incremental {
path: "tests/incremental",
mode: CompiletestMode::Incremental,
suite: "incremental",
default: true,
});
test!(Debuginfo {
path: "tests/debuginfo",
mode: CompiletestMode::Debuginfo,
suite: "debuginfo",
default: true,
compare_mode: Some("split-dwarf"),
});
test!(UiFullDeps {
path: "tests/ui-fulldeps",
mode: CompiletestMode::Ui,
suite: "ui-fulldeps",
default: true,
IS_HOST: true,
});
test!(Rustdoc {
path: "tests/rustdoc",
mode: CompiletestMode::Rustdoc,
suite: "rustdoc",
default: true,
IS_HOST: true,
});
test!(RustdocUi {
path: "tests/rustdoc-ui",
mode: CompiletestMode::Ui,
suite: "rustdoc-ui",
default: true,
IS_HOST: true,
});
test!(RustdocJson {
path: "tests/rustdoc-json",
mode: CompiletestMode::RustdocJson,
suite: "rustdoc-json",
default: true,
IS_HOST: true,
});
test!(Pretty {
path: "tests/pretty",
mode: CompiletestMode::Pretty,
suite: "pretty",
default: true,
IS_HOST: true,
});
test!(RunMake {
path: "tests/run-make",
mode: CompiletestMode::RunMake,
suite: "run-make",
default: true,
});
test!(RunMakeCargo {
path: "tests/run-make-cargo",
mode: CompiletestMode::RunMake,
suite: "run-make-cargo",
default: true
});
test!(AssemblyLlvm {
path: "tests/assembly-llvm",
mode: CompiletestMode::Assembly,
suite: "assembly-llvm",
default: true
});
/// Runs the coverage test suite at `tests/coverage` in some or all of the
/// coverage test modes.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Coverage {
pub compiler: Compiler,
pub target: TargetSelection,
pub(crate) mode: CompiletestMode,
}
impl Coverage {
const PATH: &'static str = "tests/coverage";
const SUITE: &'static str = "coverage";
const ALL_MODES: &[CompiletestMode] =
&[CompiletestMode::CoverageMap, CompiletestMode::CoverageRun];
}
impl Step for Coverage {
type Output = ();
/// Compiletest will automatically skip the "coverage-run" tests if necessary.
const IS_HOST: bool = false;
fn should_run(mut run: ShouldRun<'_>) -> ShouldRun<'_> {
// Support various invocation styles, including:
// - `./x test coverage`
// - `./x test tests/coverage/trivial.rs`
// - `./x test coverage-map`
// - `./x test coverage-run -- tests/coverage/trivial.rs`
run = run.suite_path(Self::PATH);
for mode in Self::ALL_MODES {
run = run.alias(mode.as_str());
}
run
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
let compiler = run.builder.compiler(run.builder.top_stage, run.build_triple());
let target = run.target;
// List of (coverage) test modes that the coverage test suite will be
// run in. It's OK for this to contain duplicates, because the call to
// `Builder::ensure` below will take care of deduplication.
let mut modes = vec![];
// From the pathsets that were selected on the command-line (or by default),
// determine which modes to run in.
for path in &run.paths {
match path {
PathSet::Set(_) => {
for &mode in Self::ALL_MODES {
if path.assert_single_path().path == Path::new(mode.as_str()) {
modes.push(mode);
break;
}
}
}
PathSet::Suite(_) => {
modes.extend_from_slice(Self::ALL_MODES);
break;
}
}
}
// Skip any modes that were explicitly skipped/excluded on the command-line.
// FIXME(Zalathar): Integrate this into central skip handling somehow?
modes.retain(|mode| {
!run.builder.config.skip.iter().any(|skip| skip == Path::new(mode.as_str()))
});
// FIXME(Zalathar): Make these commands skip all coverage tests, as expected:
// - `./x test --skip=tests`
// - `./x test --skip=tests/coverage`
// - `./x test --skip=coverage`
// Skip handling currently doesn't have a way to know that skipping the coverage
// suite should also skip the `coverage-map` and `coverage-run` aliases.
for mode in modes {
run.builder.ensure(Coverage { compiler, target, mode });
}
}
fn run(self, builder: &Builder<'_>) {
let Self { compiler, target, mode } = self;
// Like other compiletest suite test steps, delegate to an internal
// compiletest task to actually run the tests.
builder.ensure(Compiletest {
test_compiler: compiler,
target,
mode,
suite: Self::SUITE,
path: Self::PATH,
compare_mode: None,
});
}
}
test!(CoverageRunRustdoc {
path: "tests/coverage-run-rustdoc",
mode: CompiletestMode::CoverageRun,
suite: "coverage-run-rustdoc",
default: true,
IS_HOST: true,
});
// For the mir-opt suite we do not use macros, as we need custom behavior when blessing.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MirOpt {
pub compiler: Compiler,
pub target: TargetSelection,
}
impl Step for MirOpt {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.suite_path("tests/mir-opt")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
let compiler = run.builder.compiler(run.builder.top_stage, run.build_triple());
run.builder.ensure(MirOpt { compiler, target: run.target });
}
fn run(self, builder: &Builder<'_>) {
let run = |target| {
builder.ensure(Compiletest {
test_compiler: self.compiler,
target,
mode: CompiletestMode::MirOpt,
suite: "mir-opt",
path: "tests/mir-opt",
compare_mode: None,
})
};
run(self.target);
// Run more targets with `--bless`. But we always run the host target first, since some
// tests use very specific `only` clauses that are not covered by the target set below.
if builder.config.cmd.bless() {
// All that we really need to do is cover all combinations of 32/64-bit and unwind/abort,
// but while we're at it we might as well flex our cross-compilation support. This
// selection covers all our tier 1 operating systems and architectures using only tier
// 1 targets.
for target in ["aarch64-unknown-linux-gnu", "i686-pc-windows-msvc"] {
run(TargetSelection::from_user(target));
}
for target in ["x86_64-apple-darwin", "i686-unknown-linux-musl"] {
let target = TargetSelection::from_user(target);
let panic_abort_target = builder.ensure(MirOptPanicAbortSyntheticTarget {
compiler: self.compiler,
base: target,
});
run(panic_abort_target);
}
}
}
}
/// Executes the `compiletest` tool to run a suite of tests.
///
/// Compiles all tests with `test_compiler` for `target` with the specified
/// compiletest `mode` and `suite` arguments. For example `mode` can be
/// "mir-opt" and `suite` can be something like "debuginfo".
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Compiletest {
/// The compiler that we're testing.
test_compiler: Compiler,
target: TargetSelection,
mode: CompiletestMode,
suite: &'static str,
path: &'static str,
compare_mode: Option<&'static str>,
}
impl Step for Compiletest {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.never()
}
fn run(self, builder: &Builder<'_>) {
if builder.doc_tests == DocTests::Only {
return;
}
if builder.top_stage == 0 && !builder.config.compiletest_allow_stage0 {
eprintln!("\
ERROR: `--stage 0` runs compiletest on the stage0 (precompiled) compiler, not your local changes, and will almost always cause tests to fail
HELP: to test the compiler or standard library, omit the stage or explicitly use `--stage 1` instead
NOTE: if you're sure you want to do this, please open an issue as to why. In the meantime, you can override this with `--set build.compiletest-allow-stage0=true`."
);
crate::exit!(1);
}
let mut test_compiler = self.test_compiler;
let target = self.target;
let mode = self.mode;
let suite = self.suite;
// Path for test suite
let suite_path = self.path;
// Skip codegen tests if they aren't enabled in configuration.
if !builder.config.codegen_tests && mode == CompiletestMode::Codegen {
return;
}
// Support stage 1 ui-fulldeps. This is somewhat complicated: ui-fulldeps tests for the most
// part test the *API* of the compiler, not how it compiles a given file. As a result, we
// can run them against the stage 1 sources as long as we build them with the stage 0
// bootstrap compiler.
// NOTE: Only stage 1 is special cased because we need the rustc_private artifacts to match the
// running compiler in stage 2 when plugins run.
let query_compiler;
let (stage, stage_id) = if suite == "ui-fulldeps" && test_compiler.stage == 1 {
// Even when using the stage 0 compiler, we also need to provide the stage 1 compiler
// so that compiletest can query it for target information.
query_compiler = Some(test_compiler);
// At stage 0 (stage - 1) we are using the stage0 compiler. Using `self.target` can lead
// finding an incorrect compiler path on cross-targets, as the stage 0 is always equal to
// `build.build` in the configuration.
let build = builder.build.host_target;
test_compiler = builder.compiler(test_compiler.stage - 1, build);
let test_stage = test_compiler.stage + 1;
(test_stage, format!("stage{test_stage}-{build}"))
} else {
query_compiler = None;
let stage = test_compiler.stage;
(stage, format!("stage{stage}-{target}"))
};
if suite.ends_with("fulldeps") {
builder.ensure(compile::Rustc::new(test_compiler, target));
}
if suite == "debuginfo" {
builder.ensure(dist::DebuggerScripts {
sysroot: builder.sysroot(test_compiler).to_path_buf(),
target,
});
}
if mode == CompiletestMode::RunMake {
builder.tool_exe(Tool::RunMakeSupport);
}
// ensure that `libproc_macro` is available on the host.
if suite == "mir-opt" {
builder.ensure(
compile::Std::new(test_compiler, test_compiler.host).is_for_mir_opt_tests(true),
);
} else {
builder.std(test_compiler, test_compiler.host);
}
let mut cmd = builder.tool_cmd(Tool::Compiletest);
if suite == "mir-opt" {
builder.ensure(compile::Std::new(test_compiler, target).is_for_mir_opt_tests(true));
} else {
builder.std(test_compiler, target);
}
builder.ensure(RemoteCopyLibs { build_compiler: test_compiler, target });
// compiletest currently has... a lot of arguments, so let's just pass all
// of them!
cmd.arg("--stage").arg(stage.to_string());
cmd.arg("--stage-id").arg(stage_id);
cmd.arg("--compile-lib-path").arg(builder.rustc_libdir(test_compiler));
cmd.arg("--run-lib-path").arg(builder.sysroot_target_libdir(test_compiler, target));
cmd.arg("--rustc-path").arg(builder.rustc(test_compiler));
if let Some(query_compiler) = query_compiler {
cmd.arg("--query-rustc-path").arg(builder.rustc(query_compiler));
}
// Minicore auxiliary lib for `no_core` tests that need `core` stubs in cross-compilation
// scenarios.
cmd.arg("--minicore-path")
.arg(builder.src.join("tests").join("auxiliary").join("minicore.rs"));
let is_rustdoc = suite == "rustdoc-ui" || suite == "rustdoc-js";
// There are (potentially) 2 `cargo`s to consider:
//
// - A "bootstrap" cargo, which is the same cargo used to build bootstrap itself, and is
// used to build the `run-make` test recipes and the `run-make-support` test library. All
// of these may not use unstable rustc/cargo features.
// - An in-tree cargo, which should be considered as under test. The `run-make-cargo` test
// suite is intended to support the use case of testing the "toolchain" (that is, at the
// minimum the interaction between in-tree cargo + rustc) together.
//
// For build time and iteration purposes, we partition `run-make` tests which needs an
// in-tree cargo (a smaller subset) versus `run-make` tests that do not into two test
// suites, `run-make` and `run-make-cargo`. That way, contributors who do not need to run
// the `run-make` tests that need in-tree cargo do not need to spend time building in-tree
// cargo.
if mode == CompiletestMode::RunMake {
// We need to pass the compiler that was used to compile run-make-support,
// because we have to use the same compiler to compile rmake.rs recipes.
let stage0_rustc_path = builder.compiler(0, test_compiler.host);
cmd.arg("--stage0-rustc-path").arg(builder.rustc(stage0_rustc_path));
if suite == "run-make-cargo" {
let cargo_path = if test_compiler.stage == 0 {
// If we're using `--stage 0`, we should provide the bootstrap cargo.
builder.initial_cargo.clone()
} else {
builder
.ensure(tool::Cargo::from_build_compiler(
builder.compiler(test_compiler.stage - 1, test_compiler.host),
test_compiler.host,
))
.tool_path
};
cmd.arg("--cargo-path").arg(cargo_path);
}
}
// Avoid depending on rustdoc when we don't need it.
if matches!(
mode,
CompiletestMode::RunMake
| CompiletestMode::Rustdoc
| CompiletestMode::RustdocJs
| CompiletestMode::RustdocJson
) || matches!(suite, "rustdoc-ui" | "coverage-run-rustdoc")
{
cmd.arg("--rustdoc-path").arg(builder.rustdoc_for_compiler(test_compiler));
}
if mode == CompiletestMode::RustdocJson {
// Use the stage0 compiler for jsondocck
let json_compiler = builder.compiler(0, builder.host_target);
cmd.arg("--jsondocck-path")
.arg(builder.ensure(tool::JsonDocCk { compiler: json_compiler, target }).tool_path);
cmd.arg("--jsondoclint-path").arg(
builder.ensure(tool::JsonDocLint { compiler: json_compiler, target }).tool_path,
);
}
if matches!(mode, CompiletestMode::CoverageMap | CompiletestMode::CoverageRun) {
let coverage_dump = builder.tool_exe(Tool::CoverageDump);
cmd.arg("--coverage-dump-path").arg(coverage_dump);
}
cmd.arg("--src-root").arg(&builder.src);
cmd.arg("--src-test-suite-root").arg(builder.src.join("tests").join(suite));
// N.B. it's important to distinguish between the *root* build directory, the *host* build
// directory immediately under the root build directory, and the test-suite-specific build
// directory.
cmd.arg("--build-root").arg(&builder.out);
cmd.arg("--build-test-suite-root").arg(testdir(builder, test_compiler.host).join(suite));
// When top stage is 0, that means that we're testing an externally provided compiler.
// In that case we need to use its specific sysroot for tests to pass.
// Note: DO NOT check if test_compiler.stage is 0, because the test compiler can be stage 0
// even if the top stage is 1 (when we run the ui-fulldeps suite).
let sysroot = if builder.top_stage == 0 {
builder.initial_sysroot.clone()
} else {
builder.sysroot(test_compiler)
};
cmd.arg("--sysroot-base").arg(sysroot);
cmd.arg("--suite").arg(suite);
cmd.arg("--mode").arg(mode.as_str());
cmd.arg("--target").arg(target.rustc_target_arg());
cmd.arg("--host").arg(&*test_compiler.host.triple);
cmd.arg("--llvm-filecheck").arg(builder.llvm_filecheck(builder.config.host_target));
if let Some(codegen_backend) = builder.config.cmd.test_codegen_backend() {
if !builder
.config
.enabled_codegen_backends(test_compiler.host)
.contains(codegen_backend)
{
eprintln!(
"\
ERROR: No configured backend named `{name}`
HELP: You can add it into `bootstrap.toml` in `rust.codegen-backends = [{name:?}]`",
name = codegen_backend.name(),
);
crate::exit!(1);
}
if let CodegenBackendKind::Gcc = codegen_backend
&& builder.config.rustc_debug_assertions
{
eprintln!(
r#"WARNING: Running tests with the GCC codegen backend while rustc debug assertions are enabled. This might lead to test failures.
Please disable assertions with `rust.debug-assertions = false`.
"#
);
}
// Tells compiletest that we want to use this codegen in particular and to override
// the default one.
cmd.arg("--override-codegen-backend").arg(codegen_backend.name());
// Tells compiletest which codegen backend to use.
// It is used to e.g. ignore tests that don't support that codegen backend.
cmd.arg("--default-codegen-backend").arg(codegen_backend.name());
} else {
// Tells compiletest which codegen backend to use.
// It is used to e.g. ignore tests that don't support that codegen backend.
cmd.arg("--default-codegen-backend")
.arg(builder.config.default_codegen_backend(test_compiler.host).name());
}
if builder.config.cmd.bypass_ignore_backends() {
cmd.arg("--bypass-ignore-backends");
}
if builder.build.config.llvm_enzyme {
cmd.arg("--has-enzyme");
}
if builder.build.config.llvm_offload {
cmd.arg("--has-offload");
}
if builder.config.cmd.bless() {
cmd.arg("--bless");
}
if builder.config.cmd.force_rerun() {
cmd.arg("--force-rerun");
}
if builder.config.cmd.no_capture() {
cmd.arg("--no-capture");
}
let compare_mode =
builder.config.cmd.compare_mode().or_else(|| {
if builder.config.test_compare_mode { self.compare_mode } else { None }
});
if let Some(ref pass) = builder.config.cmd.pass() {
cmd.arg("--pass");
cmd.arg(pass);
}
if let Some(ref run) = builder.config.cmd.run() {
cmd.arg("--run");
cmd.arg(run);
}
if let Some(ref nodejs) = builder.config.nodejs {
cmd.arg("--nodejs").arg(nodejs);
} else if mode == CompiletestMode::RustdocJs {
panic!("need nodejs to run rustdoc-js suite");
}
if builder.config.rust_optimize_tests {
cmd.arg("--optimize-tests");
}
if builder.config.rust_randomize_layout {
cmd.arg("--rust-randomized-layout");
}
if builder.config.cmd.only_modified() {
cmd.arg("--only-modified");
}
if let Some(compiletest_diff_tool) = &builder.config.compiletest_diff_tool {
cmd.arg("--compiletest-diff-tool").arg(compiletest_diff_tool);
}
let mut flags = if is_rustdoc { Vec::new() } else { vec!["-Crpath".to_string()] };
flags.push(format!(
"-Cdebuginfo={}",
if mode == CompiletestMode::Codegen {
// codegen tests typically check LLVM IR and are sensitive to additional debuginfo.
// So do not apply `rust.debuginfo-level-tests` for codegen tests.
if builder.config.rust_debuginfo_level_tests
!= crate::core::config::DebuginfoLevel::None
{
println!(
"NOTE: ignoring `rust.debuginfo-level-tests={}` for codegen tests",
builder.config.rust_debuginfo_level_tests
);
}
crate::core::config::DebuginfoLevel::None
} else {
builder.config.rust_debuginfo_level_tests
}
));
flags.extend(builder.config.cmd.compiletest_rustc_args().iter().map(|s| s.to_string()));
if suite != "mir-opt" {
if let Some(linker) = builder.linker(target) {
cmd.arg("--target-linker").arg(linker);
}
if let Some(linker) = builder.linker(test_compiler.host) {
cmd.arg("--host-linker").arg(linker);
}
}
// FIXME(136096): on macOS, we get linker warnings about duplicate `-lm` flags.
if suite == "ui-fulldeps" && target.ends_with("darwin") {
flags.push("-Alinker_messages".into());
}
let mut hostflags = flags.clone();
hostflags.extend(linker_flags(builder, test_compiler.host, LldThreads::No));
let mut targetflags = flags;
// Provide `rust_test_helpers` for both host and target.
if suite == "ui" || suite == "incremental" {
builder.ensure(TestHelpers { target: test_compiler.host });
builder.ensure(TestHelpers { target });
hostflags.push(format!(
"-Lnative={}",
builder.test_helpers_out(test_compiler.host).display()
));
targetflags.push(format!("-Lnative={}", builder.test_helpers_out(target).display()));
}
for flag in hostflags {
cmd.arg("--host-rustcflags").arg(flag);
}
for flag in targetflags {
cmd.arg("--target-rustcflags").arg(flag);
}
cmd.arg("--python").arg(
builder.config.python.as_ref().expect("python is required for running rustdoc tests"),
);
// Discover and set some flags related to running tests on Android targets.
let android = android::discover_android(builder, target);
if let Some(android::Android { adb_path, adb_test_dir, android_cross_path }) = &android {
cmd.arg("--adb-path").arg(adb_path);
cmd.arg("--adb-test-dir").arg(adb_test_dir);
cmd.arg("--android-cross-path").arg(android_cross_path);
}
if mode == CompiletestMode::Debuginfo {
if let Some(debuggers::Cdb { cdb }) = debuggers::discover_cdb(target) {
cmd.arg("--cdb").arg(cdb);
}
if let Some(debuggers::Gdb { gdb }) = debuggers::discover_gdb(builder, android.as_ref())
{
cmd.arg("--gdb").arg(gdb.as_ref());
}
if let Some(debuggers::Lldb { lldb_exe, lldb_version }) =
debuggers::discover_lldb(builder)
{
cmd.arg("--lldb").arg(lldb_exe);
cmd.arg("--lldb-version").arg(lldb_version);
}
}
if helpers::forcing_clang_based_tests() {
let clang_exe = builder.llvm_out(target).join("bin").join("clang");
cmd.arg("--run-clang-based-tests-with").arg(clang_exe);
}
for exclude in &builder.config.skip {
cmd.arg("--skip");
cmd.arg(exclude);
}
// Get paths from cmd args
let mut paths = match &builder.config.cmd {
Subcommand::Test { .. } => &builder.config.paths[..],
_ => &[],
};
// in rustdoc-js mode, allow filters to be rs files or js files.
// use a late-initialized Vec to avoid cloning for other modes.
let mut paths_v;
if mode == CompiletestMode::RustdocJs {
paths_v = paths.to_vec();
for p in &mut paths_v {
if let Some(ext) = p.extension()
&& ext == "js"
{
p.set_extension("rs");
}
}
paths = &paths_v;
}
// Get test-args by striping suite path
let mut test_args: Vec<&str> = paths
.iter()
.filter_map(|p| helpers::is_valid_test_suite_arg(p, suite_path, builder))
.collect();
test_args.append(&mut builder.config.test_args());
// On Windows, replace forward slashes in test-args by backslashes
// so the correct filters are passed to libtest
if cfg!(windows) {
let test_args_win: Vec<String> =
test_args.iter().map(|s| s.replace('/', "\\")).collect();
cmd.args(&test_args_win);
} else {
cmd.args(&test_args);
}
if builder.is_verbose() {
cmd.arg("--verbose");
}
if builder.config.rustc_debug_assertions {
cmd.arg("--with-rustc-debug-assertions");
}
if builder.config.std_debug_assertions {
cmd.arg("--with-std-debug-assertions");
}
if builder.config.rust_remap_debuginfo {
cmd.arg("--with-std-remap-debuginfo");
}
cmd.arg("--jobs").arg(builder.jobs().to_string());
let mut llvm_components_passed = false;
let mut copts_passed = false;
if builder.config.llvm_enabled(test_compiler.host) {
let llvm::LlvmResult { host_llvm_config, .. } =
builder.ensure(llvm::Llvm { target: builder.config.host_target });
if !builder.config.dry_run() {
let llvm_version = get_llvm_version(builder, &host_llvm_config);
let llvm_components = command(&host_llvm_config)
.cached()
.arg("--components")
.run_capture_stdout(builder)
.stdout();
// Remove trailing newline from llvm-config output.
cmd.arg("--llvm-version")
.arg(llvm_version.trim())
.arg("--llvm-components")
.arg(llvm_components.trim());
llvm_components_passed = true;
}
if !builder.config.is_rust_llvm(target) {
cmd.arg("--system-llvm");
}
// Tests that use compiler libraries may inherit the `-lLLVM` link
// requirement, but the `-L` library path is not propagated across
// separate compilations. We can add LLVM's library path to the
// rustc args as a workaround.
if !builder.config.dry_run() && suite.ends_with("fulldeps") {
let llvm_libdir = command(&host_llvm_config)
.cached()
.arg("--libdir")
.run_capture_stdout(builder)
.stdout();
let link_llvm = if target.is_msvc() {
format!("-Clink-arg=-LIBPATH:{llvm_libdir}")
} else {
format!("-Clink-arg=-L{llvm_libdir}")
};
cmd.arg("--host-rustcflags").arg(link_llvm);
}
if !builder.config.dry_run()
&& matches!(mode, CompiletestMode::RunMake | CompiletestMode::CoverageRun)
{
// The llvm/bin directory contains many useful cross-platform
// tools. Pass the path to run-make tests so they can use them.
// (The coverage-run tests also need these tools to process
// coverage reports.)
let llvm_bin_path = host_llvm_config
.parent()
.expect("Expected llvm-config to be contained in directory");
assert!(llvm_bin_path.is_dir());
cmd.arg("--llvm-bin-dir").arg(llvm_bin_path);
}
if !builder.config.dry_run() && mode == CompiletestMode::RunMake {
// If LLD is available, add it to the PATH
if builder.config.lld_enabled {
let lld_install_root =
builder.ensure(llvm::Lld { target: builder.config.host_target });
let lld_bin_path = lld_install_root.join("bin");
let old_path = env::var_os("PATH").unwrap_or_default();
let new_path = env::join_paths(
std::iter::once(lld_bin_path).chain(env::split_paths(&old_path)),
)
.expect("Could not add LLD bin path to PATH");
cmd.env("PATH", new_path);
}
}
}
// Only pass correct values for these flags for the `run-make` suite as it
// requires that a C++ compiler was configured which isn't always the case.
if !builder.config.dry_run() && mode == CompiletestMode::RunMake {
let mut cflags = builder.cc_handled_clags(target, CLang::C);
cflags.extend(builder.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::C));
let mut cxxflags = builder.cc_handled_clags(target, CLang::Cxx);
cxxflags.extend(builder.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::Cxx));
cmd.arg("--cc")
.arg(builder.cc(target))
.arg("--cxx")
.arg(builder.cxx(target).unwrap())
.arg("--cflags")
.arg(cflags.join(" "))
.arg("--cxxflags")
.arg(cxxflags.join(" "));
copts_passed = true;
if let Some(ar) = builder.ar(target) {
cmd.arg("--ar").arg(ar);
}
}
if !llvm_components_passed {
cmd.arg("--llvm-components").arg("");
}
if !copts_passed {
cmd.arg("--cc")
.arg("")
.arg("--cxx")
.arg("")
.arg("--cflags")
.arg("")
.arg("--cxxflags")
.arg("");
}
if builder.remote_tested(target) {
cmd.arg("--remote-test-client").arg(builder.tool_exe(Tool::RemoteTestClient));
} else if let Some(tool) = builder.runner(target) {
cmd.arg("--runner").arg(tool);
}
if suite != "mir-opt" {
// Running a C compiler on MSVC requires a few env vars to be set, to be
// sure to set them here.
//
// Note that if we encounter `PATH` we make sure to append to our own `PATH`
// rather than stomp over it.
if !builder.config.dry_run() && target.is_msvc() {
for (k, v) in builder.cc[&target].env() {
if k != "PATH" {
cmd.env(k, v);
}
}
}
}
// Special setup to enable running with sanitizers on MSVC.
if !builder.config.dry_run()
&& target.contains("msvc")
&& builder.config.sanitizers_enabled(target)
{
// Ignore interception failures: not all dlls in the process will have been built with
// address sanitizer enabled (e.g., ntdll.dll).
cmd.env("ASAN_WIN_CONTINUE_ON_INTERCEPTION_FAILURE", "1");
// Add the address sanitizer runtime to the PATH - it is located next to cl.exe.
let asan_runtime_path = builder.cc[&target].path().parent().unwrap().to_path_buf();
let old_path = cmd
.get_envs()
.find_map(|(k, v)| (k == "PATH").then_some(v))
.flatten()
.map_or_else(|| env::var_os("PATH").unwrap_or_default(), |v| v.to_owned());
let new_path = env::join_paths(
env::split_paths(&old_path).chain(std::iter::once(asan_runtime_path)),
)
.expect("Could not add ASAN runtime path to PATH");
cmd.env("PATH", new_path);
}
// Some UI tests trigger behavior in rustc where it reads $CARGO and changes behavior if it exists.
// To make the tests work that rely on it not being set, make sure it is not set.
cmd.env_remove("CARGO");
cmd.env("RUSTC_BOOTSTRAP", "1");
// Override the rustc version used in symbol hashes to reduce the amount of normalization
// needed when diffing test output.
cmd.env("RUSTC_FORCE_RUSTC_VERSION", "compiletest");
cmd.env("DOC_RUST_LANG_ORG_CHANNEL", builder.doc_rust_lang_org_channel());
builder.add_rust_test_threads(&mut cmd);
if builder.config.sanitizers_enabled(target) {
cmd.env("RUSTC_SANITIZER_SUPPORT", "1");
}
if builder.config.profiler_enabled(target) {
cmd.arg("--profiler-runtime");
}
cmd.env("RUST_TEST_TMPDIR", builder.tempdir());
if builder.config.cmd.rustfix_coverage() {
cmd.arg("--rustfix-coverage");
}
cmd.arg("--channel").arg(&builder.config.channel);
if !builder.config.omit_git_hash {
cmd.arg("--git-hash");
}
let git_config = builder.config.git_config();
cmd.arg("--nightly-branch").arg(git_config.nightly_branch);
cmd.arg("--git-merge-commit-email").arg(git_config.git_merge_commit_email);
#[cfg(feature = "build-metrics")]
builder.metrics.begin_test_suite(
build_helper::metrics::TestSuiteMetadata::Compiletest {
suite: suite.into(),
mode: mode.to_string(),
compare_mode: None,
target: self.target.triple.to_string(),
host: self.test_compiler.host.triple.to_string(),
stage: self.test_compiler.stage,
},
builder,
);
let _group = builder.msg_test(
format!("with compiletest suite={suite} mode={mode}"),
target,
test_compiler.stage,
);
try_run_tests(builder, &mut cmd, false);
if let Some(compare_mode) = compare_mode {
cmd.arg("--compare-mode").arg(compare_mode);
#[cfg(feature = "build-metrics")]
builder.metrics.begin_test_suite(
build_helper::metrics::TestSuiteMetadata::Compiletest {
suite: suite.into(),
mode: mode.to_string(),
compare_mode: Some(compare_mode.into()),
target: self.target.triple.to_string(),
host: self.test_compiler.host.triple.to_string(),
stage: self.test_compiler.stage,
},
builder,
);
builder.info(&format!(
"Check compiletest suite={} mode={} compare_mode={} ({} -> {})",
suite, mode, compare_mode, &test_compiler.host, target
));
let _time = helpers::timeit(builder);
try_run_tests(builder, &mut cmd, false);
}
}
fn metadata(&self) -> Option<StepMetadata> {
Some(
StepMetadata::test(&format!("compiletest-{}", self.suite), self.target)
.stage(self.test_compiler.stage),
)
}
}
/// Runs the documentation tests for a book in `src/doc` using the `rustdoc` of `test_compiler`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct BookTest {
test_compiler: Compiler,
path: PathBuf,
name: &'static str,
is_ext_doc: bool,
dependencies: Vec<&'static str>,
}
impl Step for BookTest {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.never()
}
fn run(self, builder: &Builder<'_>) {
// External docs are different from local because:
// - Some books need pre-processing by mdbook before being tested.
// - They need to save their state to toolstate.
// - They are only tested on the "checktools" builders.
//
// The local docs are tested by default, and we don't want to pay the
// cost of building mdbook, so they use `rustdoc --test` directly.
// Also, the unstable book is special because SUMMARY.md is generated,
// so it is easier to just run `rustdoc` on its files.
if self.is_ext_doc {
self.run_ext_doc(builder);
} else {
self.run_local_doc(builder);
}
}
}
impl BookTest {
/// This runs the equivalent of `mdbook test` (via the rustbook wrapper)
/// which in turn runs `rustdoc --test` on each file in the book.
fn run_ext_doc(self, builder: &Builder<'_>) {
let test_compiler = self.test_compiler;
builder.std(test_compiler, test_compiler.host);
// mdbook just executes a binary named "rustdoc", so we need to update
// PATH so that it points to our rustdoc.
let mut rustdoc_path = builder.rustdoc_for_compiler(test_compiler);
rustdoc_path.pop();
let old_path = env::var_os("PATH").unwrap_or_default();
let new_path = env::join_paths(iter::once(rustdoc_path).chain(env::split_paths(&old_path)))
.expect("could not add rustdoc to PATH");
let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
let path = builder.src.join(&self.path);
// Books often have feature-gated example text.
rustbook_cmd.env("RUSTC_BOOTSTRAP", "1");
rustbook_cmd.env("PATH", new_path).arg("test").arg(path);
// Books may also need to build dependencies. For example, `TheBook` has
// code samples which use the `trpl` crate. For the `rustdoc` invocation
// to find them them successfully, they need to be built first and their
// paths used to generate the
let libs = if !self.dependencies.is_empty() {
let mut lib_paths = vec![];
for dep in self.dependencies {
let mode = Mode::ToolRustcPrivate;
let target = builder.config.host_target;
let cargo = tool::prepare_tool_cargo(
builder,
test_compiler,
mode,
target,
Kind::Build,
dep,
SourceType::Submodule,
&[],
);
let stamp = BuildStamp::new(&builder.cargo_out(test_compiler, mode, target))
.with_prefix(PathBuf::from(dep).file_name().and_then(|v| v.to_str()).unwrap());
let output_paths = run_cargo(builder, cargo, vec![], &stamp, vec![], false, false);
let directories = output_paths
.into_iter()
.filter_map(|p| p.parent().map(ToOwned::to_owned))
.fold(HashSet::new(), |mut set, dir| {
set.insert(dir);
set
});
lib_paths.extend(directories);
}
lib_paths
} else {
vec![]
};
if !libs.is_empty() {
let paths = libs
.into_iter()
.map(|path| path.into_os_string())
.collect::<Vec<OsString>>()
.join(OsStr::new(","));
rustbook_cmd.args([OsString::from("--library-path"), paths]);
}
builder.add_rust_test_threads(&mut rustbook_cmd);
let _guard = builder.msg_test(
format_args!("mdbook {}", self.path.display()),
test_compiler.host,
test_compiler.stage,
);
let _time = helpers::timeit(builder);
let toolstate = if rustbook_cmd.delay_failure().run(builder) {
ToolState::TestPass
} else {
ToolState::TestFail
};
builder.save_toolstate(self.name, toolstate);
}
/// This runs `rustdoc --test` on all `.md` files in the path.
fn run_local_doc(self, builder: &Builder<'_>) {
let test_compiler = self.test_compiler;
let host = self.test_compiler.host;
builder.std(test_compiler, host);
let _guard = builder.msg_test(
format!("book {}", self.name),
test_compiler.host,
test_compiler.stage,
);
// Do a breadth-first traversal of the `src/doc` directory and just run
// tests for all files that end in `*.md`
let mut stack = vec![builder.src.join(self.path)];
let _time = helpers::timeit(builder);
let mut files = Vec::new();
while let Some(p) = stack.pop() {
if p.is_dir() {
stack.extend(t!(p.read_dir()).map(|p| t!(p).path()));
continue;
}
if p.extension().and_then(|s| s.to_str()) != Some("md") {
continue;
}
files.push(p);
}
files.sort();
for file in files {
markdown_test(builder, test_compiler, &file);
}
}
}
macro_rules! test_book {
($(
$name:ident, $path:expr, $book_name:expr,
default=$default:expr
$(,submodules = $submodules:expr)?
$(,dependencies=$dependencies:expr)?
;
)+) => {
$(
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name {
test_compiler: Compiler,
}
impl Step for $name {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path($path)
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
const { $default }
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure($name {
test_compiler: run.builder.compiler(run.builder.top_stage, run.target),
});
}
fn run(self, builder: &Builder<'_>) {
$(
for submodule in $submodules {
builder.require_submodule(submodule, None);
}
)*
let dependencies = vec![];
$(
let mut dependencies = dependencies;
for dep in $dependencies {
dependencies.push(dep);
}
)?
builder.ensure(BookTest {
test_compiler: self.test_compiler,
path: PathBuf::from($path),
name: $book_name,
is_ext_doc: !$default,
dependencies,
});
}
}
)+
}
}
test_book!(
Nomicon, "src/doc/nomicon", "nomicon", default=false, submodules=["src/doc/nomicon"];
Reference, "src/doc/reference", "reference", default=false, submodules=["src/doc/reference"];
RustdocBook, "src/doc/rustdoc", "rustdoc", default=true;
RustcBook, "src/doc/rustc", "rustc", default=true;
RustByExample, "src/doc/rust-by-example", "rust-by-example", default=false, submodules=["src/doc/rust-by-example"];
EmbeddedBook, "src/doc/embedded-book", "embedded-book", default=false, submodules=["src/doc/embedded-book"];
TheBook, "src/doc/book", "book", default=false, submodules=["src/doc/book"], dependencies=["src/doc/book/packages/trpl"];
UnstableBook, "src/doc/unstable-book", "unstable-book", default=true;
EditionGuide, "src/doc/edition-guide", "edition-guide", default=false, submodules=["src/doc/edition-guide"];
);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ErrorIndex {
compilers: RustcPrivateCompilers,
}
impl Step for ErrorIndex {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
// Also add `error-index` here since that is what appears in the error message
// when this fails.
run.path("src/tools/error_index_generator").alias("error-index")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
// error_index_generator depends on librustdoc. Use the compiler that
// is normally used to build rustdoc for other tests (like compiletest
// tests in tests/rustdoc) so that it shares the same artifacts.
let compilers = RustcPrivateCompilers::new(
run.builder,
run.builder.top_stage,
run.builder.config.host_target,
);
run.builder.ensure(ErrorIndex { compilers });
}
/// Runs the error index generator tool to execute the tests located in the error
/// index.
///
/// The `error_index_generator` tool lives in `src/tools` and is used to
/// generate a markdown file from the error indexes of the code base which is
/// then passed to `rustdoc --test`.
fn run(self, builder: &Builder<'_>) {
// The compiler that we are testing
let target_compiler = self.compilers.target_compiler();
let dir = testdir(builder, target_compiler.host);
t!(fs::create_dir_all(&dir));
let output = dir.join("error-index.md");
let mut tool = tool::ErrorIndex::command(builder, self.compilers);
tool.arg("markdown").arg(&output);
let guard = builder.msg_test("error-index", target_compiler.host, target_compiler.stage);
let _time = helpers::timeit(builder);
tool.run_capture(builder);
drop(guard);
// The tests themselves need to link to std, so make sure it is
// available.
builder.std(target_compiler, target_compiler.host);
markdown_test(builder, target_compiler, &output);
}
}
fn markdown_test(builder: &Builder<'_>, compiler: Compiler, markdown: &Path) -> bool {
if let Ok(contents) = fs::read_to_string(markdown)
&& !contents.contains("```")
{
return true;
}
builder.do_if_verbose(|| println!("doc tests for: {}", markdown.display()));
let mut cmd = builder.rustdoc_cmd(compiler);
builder.add_rust_test_threads(&mut cmd);
// allow for unstable options such as new editions
cmd.arg("-Z");
cmd.arg("unstable-options");
cmd.arg("--test");
cmd.arg(markdown);
cmd.env("RUSTC_BOOTSTRAP", "1");
let test_args = builder.config.test_args().join(" ");
cmd.arg("--test-args").arg(test_args);
cmd = cmd.delay_failure();
if !builder.config.verbose_tests {
cmd.run_capture(builder).is_success()
} else {
cmd.run(builder)
}
}
/// Runs `cargo test` for the compiler crates in `compiler/`.
///
/// (This step does not test `rustc_codegen_cranelift` or `rustc_codegen_gcc`,
/// which have their own separate test steps.)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CrateLibrustc {
/// The compiler that will run unit tests and doctests on the in-tree rustc source.
build_compiler: Compiler,
target: TargetSelection,
crates: Vec<String>,
}
impl Step for CrateLibrustc {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.crate_or_deps("rustc-main").path("compiler")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
let host = run.build_triple();
let build_compiler = builder.compiler(builder.top_stage - 1, host);
let crates = run.make_run_crates(Alias::Compiler);
builder.ensure(CrateLibrustc { build_compiler, target: run.target, crates });
}
fn run(self, builder: &Builder<'_>) {
builder.std(self.build_compiler, self.target);
// To actually run the tests, delegate to a copy of the `Crate` step.
builder.ensure(Crate {
build_compiler: self.build_compiler,
target: self.target,
mode: Mode::Rustc,
crates: self.crates,
});
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("CrateLibrustc", self.target).built_by(self.build_compiler))
}
}
/// Given a `cargo test` subcommand, add the appropriate flags and run it.
///
/// Returns whether the test succeeded.
fn run_cargo_test<'a>(
cargo: builder::Cargo,
libtest_args: &[&str],
crates: &[String],
description: impl Into<Option<&'a str>>,
target: TargetSelection,
builder: &Builder<'_>,
) -> bool {
let compiler = cargo.compiler();
let stage = match cargo.mode() {
Mode::Std => compiler.stage,
_ => compiler.stage + 1,
};
let mut cargo = prepare_cargo_test(cargo, libtest_args, crates, target, builder);
let _time = helpers::timeit(builder);
let _group = description.into().and_then(|what| builder.msg_test(what, target, stage));
#[cfg(feature = "build-metrics")]
builder.metrics.begin_test_suite(
build_helper::metrics::TestSuiteMetadata::CargoPackage {
crates: crates.iter().map(|c| c.to_string()).collect(),
target: target.triple.to_string(),
host: compiler.host.triple.to_string(),
stage: compiler.stage,
},
builder,
);
add_flags_and_try_run_tests(builder, &mut cargo)
}
/// Given a `cargo test` subcommand, pass it the appropriate test flags given a `builder`.
fn prepare_cargo_test(
cargo: builder::Cargo,
libtest_args: &[&str],
crates: &[String],
target: TargetSelection,
builder: &Builder<'_>,
) -> BootstrapCommand {
let compiler = cargo.compiler();
let mut cargo: BootstrapCommand = cargo.into();
// Propagate `--bless` if it has not already been set/unset
// Any tools that want to use this should bless if `RUSTC_BLESS` is set to
// anything other than `0`.
if builder.config.cmd.bless() && !cargo.get_envs().any(|v| v.0 == "RUSTC_BLESS") {
cargo.env("RUSTC_BLESS", "Gesundheit");
}
// Pass in some standard flags then iterate over the graph we've discovered
// in `cargo metadata` with the maps above and figure out what `-p`
// arguments need to get passed.
if builder.kind == Kind::Test && !builder.fail_fast {
cargo.arg("--no-fail-fast");
}
if builder.config.json_output {
cargo.arg("--message-format=json");
}
match builder.doc_tests {
DocTests::Only => {
cargo.arg("--doc");
}
DocTests::No => {
cargo.args(["--bins", "--examples", "--tests", "--benches"]);
}
DocTests::Yes => {}
}
for krate in crates {
cargo.arg("-p").arg(krate);
}
cargo.arg("--").args(builder.config.test_args()).args(libtest_args);
if !builder.config.verbose_tests {
cargo.arg("--quiet");
}
// The tests are going to run with the *target* libraries, so we need to
// ensure that those libraries show up in the LD_LIBRARY_PATH equivalent.
//
// Note that to run the compiler we need to run with the *host* libraries,
// but our wrapper scripts arrange for that to be the case anyway.
//
// We skip everything on Miri as then this overwrites the libdir set up
// by `Cargo::new` and that actually makes things go wrong.
if builder.kind != Kind::Miri {
let mut dylib_paths = builder.rustc_lib_paths(compiler);
dylib_paths.push(builder.sysroot_target_libdir(compiler, target));
helpers::add_dylib_path(dylib_paths, &mut cargo);
}
if builder.remote_tested(target) {
cargo.env(
format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)),
format!("{} run 0", builder.tool_exe(Tool::RemoteTestClient).display()),
);
} else if let Some(tool) = builder.runner(target) {
cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), tool);
}
cargo
}
/// Runs `cargo test` for standard library crates.
///
/// (Also used internally to run `cargo test` for compiler crates.)
///
/// FIXME(Zalathar): Try to split this into two separate steps: a user-visible
/// step for testing standard library crates, and an internal step used for both
/// library crates and compiler crates.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Crate {
/// The compiler that will *build* libstd or rustc in test mode.
build_compiler: Compiler,
target: TargetSelection,
mode: Mode,
crates: Vec<String>,
}
impl Step for Crate {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.crate_or_deps("sysroot").crate_or_deps("coretests").crate_or_deps("alloctests")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
let host = run.build_triple();
let build_compiler = builder.compiler(builder.top_stage, host);
let crates = run
.paths
.iter()
.map(|p| builder.crate_paths[&p.assert_single_path().path].clone())
.collect();
builder.ensure(Crate { build_compiler, target: run.target, mode: Mode::Std, crates });
}
/// Runs all unit tests plus documentation tests for a given crate defined
/// by a `Cargo.toml` (single manifest)
///
/// This is what runs tests for crates like the standard library, compiler, etc.
/// It essentially is the driver for running `cargo test`.
///
/// Currently this runs all tests for a DAG by passing a bunch of `-p foo`
/// arguments, and those arguments are discovered from `cargo metadata`.
fn run(self, builder: &Builder<'_>) {
let build_compiler = self.build_compiler;
let target = self.target;
let mode = self.mode;
// Prepare sysroot
// See [field@compile::Std::force_recompile].
builder.ensure(Std::new(build_compiler, build_compiler.host).force_recompile(true));
let mut cargo = if builder.kind == Kind::Miri {
if builder.top_stage == 0 {
eprintln!("ERROR: `x.py miri` requires stage 1 or higher");
std::process::exit(1);
}
// Build `cargo miri test` command
// (Implicitly prepares target sysroot)
let mut cargo = builder::Cargo::new(
builder,
build_compiler,
mode,
SourceType::InTree,
target,
Kind::MiriTest,
);
// This hack helps bootstrap run standard library tests in Miri. The issue is as
// follows: when running `cargo miri test` on libcore, cargo builds a local copy of core
// and makes it a dependency of the integration test crate. This copy duplicates all the
// lang items, so the build fails. (Regular testing avoids this because the sysroot is a
// literal copy of what `cargo build` produces, but since Miri builds its own sysroot
// this does not work for us.) So we need to make it so that the locally built libcore
// contains all the items from `core`, but does not re-define them -- we want to replace
// the entire crate but a re-export of the sysroot crate. We do this by swapping out the
// source file: if `MIRI_REPLACE_LIBRS_IF_NOT_TEST` is set and we are building a
// `lib.rs` file, and a `lib.miri.rs` file exists in the same folder, we build that
// instead. But crucially we only do that for the library, not the test builds.
cargo.env("MIRI_REPLACE_LIBRS_IF_NOT_TEST", "1");
// std needs to be built with `-Zforce-unstable-if-unmarked`. For some reason the builder
// does not set this directly, but relies on the rustc wrapper to set it, and we are not using
// the wrapper -- hence we have to set it ourselves.
cargo.rustflag("-Zforce-unstable-if-unmarked");
cargo
} else {
// Also prepare a sysroot for the target.
if !builder.config.is_host_target(target) {
builder.ensure(compile::Std::new(build_compiler, target).force_recompile(true));
builder.ensure(RemoteCopyLibs { build_compiler, target });
}
// Build `cargo test` command
builder::Cargo::new(
builder,
build_compiler,
mode,
SourceType::InTree,
target,
builder.kind,
)
};
match mode {
Mode::Std => {
if builder.kind == Kind::Miri {
// We can't use `std_cargo` as that uses `optimized-compiler-builtins` which
// needs host tools for the given target. This is similar to what `compile::Std`
// does when `is_for_mir_opt_tests` is true. There's probably a chance for
// de-duplication here... `std_cargo` should support a mode that avoids needing
// host tools.
cargo
.arg("--manifest-path")
.arg(builder.src.join("library/sysroot/Cargo.toml"));
} else {
compile::std_cargo(builder, target, &mut cargo, &[]);
}
}
Mode::Rustc => {
compile::rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
}
_ => panic!("can only test libraries"),
};
let mut crates = self.crates.clone();
// The core and alloc crates can't directly be tested. We
// could silently ignore them, but adding their own test
// crates is less confusing for users. We still keep core and
// alloc themself for doctests
if crates.iter().any(|crate_| crate_ == "core") {
crates.push("coretests".to_owned());
}
if crates.iter().any(|crate_| crate_ == "alloc") {
crates.push("alloctests".to_owned());
}
run_cargo_test(cargo, &[], &crates, &*crate_description(&self.crates), target, builder);
}
}
/// Run cargo tests for the rustdoc crate.
/// Rustdoc is special in various ways, which is why this step is different from `Crate`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CrateRustdoc {
host: TargetSelection,
}
impl Step for CrateRustdoc {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.paths(&["src/librustdoc", "src/tools/rustdoc"])
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
builder.ensure(CrateRustdoc { host: run.target });
}
fn run(self, builder: &Builder<'_>) {
let target = self.host;
let compiler = if builder.download_rustc() {
builder.compiler(builder.top_stage, target)
} else {
// Use the previous stage compiler to reuse the artifacts that are
// created when running compiletest for tests/rustdoc. If this used
// `compiler`, then it would cause rustdoc to be built *again*, which
// isn't really necessary.
builder.compiler_for(builder.top_stage, target, target)
};
// NOTE: normally `ensure(Rustc)` automatically runs `ensure(Std)` for us. However, when
// using `download-rustc`, the rustc_private artifacts may be in a *different sysroot* from
// the target rustdoc (`ci-rustc-sysroot` vs `stage2`). In that case, we need to ensure this
// explicitly to make sure it ends up in the stage2 sysroot.
builder.std(compiler, target);
builder.ensure(compile::Rustc::new(compiler, target));
let mut cargo = tool::prepare_tool_cargo(
builder,
compiler,
Mode::ToolRustcPrivate,
target,
builder.kind,
"src/tools/rustdoc",
SourceType::InTree,
&[],
);
if self.host.contains("musl") {
cargo.arg("'-Ctarget-feature=-crt-static'");
}
// This is needed for running doctests on librustdoc. This is a bit of
// an unfortunate interaction with how bootstrap works and how cargo
// sets up the dylib path, and the fact that the doctest (in
// html/markdown.rs) links to rustc-private libs. For stage1, the
// compiler host dylibs (in stage1/lib) are not the same as the target
// dylibs (in stage1/lib/rustlib/...). This is different from a normal
// rust distribution where they are the same.
//
// On the cargo side, normal tests use `target_process` which handles
// setting up the dylib for a *target* (stage1/lib/rustlib/... in this
// case). However, for doctests it uses `rustdoc_process` which only
// sets up the dylib path for the *host* (stage1/lib), which is the
// wrong directory.
//
// Recall that we special-cased `compiler_for(top_stage)` above, so we always use stage1.
//
// It should be considered to just stop running doctests on
// librustdoc. There is only one test, and it doesn't look too
// important. There might be other ways to avoid this, but it seems
// pretty convoluted.
//
// See also https://github.com/rust-lang/rust/issues/13983 where the
// host vs target dylibs for rustdoc are consistently tricky to deal
// with.
//
// Note that this set the host libdir for `download_rustc`, which uses a normal rust distribution.
let libdir = if builder.download_rustc() {
builder.rustc_libdir(compiler)
} else {
builder.sysroot_target_libdir(compiler, target).to_path_buf()
};
let mut dylib_path = dylib_path();
dylib_path.insert(0, PathBuf::from(&*libdir));
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
run_cargo_test(cargo, &[], &["rustdoc:0.0.0".to_string()], "rustdoc", target, builder);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CrateRustdocJsonTypes {
build_compiler: Compiler,
target: TargetSelection,
}
impl Step for CrateRustdocJsonTypes {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/rustdoc-json-types")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
builder.ensure(CrateRustdocJsonTypes {
build_compiler: get_tool_target_compiler(
builder,
ToolTargetBuildMode::Build(run.target),
),
target: run.target,
});
}
fn run(self, builder: &Builder<'_>) {
let target = self.target;
let cargo = tool::prepare_tool_cargo(
builder,
self.build_compiler,
Mode::ToolTarget,
target,
builder.kind,
"src/rustdoc-json-types",
SourceType::InTree,
&[],
);
// FIXME: this looks very wrong, libtest doesn't accept `-C` arguments and the quotes are fishy.
let libtest_args = if target.contains("musl") {
["'-Ctarget-feature=-crt-static'"].as_slice()
} else {
&[]
};
run_cargo_test(
cargo,
libtest_args,
&["rustdoc-json-types".to_string()],
"rustdoc-json-types",
target,
builder,
);
}
}
/// Some test suites are run inside emulators or on remote devices, and most
/// of our test binaries are linked dynamically which means we need to ship
/// the standard library and such to the emulator ahead of time. This step
/// represents this and is a dependency of all test suites.
///
/// Most of the time this is a no-op. For some steps such as shipping data to
/// QEMU we have to build our own tools so we've got conditional dependencies
/// on those programs as well. Note that the remote test client is built for
/// the build target (us) and the server is built for the target.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RemoteCopyLibs {
build_compiler: Compiler,
target: TargetSelection,
}
impl Step for RemoteCopyLibs {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.never()
}
fn run(self, builder: &Builder<'_>) {
let build_compiler = self.build_compiler;
let target = self.target;
if !builder.remote_tested(target) {
return;
}
builder.std(build_compiler, target);
builder.info(&format!("REMOTE copy libs to emulator ({target})"));
let remote_test_server = builder.ensure(tool::RemoteTestServer { build_compiler, target });
// Spawn the emulator and wait for it to come online
let tool = builder.tool_exe(Tool::RemoteTestClient);
let mut cmd = command(&tool);
cmd.arg("spawn-emulator")
.arg(target.triple)
.arg(&remote_test_server.tool_path)
.arg(builder.tempdir());
if let Some(rootfs) = builder.qemu_rootfs(target) {
cmd.arg(rootfs);
}
cmd.run(builder);
// Push all our dylibs to the emulator
for f in t!(builder.sysroot_target_libdir(build_compiler, target).read_dir()) {
let f = t!(f);
if helpers::is_dylib(&f.path()) {
command(&tool).arg("push").arg(f.path()).run(builder);
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Distcheck;
impl Step for Distcheck {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.alias("distcheck")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Distcheck);
}
/// Runs `distcheck`, which is a collection of smoke tests:
///
/// - Run `make check` from an unpacked dist tarball to make sure we can at the minimum run
/// check steps from those sources.
/// - Check that selected dist components (`rust-src` only at the moment) at least have expected
/// directory shape and crate manifests that cargo can generate a lockfile from.
/// - Check that we can run `cargo metadata` on the workspace in the `rustc-dev` component
///
/// FIXME(#136822): dist components are under-tested.
fn run(self, builder: &Builder<'_>) {
// Use a temporary directory completely outside the current checkout, to avoid reusing any
// local source code, built artifacts or configuration by accident
let root_dir = std::env::temp_dir().join("distcheck");
distcheck_plain_source_tarball(builder, &root_dir.join("distcheck-rustc-src"));
distcheck_rust_src(builder, &root_dir.join("distcheck-rust-src"));
distcheck_rustc_dev(builder, &root_dir.join("distcheck-rustc-dev"));
}
}
/// Check that we can build some basic things from the plain source tarball
fn distcheck_plain_source_tarball(builder: &Builder<'_>, plain_src_dir: &Path) {
builder.info("Distcheck plain source tarball");
let plain_src_tarball = builder.ensure(dist::PlainSourceTarball);
builder.clear_dir(plain_src_dir);
let configure_args: Vec<String> = std::env::var("DISTCHECK_CONFIGURE_ARGS")
.map(|args| args.split(" ").map(|s| s.to_string()).collect::<Vec<String>>())
.unwrap_or_default();
command("tar")
.arg("-xf")
.arg(plain_src_tarball.tarball())
.arg("--strip-components=1")
.current_dir(plain_src_dir)
.run(builder);
command("./configure")
.arg("--set")
.arg("rust.omit-git-hash=false")
.args(&configure_args)
.arg("--enable-vendor")
.current_dir(plain_src_dir)
.run(builder);
command(helpers::make(&builder.config.host_target.triple))
.arg("check")
// Do not run the build as if we were in CI, otherwise git would be assumed to be
// present, but we build from a tarball here
.env("GITHUB_ACTIONS", "0")
.current_dir(plain_src_dir)
.run(builder);
// Mitigate pressure on small-capacity disks.
builder.remove_dir(plain_src_dir);
}
/// Check that rust-src has all of libstd's dependencies
fn distcheck_rust_src(builder: &Builder<'_>, src_dir: &Path) {
builder.info("Distcheck rust-src");
let src_tarball = builder.ensure(dist::Src);
builder.clear_dir(src_dir);
command("tar")
.arg("-xf")
.arg(src_tarball.tarball())
.arg("--strip-components=1")
.current_dir(src_dir)
.run(builder);
let toml = src_dir.join("rust-src/lib/rustlib/src/rust/library/std/Cargo.toml");
command(&builder.initial_cargo)
// Will read the libstd Cargo.toml
// which uses the unstable `public-dependency` feature.
.env("RUSTC_BOOTSTRAP", "1")
.arg("generate-lockfile")
.arg("--manifest-path")
.arg(&toml)
.current_dir(src_dir)
.run(builder);
// Mitigate pressure on small-capacity disks.
builder.remove_dir(src_dir);
}
/// Check that rustc-dev's compiler crate source code can be loaded with `cargo metadata`
fn distcheck_rustc_dev(builder: &Builder<'_>, dir: &Path) {
builder.info("Distcheck rustc-dev");
let tarball = builder.ensure(dist::RustcDev::new(builder, builder.host_target)).unwrap();
builder.clear_dir(dir);
command("tar")
.arg("-xf")
.arg(tarball.tarball())
.arg("--strip-components=1")
.current_dir(dir)
.run(builder);
command(&builder.initial_cargo)
.arg("metadata")
.arg("--manifest-path")
.arg("rustc-dev/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml")
.env("RUSTC_BOOTSTRAP", "1")
// We might not have a globally available `rustc` binary on CI
.env("RUSTC", &builder.initial_rustc)
.current_dir(dir)
.run(builder);
// Mitigate pressure on small-capacity disks.
builder.remove_dir(dir);
}
/// Runs unit tests in `bootstrap_test.py`, which test the Python parts of bootstrap.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct BootstrapPy;
impl Step for BootstrapPy {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.alias("bootstrap-py")
}
fn is_default_step(builder: &Builder<'_>) -> bool {
// Bootstrap tests might not be perfectly self-contained and can depend
// on the environment, so only run them by default in CI, not locally.
// See `test::Bootstrap::should_run`.
builder.config.is_running_on_ci
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(BootstrapPy)
}
fn run(self, builder: &Builder<'_>) -> Self::Output {
let mut check_bootstrap = command(
builder.config.python.as_ref().expect("python is required for running bootstrap tests"),
);
check_bootstrap
.args(["-m", "unittest", "bootstrap_test.py"])
// Forward command-line args after `--` to unittest, for filtering etc.
.args(builder.config.test_args())
.env("BUILD_DIR", &builder.out)
.env("BUILD_PLATFORM", builder.build.host_target.triple)
.env("BOOTSTRAP_TEST_RUSTC_BIN", &builder.initial_rustc)
.env("BOOTSTRAP_TEST_CARGO_BIN", &builder.initial_cargo)
.current_dir(builder.src.join("src/bootstrap/"));
check_bootstrap.delay_failure().run(builder);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Bootstrap;
impl Step for Bootstrap {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/bootstrap")
}
fn is_default_step(builder: &Builder<'_>) -> bool {
// Bootstrap tests might not be perfectly self-contained and can depend on the external
// environment, submodules that are checked out, etc.
// Therefore we only run them by default on CI.
builder.config.is_running_on_ci
}
/// Tests the build system itself.
fn run(self, builder: &Builder<'_>) {
let host = builder.config.host_target;
let build_compiler = builder.compiler(0, host);
// Some tests require cargo submodule to be present.
builder.build.require_submodule("src/tools/cargo", None);
let mut cargo = tool::prepare_tool_cargo(
builder,
build_compiler,
Mode::ToolBootstrap,
host,
Kind::Test,
"src/bootstrap",
SourceType::InTree,
&[],
);
cargo.release_build(false);
cargo
.rustflag("-Cdebuginfo=2")
.env("CARGO_TARGET_DIR", builder.out.join("bootstrap"))
// Needed for insta to correctly write pending snapshots to the right directories.
.env("INSTA_WORKSPACE_ROOT", &builder.src)
.env("RUSTC_BOOTSTRAP", "1");
if builder.config.cmd.bless() {
// Tell `insta` to automatically bless any failing `.snap` files.
// Unlike compiletest blessing, the tests might still report failure.
// Does not bless inline snapshots.
cargo.env("INSTA_UPDATE", "always");
}
run_cargo_test(cargo, &[], &[], None, host, builder);
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Bootstrap);
}
}
fn get_compiler_to_test(builder: &Builder<'_>, target: TargetSelection) -> Compiler {
builder.compiler(builder.top_stage, target)
}
/// Tests the Platform Support page in the rustc book.
/// `test_compiler` is used to query the actual targets that are checked.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TierCheck {
test_compiler: Compiler,
}
impl Step for TierCheck {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/tier-check")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
run.builder
.ensure(TierCheck { test_compiler: get_compiler_to_test(run.builder, run.target) });
}
fn run(self, builder: &Builder<'_>) {
let tool_build_compiler = builder.compiler(0, builder.host_target);
let mut cargo = tool::prepare_tool_cargo(
builder,
tool_build_compiler,
Mode::ToolBootstrap,
tool_build_compiler.host,
Kind::Run,
"src/tools/tier-check",
SourceType::InTree,
&[],
);
cargo.arg(builder.src.join("src/doc/rustc/src/platform-support.md"));
cargo.arg(builder.rustc(self.test_compiler));
let _guard = builder.msg_test(
"platform support check",
self.test_compiler.host,
self.test_compiler.stage,
);
BootstrapCommand::from(cargo).delay_failure().run(builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("tier-check", self.test_compiler.host))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LintDocs {
build_compiler: Compiler,
target: TargetSelection,
}
impl Step for LintDocs {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/lint-docs")
}
fn is_default_step(builder: &Builder<'_>) -> bool {
// Lint docs tests might not work with stage 1, so do not run this test by default in
// `x test` below stage 2.
builder.top_stage >= 2
}
fn make_run(run: RunConfig<'_>) {
if run.builder.top_stage < 2 {
eprintln!("WARNING: lint-docs tests might not work below stage 2");
}
run.builder.ensure(LintDocs {
build_compiler: prepare_doc_compiler(
run.builder,
run.builder.config.host_target,
run.builder.top_stage,
),
target: run.target,
});
}
/// Tests that the lint examples in the rustc book generate the correct
/// lints and have the expected format.
fn run(self, builder: &Builder<'_>) {
builder.ensure(crate::core::build_steps::doc::RustcBook::validate(
self.build_compiler,
self.target,
));
}
fn metadata(&self) -> Option<StepMetadata> {
Some(StepMetadata::test("lint-docs", self.target).built_by(self.build_compiler))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RustInstaller;
impl Step for RustInstaller {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/rust-installer")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Self);
}
/// Ensure the version placeholder replacement tool builds
fn run(self, builder: &Builder<'_>) {
let bootstrap_host = builder.config.host_target;
let build_compiler = builder.compiler(0, bootstrap_host);
let cargo = tool::prepare_tool_cargo(
builder,
build_compiler,
Mode::ToolBootstrap,
bootstrap_host,
Kind::Test,
"src/tools/rust-installer",
SourceType::InTree,
&[],
);
let _guard = builder.msg_test("rust-installer", bootstrap_host, 1);
run_cargo_test(cargo, &[], &[], None, bootstrap_host, builder);
// We currently don't support running the test.sh script outside linux(?) environments.
// Eventually this should likely migrate to #[test]s in rust-installer proper rather than a
// set of scripts, which will likely allow dropping this if.
if bootstrap_host != "x86_64-unknown-linux-gnu" {
return;
}
let mut cmd = command(builder.src.join("src/tools/rust-installer/test.sh"));
let tmpdir = testdir(builder, build_compiler.host).join("rust-installer");
let _ = std::fs::remove_dir_all(&tmpdir);
let _ = std::fs::create_dir_all(&tmpdir);
cmd.current_dir(&tmpdir);
cmd.env("CARGO_TARGET_DIR", tmpdir.join("cargo-target"));
cmd.env("CARGO", &builder.initial_cargo);
cmd.env("RUSTC", &builder.initial_rustc);
cmd.env("TMP_DIR", &tmpdir);
cmd.delay_failure().run(builder);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TestHelpers {
pub target: TargetSelection,
}
impl Step for TestHelpers {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("tests/auxiliary/rust_test_helpers.c")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(TestHelpers { target: run.target })
}
/// Compiles the `rust_test_helpers.c` library which we used in various
/// `run-pass` tests for ABI testing.
fn run(self, builder: &Builder<'_>) {
if builder.config.dry_run() {
return;
}
// The x86_64-fortanix-unknown-sgx target doesn't have a working C
// toolchain. However, some x86_64 ELF objects can be linked
// without issues. Use this hack to compile the test helpers.
let target = if self.target == "x86_64-fortanix-unknown-sgx" {
TargetSelection::from_user("x86_64-unknown-linux-gnu")
} else {
self.target
};
let dst = builder.test_helpers_out(target);
let src = builder.src.join("tests/auxiliary/rust_test_helpers.c");
if up_to_date(&src, &dst.join("librust_test_helpers.a")) {
return;
}
let _guard = builder.msg_unstaged(Kind::Build, "test helpers", target);
t!(fs::create_dir_all(&dst));
let mut cfg = cc::Build::new();
// We may have found various cross-compilers a little differently due to our
// extra configuration, so inform cc of these compilers. Note, though, that
// on MSVC we still need cc's detection of env vars (ugh).
if !target.is_msvc() {
if let Some(ar) = builder.ar(target) {
cfg.archiver(ar);
}
cfg.compiler(builder.cc(target));
}
cfg.cargo_metadata(false)
.out_dir(&dst)
.target(&target.triple)
.host(&builder.config.host_target.triple)
.opt_level(0)
.warnings(false)
.debug(false)
.file(builder.src.join("tests/auxiliary/rust_test_helpers.c"))
.compile("rust_test_helpers");
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CodegenCranelift {
compilers: RustcPrivateCompilers,
target: TargetSelection,
}
impl Step for CodegenCranelift {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.paths(&["compiler/rustc_codegen_cranelift"])
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
let host = run.build_triple();
let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, host);
if builder.doc_tests == DocTests::Only {
return;
}
if builder.download_rustc() {
builder.info("CI rustc uses the default codegen backend. skipping");
return;
}
if !target_supports_cranelift_backend(run.target) {
builder.info("target not supported by rustc_codegen_cranelift. skipping");
return;
}
if builder.remote_tested(run.target) {
builder.info("remote testing is not supported by rustc_codegen_cranelift. skipping");
return;
}
if !builder
.config
.enabled_codegen_backends(run.target)
.contains(&CodegenBackendKind::Cranelift)
{
builder.info("cranelift not in rust.codegen-backends. skipping");
return;
}
builder.ensure(CodegenCranelift { compilers, target: run.target });
}
fn run(self, builder: &Builder<'_>) {
let compilers = self.compilers;
let build_compiler = compilers.build_compiler();
// We need to run the cranelift tests with the compiler against cranelift links to, not with
// the build compiler.
let target_compiler = compilers.target_compiler();
let target = self.target;
builder.std(target_compiler, target);
let mut cargo = builder::Cargo::new(
builder,
target_compiler,
Mode::Codegen, // Must be codegen to ensure dlopen on compiled dylibs works
SourceType::InTree,
target,
Kind::Run,
);
cargo.current_dir(&builder.src.join("compiler/rustc_codegen_cranelift"));
cargo
.arg("--manifest-path")
.arg(builder.src.join("compiler/rustc_codegen_cranelift/build_system/Cargo.toml"));
compile::rustc_cargo_env(builder, &mut cargo, target);
// Avoid incremental cache issues when changing rustc
cargo.env("CARGO_BUILD_INCREMENTAL", "false");
let _guard = builder.msg_test(
"rustc_codegen_cranelift",
target_compiler.host,
target_compiler.stage,
);
// FIXME handle vendoring for source tarballs before removing the --skip-test below
let download_dir = builder.out.join("cg_clif_download");
cargo
.arg("--")
.arg("test")
.arg("--download-dir")
.arg(&download_dir)
.arg("--out-dir")
.arg(builder.stage_out(build_compiler, Mode::Codegen).join("cg_clif"))
.arg("--no-unstable-features")
.arg("--use-backend")
.arg("cranelift")
// Avoid having to vendor the standard library dependencies
.arg("--sysroot")
.arg("llvm")
// These tests depend on crates that are not yet vendored
// FIXME remove once vendoring is handled
.arg("--skip-test")
.arg("testsuite.extended_sysroot");
cargo.into_cmd().run(builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(
StepMetadata::test("rustc_codegen_cranelift", self.target)
.built_by(self.compilers.build_compiler()),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CodegenGCC {
compilers: RustcPrivateCompilers,
target: TargetSelection,
}
impl Step for CodegenGCC {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.paths(&["compiler/rustc_codegen_gcc"])
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
let host = run.build_triple();
let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, host);
if builder.doc_tests == DocTests::Only {
return;
}
if builder.download_rustc() {
builder.info("CI rustc uses the default codegen backend. skipping");
return;
}
let triple = run.target.triple;
let target_supported =
if triple.contains("linux") { triple.contains("x86_64") } else { false };
if !target_supported {
builder.info("target not supported by rustc_codegen_gcc. skipping");
return;
}
if builder.remote_tested(run.target) {
builder.info("remote testing is not supported by rustc_codegen_gcc. skipping");
return;
}
if !builder.config.enabled_codegen_backends(run.target).contains(&CodegenBackendKind::Gcc) {
builder.info("gcc not in rust.codegen-backends. skipping");
return;
}
builder.ensure(CodegenGCC { compilers, target: run.target });
}
fn run(self, builder: &Builder<'_>) {
let compilers = self.compilers;
let target = self.target;
let gcc = builder.ensure(Gcc { target_pair: GccTargetPair::for_native_build(target) });
builder.ensure(
compile::Std::new(compilers.build_compiler(), target)
.extra_rust_args(&["-Csymbol-mangling-version=v0", "-Cpanic=abort"]),
);
let _guard = builder.msg_test(
"rustc_codegen_gcc",
compilers.target(),
compilers.target_compiler().stage,
);
let mut cargo = builder::Cargo::new(
builder,
compilers.build_compiler(),
Mode::Codegen, // Must be codegen to ensure dlopen on compiled dylibs works
SourceType::InTree,
target,
Kind::Run,
);
cargo.current_dir(&builder.src.join("compiler/rustc_codegen_gcc"));
cargo
.arg("--manifest-path")
.arg(builder.src.join("compiler/rustc_codegen_gcc/build_system/Cargo.toml"));
compile::rustc_cargo_env(builder, &mut cargo, target);
add_cg_gcc_cargo_flags(&mut cargo, &gcc);
// Avoid incremental cache issues when changing rustc
cargo.env("CARGO_BUILD_INCREMENTAL", "false");
cargo.rustflag("-Cpanic=abort");
cargo
// cg_gcc's build system ignores RUSTFLAGS. pass some flags through CG_RUSTFLAGS instead.
.env("CG_RUSTFLAGS", "-Alinker-messages")
.arg("--")
.arg("test")
.arg("--use-backend")
.arg("gcc")
.arg("--gcc-path")
.arg(gcc.libgccjit().parent().unwrap())
.arg("--out-dir")
.arg(builder.stage_out(compilers.build_compiler(), Mode::Codegen).join("cg_gcc"))
.arg("--release")
.arg("--mini-tests")
.arg("--std-tests");
cargo.args(builder.config.test_args());
cargo.into_cmd().run(builder);
}
fn metadata(&self) -> Option<StepMetadata> {
Some(
StepMetadata::test("rustc_codegen_gcc", self.target)
.built_by(self.compilers.build_compiler()),
)
}
}
/// Test step that does two things:
/// - Runs `cargo test` for the `src/tools/test-float-parse` tool.
/// - Invokes the `test-float-parse` tool to test the standard library's
/// float parsing routines.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TestFloatParse {
/// The build compiler which will build and run unit tests of `test-float-parse`, and which will
/// build the `test-float-parse` tool itself.
///
/// Note that the staging is a bit funny here, because this step essentially tests std, but it
/// also needs to build the tool. So if we test stage1 std, we build:
/// 1) stage1 rustc
/// 2) Use that to build stage1 libstd
/// 3) Use that to build and run *stage2* test-float-parse
build_compiler: Compiler,
/// Target for which we build std and test that std.
target: TargetSelection,
}
impl Step for TestFloatParse {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/test-float-parse")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Self {
build_compiler: get_compiler_to_test(run.builder, run.target),
target: run.target,
});
}
fn run(self, builder: &Builder<'_>) {
let build_compiler = self.build_compiler;
let target = self.target;
// Build the standard library that will be tested, and a stdlib for host code
builder.std(build_compiler, target);
builder.std(build_compiler, builder.host_target);
// Run any unit tests in the crate
let mut cargo_test = tool::prepare_tool_cargo(
builder,
build_compiler,
Mode::ToolStd,
target,
Kind::Test,
"src/tools/test-float-parse",
SourceType::InTree,
&[],
);
cargo_test.allow_features(TEST_FLOAT_PARSE_ALLOW_FEATURES);
run_cargo_test(cargo_test, &[], &[], "test-float-parse", target, builder);
// Run the actual parse tests.
let mut cargo_run = tool::prepare_tool_cargo(
builder,
build_compiler,
Mode::ToolStd,
target,
Kind::Run,
"src/tools/test-float-parse",
SourceType::InTree,
&[],
);
cargo_run.allow_features(TEST_FLOAT_PARSE_ALLOW_FEATURES);
if !matches!(env::var("FLOAT_PARSE_TESTS_NO_SKIP_HUGE").as_deref(), Ok("1") | Ok("true")) {
cargo_run.args(["--", "--skip-huge"]);
}
cargo_run.into_cmd().run(builder);
}
}
/// Runs the tool `src/tools/collect-license-metadata` in `ONLY_CHECK=1` mode,
/// which verifies that `license-metadata.json` is up-to-date and therefore
/// running the tool normally would not update anything.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct CollectLicenseMetadata;
impl Step for CollectLicenseMetadata {
type Output = PathBuf;
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/collect-license-metadata")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(CollectLicenseMetadata);
}
fn run(self, builder: &Builder<'_>) -> Self::Output {
let Some(reuse) = &builder.config.reuse else {
panic!("REUSE is required to collect the license metadata");
};
let dest = builder.src.join("license-metadata.json");
let mut cmd = builder.tool_cmd(Tool::CollectLicenseMetadata);
cmd.env("REUSE_EXE", reuse);
cmd.env("DEST", &dest);
cmd.env("ONLY_CHECK", "1");
cmd.run(builder);
dest
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RemoteTestClientTests {
host: TargetSelection,
}
impl Step for RemoteTestClientTests {
type Output = ();
const IS_HOST: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/remote-test-client")
}
fn is_default_step(_builder: &Builder<'_>) -> bool {
true
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Self { host: run.target });
}
fn run(self, builder: &Builder<'_>) {
let bootstrap_host = builder.config.host_target;
let compiler = builder.compiler(0, bootstrap_host);
let cargo = tool::prepare_tool_cargo(
builder,
compiler,
Mode::ToolBootstrap,
bootstrap_host,
Kind::Test,
"src/tools/remote-test-client",
SourceType::InTree,
&[],
);
run_cargo_test(cargo, &[], &[], "remote-test-client", bootstrap_host, builder);
}
}