about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2017-08-13 05:24:49 +0000
committerbors <bors@rust-lang.org>2017-08-13 05:24:49 +0000
commitadbce60d6f131e5b3789f01417dedb05e4489898 (patch)
treec35452a47f65da02696beb0bbac0348abf92f228
parent0ed03e5490b481804db27627e16e147680ed207d (diff)
parent01641c70331d704fdab05914f21921e453ae61bb (diff)
downloadrust-adbce60d6f131e5b3789f01417dedb05e4489898.tar.gz
rust-adbce60d6f131e5b3789f01417dedb05e4489898.zip
Auto merge of #43630 - Mark-Simulacrum:rustbuild-cleanups, r=alexcrichton
Rustbuild cleanups/fixes and improvements

Each commit is a standalone change, and can/should be reviewed separately.

This adds two new functionalities:

 - `--target` and `--host` can be passed without changing config.toml, and we'll respect the users' wishes, instead of requiring that all possible targets are passed.
   - Note that this means that `./x.py clean` won't be quite as wide-spread as before, since it limits itself to the configured hosts, not all hosts. This could be considered a feature as well.
 - `ignore-git` field in `config.toml` which tells Rustbuild to not attempt to load git hashes from `.git`.

This is a precursor to eventual further simplification of the configuration system, but I want to get this merged first so that later work can be made in individual PRs.

r? @alexcrichton
-rw-r--r--config.toml.example3
-rw-r--r--src/bootstrap/bin/main.rs7
-rw-r--r--src/bootstrap/builder.rs48
-rw-r--r--src/bootstrap/cc.rs31
-rw-r--r--src/bootstrap/channel.rs5
-rw-r--r--src/bootstrap/check.rs25
-rw-r--r--src/bootstrap/clean.rs2
-rw-r--r--src/bootstrap/compile.rs21
-rw-r--r--src/bootstrap/config.rs71
-rw-r--r--src/bootstrap/dist.rs5
-rw-r--r--src/bootstrap/doc.rs12
-rw-r--r--src/bootstrap/flags.rs21
-rw-r--r--src/bootstrap/install.rs2
-rw-r--r--src/bootstrap/lib.rs61
-rw-r--r--src/bootstrap/sanity.rs8
-rw-r--r--src/bootstrap/tool.rs101
-rw-r--r--src/tools/rustdoc/Cargo.toml5
17 files changed, 239 insertions, 189 deletions
diff --git a/config.toml.example b/config.toml.example
index 19678dc7793..962be2e6085 100644
--- a/config.toml.example
+++ b/config.toml.example
@@ -258,6 +258,9 @@
 # saying that the FileCheck executable is missing, you may want to disable this.
 #codegen-tests = true
 
+# Flag indicating whether git info will be retrieved from .git automatically.
+#ignore-git = false
+
 # =============================================================================
 # Options for specific targets
 #
diff --git a/src/bootstrap/bin/main.rs b/src/bootstrap/bin/main.rs
index 5ef18b89841..d02bc7972ae 100644
--- a/src/bootstrap/bin/main.rs
+++ b/src/bootstrap/bin/main.rs
@@ -21,11 +21,10 @@ extern crate bootstrap;
 
 use std::env;
 
-use bootstrap::{Flags, Config, Build};
+use bootstrap::{Config, Build};
 
 fn main() {
     let args = env::args().skip(1).collect::<Vec<_>>();
-    let flags = Flags::parse(&args);
-    let config = Config::parse(&flags.build, flags.config.clone());
-    Build::new(flags, config).build();
+    let config = Config::parse(&args);
+    Build::new(config).build();
 }
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index d7f795e4055..db2c6dfeb9f 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -120,28 +120,19 @@ impl StepDescription {
     fn maybe_run(&self, builder: &Builder, path: Option<&Path>) {
         let build = builder.build;
         let hosts = if self.only_build_targets || self.only_build {
-            &build.config.host[..1]
+            build.build_triple()
         } else {
             &build.hosts
         };
 
-        // Determine the actual targets participating in this rule.
-        // NOTE: We should keep the full projection from build triple to
-        // the hosts for the dist steps, now that the hosts array above is
-        // truncated to avoid duplication of work in that case. Therefore
-        // the original non-shadowed hosts array is used below.
+        // Determine the targets participating in this rule.
         let targets = if self.only_hosts {
-            // If --target was specified but --host wasn't specified,
-            // don't run any host-only tests. Also, respect any `--host`
-            // overrides as done for `hosts`.
-            if build.flags.host.len() > 0 {
-                &build.flags.host[..]
-            } else if build.flags.target.len() > 0 {
+            if build.config.run_host_only {
                 &[]
             } else if self.only_build {
-                &build.config.host[..1]
+                build.build_triple()
             } else {
-                &build.config.host[..]
+                &build.hosts
             }
         } else {
             &build.targets
@@ -288,7 +279,7 @@ impl<'a> Builder<'a> {
 
         let builder = Builder {
             build: build,
-            top_stage: build.flags.stage.unwrap_or(2),
+            top_stage: build.config.stage.unwrap_or(2),
             kind: kind,
             cache: Cache::new(),
             stack: RefCell::new(Vec::new()),
@@ -307,7 +298,7 @@ impl<'a> Builder<'a> {
     }
 
     pub fn run(build: &Build) {
-        let (kind, paths) = match build.flags.cmd {
+        let (kind, paths) = match build.config.cmd {
             Subcommand::Build { ref paths } => (Kind::Build, &paths[..]),
             Subcommand::Doc { ref paths } => (Kind::Doc, &paths[..]),
             Subcommand::Test { ref paths, .. } => (Kind::Test, &paths[..]),
@@ -319,7 +310,7 @@ impl<'a> Builder<'a> {
 
         let builder = Builder {
             build: build,
-            top_stage: build.flags.stage.unwrap_or(2),
+            top_stage: build.config.stage.unwrap_or(2),
             kind: kind,
             cache: Cache::new(),
             stack: RefCell::new(Vec::new()),
@@ -414,22 +405,19 @@ impl<'a> Builder<'a> {
         }
     }
 
-    pub fn rustdoc(&self, compiler: Compiler) -> PathBuf {
-        self.ensure(tool::Rustdoc { target_compiler: compiler })
+    pub fn rustdoc(&self, host: Interned<String>) -> PathBuf {
+        self.ensure(tool::Rustdoc { host })
     }
 
-    pub fn rustdoc_cmd(&self, compiler: Compiler) -> Command {
+    pub fn rustdoc_cmd(&self, host: Interned<String>) -> Command {
         let mut cmd = Command::new(&self.out.join("bootstrap/debug/rustdoc"));
+        let compiler = self.compiler(self.top_stage, host);
         cmd
             .env("RUSTC_STAGE", compiler.stage.to_string())
-            .env("RUSTC_SYSROOT", if compiler.is_snapshot(&self.build) {
-                INTERNER.intern_path(self.build.rustc_snapshot_libdir())
-            } else {
-                self.sysroot(compiler)
-            })
-            .env("RUSTC_LIBDIR", self.rustc_libdir(compiler))
+            .env("RUSTC_SYSROOT", self.sysroot(compiler))
+            .env("RUSTC_LIBDIR", self.sysroot_libdir(compiler, self.build.build))
             .env("CFG_RELEASE_CHANNEL", &self.build.config.channel)
-            .env("RUSTDOC_REAL", self.rustdoc(compiler));
+            .env("RUSTDOC_REAL", self.rustdoc(host));
         cmd
     }
 
@@ -483,7 +471,7 @@ impl<'a> Builder<'a> {
              .env("RUSTC_RPATH", self.config.rust_rpath.to_string())
              .env("RUSTDOC", self.out.join("bootstrap/debug/rustdoc"))
              .env("RUSTDOC_REAL", if cmd == "doc" || cmd == "test" {
-                 self.rustdoc(compiler)
+                 self.rustdoc(compiler.host)
              } else {
                  PathBuf::from("/path/to/nowhere/rustdoc/not/required")
              })
@@ -543,12 +531,12 @@ impl<'a> Builder<'a> {
         // Ignore incremental modes except for stage0, since we're
         // not guaranteeing correctness across builds if the compiler
         // is changing under your feet.`
-        if self.flags.incremental && compiler.stage == 0 {
+        if self.config.incremental && compiler.stage == 0 {
             let incr_dir = self.incremental_dir(compiler);
             cargo.env("RUSTC_INCREMENTAL", incr_dir);
         }
 
-        if let Some(ref on_fail) = self.flags.on_fail {
+        if let Some(ref on_fail) = self.config.on_fail {
             cargo.env("RUSTC_ON_FAIL", on_fail);
         }
 
diff --git a/src/bootstrap/cc.rs b/src/bootstrap/cc.rs
index 739904e4f7c..0f25da8a238 100644
--- a/src/bootstrap/cc.rs
+++ b/src/bootstrap/cc.rs
@@ -32,6 +32,7 @@
 //! everything.
 
 use std::process::Command;
+use std::iter;
 
 use build_helper::{cc2ar, output};
 use gcc;
@@ -43,47 +44,41 @@ use cache::Interned;
 pub fn find(build: &mut Build) {
     // For all targets we're going to need a C compiler for building some shims
     // and such as well as for being a linker for Rust code.
-    //
-    // This includes targets that aren't necessarily passed on the commandline
-    // (FIXME: Perhaps it shouldn't?)
-    for target in &build.config.target {
+    for target in build.targets.iter().chain(&build.hosts).cloned().chain(iter::once(build.build)) {
         let mut cfg = gcc::Config::new();
         cfg.cargo_metadata(false).opt_level(0).debug(false)
-           .target(target).host(&build.build);
+           .target(&target).host(&build.build);
 
         let config = build.config.target_config.get(&target);
         if let Some(cc) = config.and_then(|c| c.cc.as_ref()) {
             cfg.compiler(cc);
         } else {
-            set_compiler(&mut cfg, "gcc", *target, config, build);
+            set_compiler(&mut cfg, "gcc", target, config, build);
         }
 
         let compiler = cfg.get_compiler();
-        let ar = cc2ar(compiler.path(), target);
-        build.verbose(&format!("CC_{} = {:?}", target, compiler.path()));
+        let ar = cc2ar(compiler.path(), &target);
+        build.verbose(&format!("CC_{} = {:?}", &target, compiler.path()));
         if let Some(ref ar) = ar {
-            build.verbose(&format!("AR_{} = {:?}", target, ar));
+            build.verbose(&format!("AR_{} = {:?}", &target, ar));
         }
-        build.cc.insert(*target, (compiler, ar));
+        build.cc.insert(target, (compiler, ar));
     }
 
     // For all host triples we need to find a C++ compiler as well
-    //
-    // This includes hosts that aren't necessarily passed on the commandline
-    // (FIXME: Perhaps it shouldn't?)
-    for host in &build.config.host {
+    for host in build.hosts.iter().cloned().chain(iter::once(build.build)) {
         let mut cfg = gcc::Config::new();
         cfg.cargo_metadata(false).opt_level(0).debug(false).cpp(true)
-           .target(host).host(&build.build);
-        let config = build.config.target_config.get(host);
+           .target(&host).host(&build.build);
+        let config = build.config.target_config.get(&host);
         if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) {
             cfg.compiler(cxx);
         } else {
-            set_compiler(&mut cfg, "g++", *host, config, build);
+            set_compiler(&mut cfg, "g++", host, config, build);
         }
         let compiler = cfg.get_compiler();
         build.verbose(&format!("CXX_{} = {:?}", host, compiler.path()));
-        build.cxx.insert(*host, compiler);
+        build.cxx.insert(host, compiler);
     }
 }
 
diff --git a/src/bootstrap/channel.rs b/src/bootstrap/channel.rs
index beefaeab90b..9c1ae83d382 100644
--- a/src/bootstrap/channel.rs
+++ b/src/bootstrap/channel.rs
@@ -21,6 +21,7 @@ use std::process::Command;
 use build_helper::output;
 
 use Build;
+use config::Config;
 
 // The version number
 pub const CFG_RELEASE_NUM: &str = "1.21.0";
@@ -41,9 +42,9 @@ struct Info {
 }
 
 impl GitInfo {
-    pub fn new(dir: &Path) -> GitInfo {
+    pub fn new(config: &Config, dir: &Path) -> GitInfo {
         // See if this even begins to look like a git dir
-        if !dir.join(".git").exists() {
+        if config.ignore_git || !dir.join(".git").exists() {
             return GitInfo { inner: None }
         }
 
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index c65f5a9fb48..d4d6fdc5c1b 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -164,7 +164,7 @@ impl Step for Cargotest {
         try_run(build, cmd.arg(&build.initial_cargo)
                           .arg(&out_dir)
                           .env("RUSTC", builder.rustc(compiler))
-                          .env("RUSTDOC", builder.rustdoc(compiler)));
+                          .env("RUSTDOC", builder.rustdoc(compiler.host)));
     }
 }
 
@@ -565,7 +565,7 @@ impl Step for Compiletest {
 
         // Avoid depending on rustdoc when we don't need it.
         if mode == "rustdoc" || mode == "run-make" {
-            cmd.arg("--rustdoc-path").arg(builder.rustdoc(compiler));
+            cmd.arg("--rustdoc-path").arg(builder.rustdoc(compiler.host));
         }
 
         cmd.arg("--src-base").arg(build.src.join("src/test").join(suite));
@@ -625,7 +625,7 @@ impl Step for Compiletest {
             cmd.arg("--system-llvm");
         }
 
-        cmd.args(&build.flags.cmd.test_args());
+        cmd.args(&build.config.cmd.test_args());
 
         if build.is_verbose() {
             cmd.arg("--verbose");
@@ -814,13 +814,13 @@ fn markdown_test(builder: &Builder, compiler: Compiler, markdown: &Path) {
     }
 
     println!("doc tests for: {}", markdown.display());
-    let mut cmd = builder.rustdoc_cmd(compiler);
+    let mut cmd = builder.rustdoc_cmd(compiler.host);
     build.add_rust_test_threads(&mut cmd);
     cmd.arg("--test");
     cmd.arg(markdown);
     cmd.env("RUSTC_BOOTSTRAP", "1");
 
-    let test_args = build.flags.cmd.test_args().join(" ");
+    let test_args = build.config.cmd.test_args().join(" ");
     cmd.arg("--test-args").arg(test_args);
 
     if build.config.quiet_tests {
@@ -1051,7 +1051,7 @@ impl Step for Crate {
         cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
 
         cargo.arg("--");
-        cargo.args(&build.flags.cmd.test_args());
+        cargo.args(&build.config.cmd.test_args());
 
         if build.config.quiet_tests {
             cargo.arg("--quiet");
@@ -1147,6 +1147,7 @@ pub struct Distcheck;
 
 impl Step for Distcheck {
     type Output = ();
+    const ONLY_BUILD: bool = true;
 
     fn should_run(run: ShouldRun) -> ShouldRun {
         run.path("distcheck")
@@ -1160,16 +1161,6 @@ impl Step for Distcheck {
     fn run(self, builder: &Builder) {
         let build = builder.build;
 
-        if *build.build != *"x86_64-unknown-linux-gnu" {
-            return
-        }
-        if !build.config.host.iter().any(|s| s == "x86_64-unknown-linux-gnu") {
-            return
-        }
-        if !build.config.target.iter().any(|s| s == "x86_64-unknown-linux-gnu") {
-            return
-        }
-
         println!("Distcheck");
         let dir = build.out.join("tmp").join("distcheck");
         let _ = fs::remove_dir_all(&dir);
@@ -1236,7 +1227,7 @@ impl Step for Bootstrap {
         if !build.fail_fast {
             cmd.arg("--no-fail-fast");
         }
-        cmd.arg("--").args(&build.flags.cmd.test_args());
+        cmd.arg("--").args(&build.config.cmd.test_args());
         try_run(build, &mut cmd);
     }
 
diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs
index 308a0ab3076..119340a0190 100644
--- a/src/bootstrap/clean.rs
+++ b/src/bootstrap/clean.rs
@@ -26,7 +26,7 @@ pub fn clean(build: &Build) {
     rm_rf(&build.out.join("tmp"));
     rm_rf(&build.out.join("dist"));
 
-    for host in build.config.host.iter() {
+    for host in &build.hosts {
         let entries = match build.out.join(host).read_dir() {
             Ok(iter) => iter,
             Err(_) => continue,
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index 92a42b59212..33c3638a894 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -32,6 +32,7 @@ use serde_json;
 use util::{exe, libdir, is_dylib, copy};
 use {Build, Compiler, Mode};
 use native;
+use tool;
 
 use cache::{INTERNER, Interned};
 use builder::{Step, RunConfig, ShouldRun, Builder};
@@ -198,6 +199,12 @@ impl Step for StdLink {
             // for reason why the sanitizers are not built in stage0.
             copy_apple_sanitizer_dylibs(&build.native_dir(target), "osx", &libdir);
         }
+
+        builder.ensure(tool::CleanTools {
+            compiler: target_compiler,
+            target: target,
+            mode: Mode::Libstd,
+        });
     }
 }
 
@@ -389,6 +396,11 @@ impl Step for TestLink {
                 target);
         add_to_sysroot(&builder.sysroot_libdir(target_compiler, target),
                     &libtest_stamp(build, compiler, target));
+        builder.ensure(tool::CleanTools {
+            compiler: target_compiler,
+            target: target,
+            mode: Mode::Libtest,
+        });
     }
 }
 
@@ -567,6 +579,11 @@ impl Step for RustcLink {
                  target);
         add_to_sysroot(&builder.sysroot_libdir(target_compiler, target),
                        &librustc_stamp(build, compiler, target));
+        builder.ensure(tool::CleanTools {
+            compiler: target_compiler,
+            target: target,
+            mode: Mode::Librustc,
+        });
     }
 }
 
@@ -679,10 +696,10 @@ impl Step for Assemble {
         // link to these. (FIXME: Is that correct? It seems to be correct most
         // of the time but I think we do link to these for stage2/bin compilers
         // when not performing a full bootstrap).
-        if builder.build.flags.keep_stage.map_or(false, |s| target_compiler.stage <= s) {
+        if builder.build.config.keep_stage.map_or(false, |s| target_compiler.stage <= s) {
             builder.verbose("skipping compilation of compiler due to --keep-stage");
             let compiler = build_compiler;
-            for stage in 0..min(target_compiler.stage, builder.flags.keep_stage.unwrap()) {
+            for stage in 0..min(target_compiler.stage, builder.config.keep_stage.unwrap()) {
                 let target_compiler = builder.compiler(stage, target_compiler.host);
                 let target = target_compiler.host;
                 builder.ensure(StdLink { compiler, target_compiler, target });
diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs
index c4d5d431521..aa688fc66e2 100644
--- a/src/bootstrap/config.rs
+++ b/src/bootstrap/config.rs
@@ -19,11 +19,14 @@ use std::fs::{self, File};
 use std::io::prelude::*;
 use std::path::PathBuf;
 use std::process;
+use std::cmp;
 
 use num_cpus;
 use toml;
 use util::{exe, push_exe_path};
 use cache::{INTERNER, Interned};
+use flags::Flags;
+pub use flags::Subcommand;
 
 /// Global configuration for the entire build and/or bootstrap.
 ///
@@ -51,6 +54,17 @@ pub struct Config {
     pub extended: bool,
     pub sanitizers: bool,
     pub profiler: bool,
+    pub ignore_git: bool,
+
+    pub run_host_only: bool,
+
+    pub on_fail: Option<String>,
+    pub stage: Option<u32>,
+    pub keep_stage: Option<u32>,
+    pub src: PathBuf,
+    pub jobs: Option<u32>,
+    pub cmd: Subcommand,
+    pub incremental: bool,
 
     // llvm codegen options
     pub llvm_enabled: bool,
@@ -79,8 +93,8 @@ pub struct Config {
     pub rust_dist_src: bool,
 
     pub build: Interned<String>,
-    pub host: Vec<Interned<String>>,
-    pub target: Vec<Interned<String>>,
+    pub hosts: Vec<Interned<String>>,
+    pub targets: Vec<Interned<String>>,
     pub local_rebuild: bool,
 
     // dist misc
@@ -249,6 +263,7 @@ struct Rust {
     optimize_tests: Option<bool>,
     debuginfo_tests: Option<bool>,
     codegen_tests: Option<bool>,
+    ignore_git: Option<bool>,
 }
 
 /// TOML representation of how each build target is configured.
@@ -265,7 +280,9 @@ struct TomlTarget {
 }
 
 impl Config {
-    pub fn parse(build: &str, file: Option<PathBuf>) -> Config {
+    pub fn parse(args: &[String]) -> Config {
+        let flags = Flags::parse(&args);
+        let file = flags.config.clone();
         let mut config = Config::default();
         config.llvm_enabled = true;
         config.llvm_optimize = true;
@@ -277,11 +294,22 @@ impl Config {
         config.docs = true;
         config.rust_rpath = true;
         config.rust_codegen_units = 1;
-        config.build = INTERNER.intern_str(build);
         config.channel = "dev".to_string();
         config.codegen_tests = true;
+        config.ignore_git = false;
         config.rust_dist_src = true;
 
+        config.on_fail = flags.on_fail;
+        config.stage = flags.stage;
+        config.src = flags.src;
+        config.jobs = flags.jobs;
+        config.cmd = flags.cmd;
+        config.incremental = flags.incremental;
+        config.keep_stage = flags.keep_stage;
+
+        // If --target was specified but --host wasn't specified, don't run any host-only tests.
+        config.run_host_only = flags.host.is_empty() && !flags.target.is_empty();
+
         let toml = file.map(|file| {
             let mut f = t!(File::open(&file));
             let mut contents = String::new();
@@ -298,20 +326,37 @@ impl Config {
 
         let build = toml.build.clone().unwrap_or(Build::default());
         set(&mut config.build, build.build.clone().map(|x| INTERNER.intern_string(x)));
-        config.host.push(config.build.clone());
+        set(&mut config.build, flags.build);
+        if config.build.is_empty() {
+            // set by bootstrap.py
+            config.build = INTERNER.intern_str(&env::var("BUILD").unwrap());
+        }
+        config.hosts.push(config.build.clone());
         for host in build.host.iter() {
             let host = INTERNER.intern_str(host);
-            if !config.host.contains(&host) {
-                config.host.push(host);
+            if !config.hosts.contains(&host) {
+                config.hosts.push(host);
             }
         }
-        for target in config.host.iter().cloned()
+        for target in config.hosts.iter().cloned()
             .chain(build.target.iter().map(|s| INTERNER.intern_str(s)))
         {
-            if !config.target.contains(&target) {
-                config.target.push(target);
+            if !config.targets.contains(&target) {
+                config.targets.push(target);
             }
         }
+        config.hosts = if !flags.host.is_empty() {
+            flags.host
+        } else {
+            config.hosts
+        };
+        config.targets = if !flags.target.is_empty() {
+            flags.target
+        } else {
+            config.targets
+        };
+
+
         config.nodejs = build.nodejs.map(PathBuf::from);
         config.gdb = build.gdb.map(PathBuf::from);
         config.python = build.python.map(PathBuf::from);
@@ -327,6 +372,7 @@ impl Config {
         set(&mut config.sanitizers, build.sanitizers);
         set(&mut config.profiler, build.profiler);
         set(&mut config.openssl_static, build.openssl_static);
+        config.verbose = cmp::max(config.verbose, flags.verbose);
 
         if let Some(ref install) = toml.install {
             config.prefix = install.prefix.clone().map(PathBuf::from);
@@ -373,6 +419,7 @@ impl Config {
             set(&mut config.use_jemalloc, rust.use_jemalloc);
             set(&mut config.backtrace, rust.backtrace);
             set(&mut config.channel, rust.channel.clone());
+            set(&mut config.ignore_git, rust.ignore_git);
             config.rustc_default_linker = rust.default_linker.clone();
             config.rustc_default_ar = rust.default_ar.clone();
             config.musl_root = rust.musl_root.clone().map(PathBuf::from);
@@ -505,11 +552,11 @@ impl Config {
             match key {
                 "CFG_BUILD" if value.len() > 0 => self.build = INTERNER.intern_str(value),
                 "CFG_HOST" if value.len() > 0 => {
-                    self.host.extend(value.split(" ").map(|s| INTERNER.intern_str(s)));
+                    self.hosts.extend(value.split(" ").map(|s| INTERNER.intern_str(s)));
 
                 }
                 "CFG_TARGET" if value.len() > 0 => {
-                    self.target.extend(value.split(" ").map(|s| INTERNER.intern_str(s)));
+                    self.targets.extend(value.split(" ").map(|s| INTERNER.intern_str(s)));
                 }
                 "CFG_EXPERIMENTAL_TARGETS" if value.len() > 0 => {
                     self.llvm_experimental_targets = Some(value.to_string());
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index c322d75dd5b..bfcfb5f9a37 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -413,8 +413,7 @@ impl Step for Rustc {
             t!(fs::create_dir_all(image.join("bin")));
             cp_r(&src.join("bin"), &image.join("bin"));
 
-            install(&builder.ensure(tool::Rustdoc { target_compiler: compiler }),
-                &image.join("bin"), 0o755);
+            install(&builder.rustdoc(compiler.host), &image.join("bin"), 0o755);
 
             // Copy runtime DLLs needed by the compiler
             if libdir != "bin" {
@@ -546,7 +545,7 @@ impl Step for Std {
         // We want to package up as many target libraries as possible
         // for the `rust-std` package, so if this is a host target we
         // depend on librustc and otherwise we just depend on libtest.
-        if build.config.host.iter().any(|t| t == target) {
+        if build.hosts.iter().any(|t| t == target) {
             builder.ensure(compile::Rustc { compiler, target });
         } else {
             builder.ensure(compile::Test { compiler, target });
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index 1ee578bb62b..f0e0874abed 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -260,7 +260,7 @@ fn invoke_rustdoc(builder: &Builder, compiler: Compiler, target: Interned<String
         t!(t!(File::create(&version_info)).write_all(info.as_bytes()));
     }
 
-    let mut cmd = builder.rustdoc_cmd(compiler);
+    let mut cmd = builder.rustdoc_cmd(compiler.host);
 
     let out = out.join("book");
 
@@ -343,7 +343,7 @@ impl Step for Standalone {
             }
 
             let html = out.join(filename).with_extension("html");
-            let rustdoc = builder.rustdoc(compiler);
+            let rustdoc = builder.rustdoc(compiler.host);
             if up_to_date(&path, &html) &&
                up_to_date(&footer, &html) &&
                up_to_date(&favicon, &html) &&
@@ -353,7 +353,7 @@ impl Step for Standalone {
                 continue
             }
 
-            let mut cmd = builder.rustdoc_cmd(compiler);
+            let mut cmd = builder.rustdoc_cmd(compiler.host);
             cmd.arg("--html-after-content").arg(&footer)
                .arg("--html-before-content").arg(&version_info)
                .arg("--html-in-header").arg(&favicon)
@@ -408,7 +408,7 @@ impl Step for Std {
         let out = build.doc_out(target);
         t!(fs::create_dir_all(&out));
         let compiler = builder.compiler(stage, build.build);
-        let rustdoc = builder.rustdoc(compiler);
+        let rustdoc = builder.rustdoc(compiler.host);
         let compiler = if build.force_use_stage1(compiler, target) {
             builder.compiler(1, compiler.host)
         } else {
@@ -493,7 +493,7 @@ impl Step for Test {
         let out = build.doc_out(target);
         t!(fs::create_dir_all(&out));
         let compiler = builder.compiler(stage, build.build);
-        let rustdoc = builder.rustdoc(compiler);
+        let rustdoc = builder.rustdoc(compiler.host);
         let compiler = if build.force_use_stage1(compiler, target) {
             builder.compiler(1, compiler.host)
         } else {
@@ -554,7 +554,7 @@ impl Step for Rustc {
         let out = build.doc_out(target);
         t!(fs::create_dir_all(&out));
         let compiler = builder.compiler(stage, build.build);
-        let rustdoc = builder.rustdoc(compiler);
+        let rustdoc = builder.rustdoc(compiler.host);
         let compiler = if build.force_use_stage1(compiler, target) {
             builder.compiler(1, compiler.host)
         } else {
diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs
index 1a3a008ed26..a9cefb65f49 100644
--- a/src/bootstrap/flags.rs
+++ b/src/bootstrap/flags.rs
@@ -33,7 +33,8 @@ pub struct Flags {
     pub on_fail: Option<String>,
     pub stage: Option<u32>,
     pub keep_stage: Option<u32>,
-    pub build: Interned<String>,
+    pub build: Option<Interned<String>>,
+
     pub host: Vec<Interned<String>>,
     pub target: Vec<Interned<String>>,
     pub config: Option<PathBuf>,
@@ -68,6 +69,14 @@ pub enum Subcommand {
     },
 }
 
+impl Default for Subcommand {
+    fn default() -> Subcommand {
+        Subcommand::Build {
+            paths: vec![PathBuf::from("nowhere")],
+        }
+    }
+}
+
 impl Flags {
     pub fn parse(args: &[String]) -> Flags {
         let mut extra_help = String::new();
@@ -243,10 +252,8 @@ Arguments:
 
         // All subcommands can have an optional "Available paths" section
         if matches.opt_present("verbose") {
-            let flags = Flags::parse(&["build".to_string()]);
-            let mut config = Config::parse(&flags.build, cfg_file.clone());
-            config.build = flags.build.clone();
-            let mut build = Build::new(flags, config);
+            let config = Config::parse(&["build".to_string()]);
+            let mut build = Build::new(config);
             metadata::build(&mut build);
 
             let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
@@ -320,9 +327,7 @@ Arguments:
             stage: stage,
             on_fail: matches.opt_str("on-fail"),
             keep_stage: matches.opt_str("keep-stage").map(|j| j.parse().unwrap()),
-            build: INTERNER.intern_string(matches.opt_str("build").unwrap_or_else(|| {
-                env::var("BUILD").unwrap()
-            })),
+            build: matches.opt_str("build").map(|s| INTERNER.intern_string(s)),
             host: split(matches.opt_strs("host"))
                 .into_iter().map(|x| INTERNER.intern_string(x)).collect::<Vec<_>>(),
             target: split(matches.opt_strs("target"))
diff --git a/src/bootstrap/install.rs b/src/bootstrap/install.rs
index ebfda1e619b..89690e444d1 100644
--- a/src/bootstrap/install.rs
+++ b/src/bootstrap/install.rs
@@ -28,7 +28,7 @@ pub fn install_docs(builder: &Builder, stage: u32, host: Interned<String>) {
 }
 
 pub fn install_std(builder: &Builder, stage: u32) {
-    for target in builder.build.config.target.iter() {
+    for target in &builder.build.targets {
         install_sh(builder, "std", "rust-std", stage, Some(*target));
     }
 }
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index a8485d1d152..1452a38f6ed 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -136,13 +136,13 @@ extern crate toml;
 extern crate libc;
 
 use std::cell::Cell;
-use std::cmp;
 use std::collections::{HashSet, HashMap};
 use std::env;
 use std::fs::{self, File};
 use std::io::Read;
 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};
 
@@ -187,7 +187,7 @@ mod job {
 }
 
 pub use config::Config;
-pub use flags::{Flags, Subcommand};
+use flags::Subcommand;
 use cache::{Interned, INTERNER};
 
 /// A structure representing a Rust compiler.
@@ -215,9 +215,6 @@ pub struct Build {
     // User-specified configuration via config.toml
     config: Config,
 
-    // User-specified configuration via CLI flags
-    flags: Flags,
-
     // Derived properties from the above two configurations
     src: PathBuf,
     out: PathBuf,
@@ -288,9 +285,9 @@ impl Build {
     /// line and the filesystem `config`.
     ///
     /// By default all build output will be placed in the current directory.
-    pub fn new(flags: Flags, config: Config) -> Build {
+    pub fn new(config: Config) -> Build {
         let cwd = t!(env::current_dir());
-        let src = flags.src.clone();
+        let src = config.src.clone();
         let out = cwd.join("build");
 
         let is_sudo = match env::var_os("SUDO_USER") {
@@ -302,43 +299,21 @@ impl Build {
             }
             None => false,
         };
-        let rust_info = channel::GitInfo::new(&src);
-        let cargo_info = channel::GitInfo::new(&src.join("src/tools/cargo"));
-        let rls_info = channel::GitInfo::new(&src.join("src/tools/rls"));
-
-        let hosts = if !flags.host.is_empty() {
-            for host in flags.host.iter() {
-                if !config.host.contains(host) {
-                    panic!("specified host `{}` is not in configuration", host);
-                }
-            }
-            flags.host.clone()
-        } else {
-            config.host.clone()
-        };
-        let targets = if !flags.target.is_empty() {
-            for target in flags.target.iter() {
-                if !config.target.contains(target) {
-                    panic!("specified target `{}` is not in configuration", target);
-                }
-            }
-            flags.target.clone()
-        } else {
-            config.target.clone()
-        };
+        let rust_info = channel::GitInfo::new(&config, &src);
+        let cargo_info = channel::GitInfo::new(&config, &src.join("src/tools/cargo"));
+        let rls_info = channel::GitInfo::new(&config, &src.join("src/tools/rls"));
 
         Build {
             initial_rustc: config.initial_rustc.clone(),
             initial_cargo: config.initial_cargo.clone(),
             local_rebuild: config.local_rebuild,
-            fail_fast: flags.cmd.fail_fast(),
-            verbosity: cmp::max(flags.verbose, config.verbose),
+            fail_fast: config.cmd.fail_fast(),
+            verbosity: config.verbose,
 
-            build: config.host[0].clone(),
-            hosts: hosts,
-            targets: targets,
+            build: config.build,
+            hosts: config.hosts.clone(),
+            targets: config.targets.clone(),
 
-            flags: flags,
             config: config,
             src: src,
             out: out,
@@ -357,13 +332,19 @@ impl Build {
         }
     }
 
+    pub fn build_triple(&self) -> &[Interned<String>] {
+        unsafe {
+            slice::from_raw_parts(&self.build, 1)
+        }
+    }
+
     /// Executes the entire build, as configured by the flags and configuration.
     pub fn build(&mut self) {
         unsafe {
             job::setup(self);
         }
 
-        if let Subcommand::Clean = self.flags.cmd {
+        if let Subcommand::Clean = self.config.cmd {
             return clean::clean(self);
         }
 
@@ -608,7 +589,7 @@ impl Build {
     /// Returns the number of parallel jobs that have been configured for this
     /// build.
     fn jobs(&self) -> u32 {
-        self.flags.jobs.unwrap_or_else(|| num_cpus::get() as u32)
+        self.config.jobs.unwrap_or_else(|| num_cpus::get() as u32)
     }
 
     /// Returns the path to the C compiler for the target specified.
@@ -727,7 +708,7 @@ impl Build {
     fn force_use_stage1(&self, compiler: Compiler, target: Interned<String>) -> bool {
         !self.config.full_bootstrap &&
             compiler.stage >= 2 &&
-            self.config.host.iter().any(|h| *h == target)
+            self.hosts.iter().any(|h| *h == target)
     }
 
     /// Returns the directory that OpenSSL artifacts are compiled into if
diff --git a/src/bootstrap/sanity.rs b/src/bootstrap/sanity.rs
index 7063b28f19d..436a13500f2 100644
--- a/src/bootstrap/sanity.rs
+++ b/src/bootstrap/sanity.rs
@@ -85,7 +85,7 @@ pub fn check(build: &mut Build) {
     }
 
     // We need cmake, but only if we're actually building LLVM or sanitizers.
-    let building_llvm = build.config.host.iter()
+    let building_llvm = build.hosts.iter()
         .filter_map(|host| build.config.target_config.get(host))
         .any(|config| config.llvm_config.is_none());
     if building_llvm || build.config.sanitizers {
@@ -114,7 +114,7 @@ pub fn check(build: &mut Build) {
 
     // We're gonna build some custom C code here and there, host triples
     // also build some C++ shims for LLVM so we need a C++ compiler.
-    for target in &build.config.target {
+    for target in &build.targets {
         // On emscripten we don't actually need the C compiler to just
         // build the target artifacts, only for testing. For the sake
         // of easier bot configuration, just skip detection.
@@ -128,7 +128,7 @@ pub fn check(build: &mut Build) {
         }
     }
 
-    for host in build.config.host.iter() {
+    for host in &build.hosts {
         cmd_finder.must_have(build.cxx(*host).unwrap());
 
         // The msvc hosts don't use jemalloc, turn it off globally to
@@ -144,7 +144,7 @@ pub fn check(build: &mut Build) {
         panic!("FileCheck executable {:?} does not exist", filecheck);
     }
 
-    for target in &build.config.target {
+    for target in &build.targets {
         // Can't compile for iOS unless we're on macOS
         if target.contains("apple-ios") &&
            !build.build.contains("apple-darwin") {
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index 862b3e2b1ed..d798e8de3df 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -23,10 +23,10 @@ use channel::GitInfo;
 use cache::Interned;
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
-struct CleanTools {
-    compiler: Compiler,
-    target: Interned<String>,
-    mode: Mode,
+pub struct CleanTools {
+    pub compiler: Compiler,
+    pub target: Interned<String>,
+    pub mode: Mode,
 }
 
 impl Step for CleanTools {
@@ -82,7 +82,6 @@ impl Step for ToolBuild {
         let target = self.target;
         let tool = self.tool;
 
-        builder.ensure(CleanTools { compiler, target, mode: self.mode });
         match self.mode {
             Mode::Libstd => builder.ensure(compile::Std { compiler, target }),
             Mode::Libtest => builder.ensure(compile::Test { compiler, target }),
@@ -93,36 +92,46 @@ impl Step for ToolBuild {
         let _folder = build.fold_output(|| format!("stage{}-{}", compiler.stage, tool));
         println!("Building stage{} tool {} ({})", compiler.stage, tool, target);
 
-        let mut cargo = builder.cargo(compiler, Mode::Tool, target, "build");
-        let dir = build.src.join("src/tools").join(tool);
-        cargo.arg("--manifest-path").arg(dir.join("Cargo.toml"));
-
-        // We don't want to build tools dynamically as they'll be running across
-        // stages and such and it's just easier if they're not dynamically linked.
-        cargo.env("RUSTC_NO_PREFER_DYNAMIC", "1");
-
-        if let Some(dir) = build.openssl_install_dir(target) {
-            cargo.env("OPENSSL_STATIC", "1");
-            cargo.env("OPENSSL_DIR", dir);
-            cargo.env("LIBZ_SYS_STATIC", "1");
-        }
+        let mut cargo = prepare_tool_cargo(builder, compiler, target, tool);
+        build.run(&mut cargo);
+        build.cargo_out(compiler, Mode::Tool, target).join(exe(tool, &compiler.host))
+    }
+}
 
-        cargo.env("CFG_RELEASE_CHANNEL", &build.config.channel);
+fn prepare_tool_cargo(
+    builder: &Builder,
+    compiler: Compiler,
+    target: Interned<String>,
+    tool: &'static str,
+) -> Command {
+    let build = builder.build;
+    let mut cargo = builder.cargo(compiler, Mode::Tool, target, "build");
+    let dir = build.src.join("src/tools").join(tool);
+    cargo.arg("--manifest-path").arg(dir.join("Cargo.toml"));
+
+    // We don't want to build tools dynamically as they'll be running across
+    // stages and such and it's just easier if they're not dynamically linked.
+    cargo.env("RUSTC_NO_PREFER_DYNAMIC", "1");
+
+    if let Some(dir) = build.openssl_install_dir(target) {
+        cargo.env("OPENSSL_STATIC", "1");
+        cargo.env("OPENSSL_DIR", dir);
+        cargo.env("LIBZ_SYS_STATIC", "1");
+    }
 
-        let info = GitInfo::new(&dir);
-        if let Some(sha) = info.sha() {
-            cargo.env("CFG_COMMIT_HASH", sha);
-        }
-        if let Some(sha_short) = info.sha_short() {
-            cargo.env("CFG_SHORT_COMMIT_HASH", sha_short);
-        }
-        if let Some(date) = info.commit_date() {
-            cargo.env("CFG_COMMIT_DATE", date);
-        }
+    cargo.env("CFG_RELEASE_CHANNEL", &build.config.channel);
 
-        build.run(&mut cargo);
-        build.cargo_out(compiler, Mode::Tool, target).join(exe(tool, &compiler.host))
+    let info = GitInfo::new(&build.config, &dir);
+    if let Some(sha) = info.sha() {
+        cargo.env("CFG_COMMIT_HASH", sha);
+    }
+    if let Some(sha_short) = info.sha_short() {
+        cargo.env("CFG_SHORT_COMMIT_HASH", sha_short);
     }
+    if let Some(date) = info.commit_date() {
+        cargo.env("CFG_COMMIT_DATE", date);
+    }
+    cargo
 }
 
 macro_rules! tool {
@@ -226,7 +235,7 @@ impl Step for RemoteTestServer {
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Rustdoc {
-    pub target_compiler: Compiler,
+    pub host: Interned<String>,
 }
 
 impl Step for Rustdoc {
@@ -240,14 +249,20 @@ impl Step for Rustdoc {
 
     fn make_run(run: RunConfig) {
         run.builder.ensure(Rustdoc {
-            target_compiler: run.builder.compiler(run.builder.top_stage, run.host),
+            host: run.host,
         });
     }
 
     fn run(self, builder: &Builder) -> PathBuf {
-        let target_compiler = self.target_compiler;
+        let build = builder.build;
+        let target_compiler = builder.compiler(builder.top_stage, self.host);
+        let target = target_compiler.host;
         let build_compiler = if target_compiler.stage == 0 {
             builder.compiler(0, builder.build.build)
+        } else if target_compiler.stage >= 2 {
+            // Past stage 2, we consider the compiler to be ABI-compatible and hence capable of
+            // building rustdoc itself.
+            builder.compiler(target_compiler.stage, builder.build.build)
         } else {
             // Similar to `compile::Assemble`, build with the previous stage's compiler. Otherwise
             // we'd have stageN/bin/rustc and stageN/bin/rustdoc be effectively different stage
@@ -255,12 +270,18 @@ impl Step for Rustdoc {
             builder.compiler(target_compiler.stage - 1, builder.build.build)
         };
 
-        let tool_rustdoc = builder.ensure(ToolBuild {
-            compiler: build_compiler,
-            target: target_compiler.host,
-            tool: "rustdoc",
-            mode: Mode::Librustc,
-        });
+        builder.ensure(compile::Rustc { compiler: build_compiler, target });
+
+        let _folder = build.fold_output(|| format!("stage{}-rustdoc", target_compiler.stage));
+        println!("Building rustdoc for stage{} ({})", target_compiler.stage, target_compiler.host);
+
+        let mut cargo = prepare_tool_cargo(builder, build_compiler, target, "rustdoc");
+        build.run(&mut cargo);
+        // Cargo adds a number of paths to the dylib search path on windows, which results in
+        // the wrong rustdoc being executed. To avoid the conflicting rustdocs, we name the "tool"
+        // rustdoc a different name.
+        let tool_rustdoc = build.cargo_out(build_compiler, Mode::Tool, target)
+            .join(exe("rustdoc-tool-binary", &target_compiler.host));
 
         // don't create a stage0-sysroot/bin directory.
         if target_compiler.stage > 0 {
diff --git a/src/tools/rustdoc/Cargo.toml b/src/tools/rustdoc/Cargo.toml
index b6edb76d7f9..344f617ef95 100644
--- a/src/tools/rustdoc/Cargo.toml
+++ b/src/tools/rustdoc/Cargo.toml
@@ -3,8 +3,11 @@ name = "rustdoc-tool"
 version = "0.0.0"
 authors = ["The Rust Project Developers"]
 
+# Cargo adds a number of paths to the dylib search path on windows, which results in
+# the wrong rustdoc being executed. To avoid the conflicting rustdocs, we name the "tool"
+# rustdoc a different name.
 [[bin]]
-name = "rustdoc"
+name = "rustdoc-tool-binary"
 path = "main.rs"
 
 [dependencies]