diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs index d2e0a8e93681c..78ec2579c4aa7 100644 --- a/src/bootstrap/check.rs +++ b/src/bootstrap/check.rs @@ -23,7 +23,7 @@ use std::path::{PathBuf, Path}; use std::process::Command; use std::io::Read; -use build_helper::{self, output}; +use build_helper::{self, output, BuildExpectation}; use builder::{Kind, RunConfig, ShouldRun, Builder, Compiler, Step}; use cache::{INTERNER, Interned}; @@ -33,6 +33,7 @@ use native; use tool::{self, Tool}; use util::{self, dylib_path, dylib_path_var}; use {Build, Mode}; +use toolstate::ToolState; const ADB_TEST_DIR: &str = "/data/tmp/work"; @@ -64,17 +65,21 @@ impl fmt::Display for TestKind { } } -fn try_run(build: &Build, cmd: &mut Command) { +fn try_run_expecting(build: &Build, cmd: &mut Command, expect: BuildExpectation) { if !build.fail_fast { - if !build.try_run(cmd) { + if !build.try_run(cmd, expect) { let failures = build.delayed_failures.get(); build.delayed_failures.set(failures + 1); } } else { - build.run(cmd); + build.run_expecting(cmd, expect); } } +fn try_run(build: &Build, cmd: &mut Command) { + try_run_expecting(build, cmd, BuildExpectation::None) +} + fn try_run_quiet(build: &Build, cmd: &mut Command) { if !build.fail_fast { if !build.try_run_quiet(cmd) { @@ -333,7 +338,11 @@ impl Step for Miri { builder.add_rustc_lib_path(compiler, &mut cargo); - try_run(build, &mut cargo); + try_run_expecting( + build, + &mut cargo, + builder.build.config.toolstate.miri.passes(ToolState::Testing), + ); } } diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index 349482eab5f87..c8b2ed042c119 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -27,6 +27,7 @@ use util::exe; use cache::{INTERNER, Interned}; use flags::Flags; pub use flags::Subcommand; +use toolstate::ToolStates; /// Global configuration for the entire build and/or bootstrap. /// @@ -131,6 +132,8 @@ pub struct Config { // These are either the stage0 downloaded binaries or the locally installed ones. pub initial_cargo: PathBuf, pub initial_rustc: PathBuf, + + pub toolstate: ToolStates, } /// Per-target configuration stored in the global configuration structure. @@ -333,6 +336,18 @@ impl Config { } }).unwrap_or_else(|| TomlConfig::default()); + let toolstate_toml_path = config.src.join("src/tools/toolstate.toml"); + let parse_toolstate = || -> Result<_, Box<::std::error::Error>> { + let mut f = File::open(toolstate_toml_path)?; + let mut contents = String::new(); + f.read_to_string(&mut contents)?; + Ok(toml::from_str(&contents)?) + }; + config.toolstate = parse_toolstate().unwrap_or_else(|err| { + println!("failed to parse TOML configuration 'toolstate.toml': {}", err); + process::exit(2); + }); + let build = toml.build.clone().unwrap_or(Build::default()); set(&mut config.build, build.build.clone().map(|x| INTERNER.intern_string(x))); set(&mut config.build, flags.build); diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index 67791e8758c0b..6bca17c8ba860 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -143,7 +143,8 @@ use std::path::{PathBuf, Path}; use std::process::Command; use std::slice; -use build_helper::{run_silent, run_suppressed, try_run_silent, try_run_suppressed, output, mtime}; +use build_helper::{run_silent, run_suppressed, try_run_silent, try_run_suppressed, output, mtime, + BuildExpectation}; use util::{exe, libdir, OutputFolder, CiEnv}; @@ -164,6 +165,7 @@ pub mod util; mod builder; mod cache; mod tool; +mod toolstate; #[cfg(windows)] mod job; @@ -542,24 +544,31 @@ impl Build { .join(libdir(&self.config.build)) } + /// Runs a command, printing out nice contextual information if its build + /// status is not the expected one + fn run_expecting(&self, cmd: &mut Command, expect: BuildExpectation) { + self.verbose(&format!("running: {:?}", cmd)); + run_silent(cmd, expect) + } + /// Runs a command, printing out nice contextual information if it fails. fn run(&self, cmd: &mut Command) { - self.verbose(&format!("running: {:?}", cmd)); - run_silent(cmd) + self.run_expecting(cmd, BuildExpectation::None) } /// Runs a command, printing out nice contextual information if it fails. fn run_quiet(&self, cmd: &mut Command) { self.verbose(&format!("running: {:?}", cmd)); - run_suppressed(cmd) + run_suppressed(cmd, BuildExpectation::None) } - /// Runs a command, printing out nice contextual information if it fails. - /// Exits if the command failed to execute at all, otherwise returns its - /// `status.success()`. - fn try_run(&self, cmd: &mut Command) -> bool { + /// Runs a command, printing out nice contextual information if its build + /// status is not the expected one. + /// Exits if the command failed to execute at all, otherwise returns whether + /// the expectation was met + fn try_run(&self, cmd: &mut Command, expect: BuildExpectation) -> bool { self.verbose(&format!("running: {:?}", cmd)); - try_run_silent(cmd) + try_run_silent(cmd, expect) } /// Runs a command, printing out nice contextual information if it fails. @@ -567,7 +576,7 @@ impl Build { /// `status.success()`. fn try_run_quiet(&self, cmd: &mut Command) -> bool { self.verbose(&format!("running: {:?}", cmd)); - try_run_suppressed(cmd) + try_run_suppressed(cmd, BuildExpectation::None) } pub fn is_verbose(&self) -> bool { diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 541247804857b..4e422473b9ad4 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -21,6 +21,8 @@ use compile::{self, libtest_stamp, libstd_stamp, librustc_stamp}; use native; use channel::GitInfo; use cache::Interned; +use toolstate::ToolState; +use build_helper::BuildExpectation; #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct CleanTools { @@ -64,6 +66,7 @@ struct ToolBuild { tool: &'static str, path: &'static str, mode: Mode, + expectation: BuildExpectation, } impl Step for ToolBuild { @@ -83,6 +86,7 @@ impl Step for ToolBuild { let target = self.target; let tool = self.tool; let path = self.path; + let expectation = self.expectation; match self.mode { Mode::Libstd => builder.ensure(compile::Std { compiler, target }), @@ -95,7 +99,7 @@ impl Step for ToolBuild { println!("Building stage{} tool {} ({})", compiler.stage, tool, target); let mut cargo = prepare_tool_cargo(builder, compiler, target, "build", path); - build.run(&mut cargo); + build.run_expecting(&mut cargo, expectation); build.cargo_out(compiler, Mode::Tool, target).join(exe(tool, &compiler.host)) } } @@ -200,6 +204,7 @@ macro_rules! tool { tool: $tool_name, mode: $mode, path: $path, + expectation: BuildExpectation::None, }) } } @@ -247,6 +252,7 @@ impl Step for RemoteTestServer { tool: "remote-test-server", mode: Mode::Libstd, path: "src/tools/remote-test-server", + expectation: BuildExpectation::None, }) } } @@ -359,6 +365,7 @@ impl Step for Cargo { tool: "cargo", mode: Mode::Librustc, path: "src/tools/cargo", + expectation: BuildExpectation::None, }) } } @@ -398,6 +405,7 @@ impl Step for Clippy { tool: "clippy", mode: Mode::Librustc, path: "src/tools/clippy", + expectation: BuildExpectation::None, }) } } @@ -441,6 +449,7 @@ impl Step for Rls { tool: "rls", mode: Mode::Librustc, path: "src/tools/rls", + expectation: BuildExpectation::None, }) } } @@ -492,8 +501,8 @@ impl Step for Miri { const ONLY_HOSTS: bool = true; fn should_run(run: ShouldRun) -> ShouldRun { - let builder = run.builder; - run.path("src/tools/miri").default_condition(builder.build.config.test_miri) + let build_miri = run.builder.build.config.test_miri; + run.path("src/tools/miri").default_condition(build_miri) } fn make_run(run: RunConfig) { @@ -510,6 +519,7 @@ impl Step for Miri { tool: "miri", mode: Mode::Librustc, path: "src/tools/miri", + expectation: builder.build.config.toolstate.miri.passes(ToolState::Compiling), }) } } diff --git a/src/bootstrap/toolstate.rs b/src/bootstrap/toolstate.rs new file mode 100644 index 0000000000000..9556a8b52df67 --- /dev/null +++ b/src/bootstrap/toolstate.rs @@ -0,0 +1,48 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use build_helper::BuildExpectation; + +#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] +/// Whether a tool can be compiled, tested or neither +pub enum ToolState { + /// The tool compiles successfully, but the test suite fails + Compiling = 1, + /// The tool compiles successfully and its test suite passes + Testing = 2, + /// The tool can't even be compiled + Broken = 0, +} + +impl ToolState { + /// If a tool with the current toolstate should be working on + /// the given toolstate + pub fn passes(self, other: ToolState) -> BuildExpectation { + if self as usize >= other as usize { + BuildExpectation::Succeeding + } else { + BuildExpectation::Failing + } + } +} + +impl Default for ToolState { + fn default() -> Self { + // err on the safe side + ToolState::Broken + } +} + +#[derive(Copy, Clone, Debug, Deserialize, Default)] +/// Used to express which tools should (not) be compiled or tested. +/// This is created from `toolstate.toml`. +pub struct ToolStates { + pub miri: ToolState, +} diff --git a/src/build_helper/lib.rs b/src/build_helper/lib.rs index 8b4c7f2ac3177..e81dab70b43e7 100644 --- a/src/build_helper/lib.rs +++ b/src/build_helper/lib.rs @@ -35,55 +35,97 @@ macro_rules! t { }) } -pub fn run(cmd: &mut Command) { +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum BuildExpectation { + Succeeding, + Failing, + None, +} + +pub fn run(cmd: &mut Command, expect: BuildExpectation) { println!("running: {:?}", cmd); - run_silent(cmd); + run_silent(cmd, expect); } -pub fn run_silent(cmd: &mut Command) { - if !try_run_silent(cmd) { +pub fn run_silent(cmd: &mut Command, expect: BuildExpectation) { + if !try_run_silent(cmd, expect) { std::process::exit(1); } } -pub fn try_run_silent(cmd: &mut Command) -> bool { +pub fn try_run_silent(cmd: &mut Command, expect: BuildExpectation) -> bool { let status = match cmd.status() { Ok(status) => status, Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)), }; - if !status.success() { - println!("\n\ncommand did not execute successfully: {:?}\n\ - expected success, got: {}\n\n", - cmd, - status); + process_status( + cmd, + status.success(), + expect, + || println!("\n\ncommand did not execute successfully: {:?}\n\ + expected success, got: {}\n\n", + cmd, + status)) +} + +fn process_status( + cmd: &Command, + success: bool, + expect: BuildExpectation, + f: F, +) -> bool { + use BuildExpectation::*; + match (expect, success) { + (None, false) => { f(); false }, + // Non-tool build succeeds, everything is good + (None, true) => true, + // Tool expected to work and is working + (Succeeding, true) => true, + // Tool expected to fail and is failing + (Failing, false) => { + println!("This failure is expected (see `src/tools/toolstate.toml`)"); + true + }, + // Tool expected to work, but is failing + (Succeeding, false) => { + f(); + println!("You can disable the tool in `src/tools/toolstate.toml`"); + false + }, + // Tool expected to fail, but is working + (Failing, true) => { + println!("Expected `{:?}` to fail, but it succeeded.\n\ + Please adjust `src/tools/toolstate.toml` accordingly", cmd); + false + } } - status.success() } -pub fn run_suppressed(cmd: &mut Command) { - if !try_run_suppressed(cmd) { +pub fn run_suppressed(cmd: &mut Command, expect: BuildExpectation) { + if !try_run_suppressed(cmd, expect) { std::process::exit(1); } } -pub fn try_run_suppressed(cmd: &mut Command) -> bool { +pub fn try_run_suppressed(cmd: &mut Command, expect: BuildExpectation) -> bool { let output = match cmd.output() { Ok(status) => status, Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)), }; - if !output.status.success() { - println!("\n\ncommand did not execute successfully: {:?}\n\ + process_status( + cmd, + output.status.success(), + expect, + || println!("\n\ncommand did not execute successfully: {:?}\n\ expected success, got: {}\n\n\ stdout ----\n{}\n\ stderr ----\n{}\n\n", cmd, output.status, String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr)); - } - output.status.success() + String::from_utf8_lossy(&output.stderr))) } pub fn gnu_target(target: &str) -> String { diff --git a/src/liballoc_jemalloc/build.rs b/src/liballoc_jemalloc/build.rs index 1864df4477abf..d89d3bcdb62a5 100644 --- a/src/liballoc_jemalloc/build.rs +++ b/src/liballoc_jemalloc/build.rs @@ -16,7 +16,7 @@ extern crate gcc; use std::env; use std::path::PathBuf; use std::process::Command; -use build_helper::{run, native_lib_boilerplate}; +use build_helper::{run, native_lib_boilerplate, BuildExpectation}; fn main() { // FIXME: This is a hack to support building targets that don't @@ -126,7 +126,7 @@ fn main() { cmd.arg("--with-lg-quantum=4"); } - run(&mut cmd); + run(&mut cmd, BuildExpectation::None); let mut make = Command::new(build_helper::make(&host)); make.current_dir(&native.out_dir) @@ -143,7 +143,7 @@ fn main() { .arg(env::var("NUM_JOBS").expect("NUM_JOBS was not set")); } - run(&mut make); + run(&mut make, BuildExpectation::None); // The pthread_atfork symbols is used by jemalloc on android but the really // old android we're building on doesn't have them defined, so just make diff --git a/src/libstd/build.rs b/src/libstd/build.rs index ebf07eb0423a0..19ea25fc7df07 100644 --- a/src/libstd/build.rs +++ b/src/libstd/build.rs @@ -15,7 +15,7 @@ extern crate gcc; use std::env; use std::process::Command; -use build_helper::{run, native_lib_boilerplate}; +use build_helper::{run, native_lib_boilerplate, BuildExpectation}; fn main() { let target = env::var("TARGET").expect("TARGET was not set"); @@ -97,11 +97,14 @@ fn build_libbacktrace(host: &str, target: &str) -> Result<(), ()> { .env("CC", compiler.path()) .env("AR", &ar) .env("RANLIB", format!("{} s", ar.display())) - .env("CFLAGS", cflags)); + .env("CFLAGS", cflags), + BuildExpectation::None); run(Command::new(build_helper::make(host)) .current_dir(&native.out_dir) .arg(format!("INCDIR={}", native.src_dir.display())) - .arg("-j").arg(env::var("NUM_JOBS").expect("NUM_JOBS was not set"))); + .arg("-j").arg(env::var("NUM_JOBS").expect("NUM_JOBS was not set")), + BuildExpectation::None); + Ok(()) } diff --git a/src/tools/toolstate.toml b/src/tools/toolstate.toml new file mode 100644 index 0000000000000..697be4efadbc8 --- /dev/null +++ b/src/tools/toolstate.toml @@ -0,0 +1,24 @@ +# This file reflects the current status of all tools which are allowed +# to fail without failing the build. +# +# There are three states a tool can be in: +# 1. Broken: The tool doesn't build +# 2. Building: The tool builds but its tests are failing +# 3. Testing: The tool builds and its tests are passing +# +# In the future there will be further states like "Distributing", which +# configures whether the tool is included in the Rust distribution. +# +# If a tool was working before your PR but is broken now, consider +# updating the tool within your PR. How to do that is described in +# "CONTRIBUTING.md#External Dependencies". If the effort required is not +# warranted (e.g. due to the tool abusing some API that you changed, and +# fixing the tool would mean a significant refactoring), you can disable +# the tool here, by changing its state to `Broken`. Remember to ping +# the tool authors if you do not fix their tool, so they can proactively +# fix it, instead of being surprised by the breakage. +# +# Each tool has a list of people to ping + +# ping @oli-obk @RalfJung @eddyb +miri = "Testing"