about summary refs log tree commit diff
path: root/src/bootstrap
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2016-12-27 17:02:52 -0800
committerEsteban Küber <esteban@kuber.com.ar>2016-12-27 17:02:52 -0800
commite766c465d2e4c4e3c106bfa8343cbe6f9192d445 (patch)
tree821a7cf1e0b04ac9c0cddede6eb760bbf2d0ce62 /src/bootstrap
parent96c52d4fd86aed6320732a511c04bcbfff7d117f (diff)
parent314c28b729ae359b99586cc62c486c28e0d44424 (diff)
Merge branch 'master' into escape-reason-docs
Diffstat (limited to 'src/bootstrap')
-rw-r--r--src/bootstrap/README.md49
-rw-r--r--src/bootstrap/bin/rustc.rs55
-rw-r--r--src/bootstrap/bin/rustdoc.rs2
-rw-r--r--src/bootstrap/bootstrap.py24
-rw-r--r--src/bootstrap/channel.rs4
-rw-r--r--src/bootstrap/check.rs7
-rw-r--r--src/bootstrap/config.rs55
-rw-r--r--src/bootstrap/config.toml.example10
-rw-r--r--src/bootstrap/dist.rs36
-rw-r--r--src/bootstrap/flags.rs35
-rw-r--r--src/bootstrap/lib.rs27
-rw-r--r--src/bootstrap/mk/Makefile.in5
-rw-r--r--src/bootstrap/native.rs8
-rw-r--r--src/bootstrap/sanity.rs6
-rw-r--r--src/bootstrap/step.rs65
-rw-r--r--src/bootstrap/util.rs6
16 files changed, 320 insertions, 74 deletions
diff --git a/src/bootstrap/README.md b/src/bootstrap/README.md
index d0b501e4d89..ac84edb4038 100644
--- a/src/bootstrap/README.md
+++ b/src/bootstrap/README.md
@@ -22,7 +22,7 @@ Note that if you're on Unix you should be able to execute the script directly:
 ./x.py build
 ```
 
-The script accepts commands, flags, and filters to determine what to do:
+The script accepts commands, flags, and arguments to determine what to do:
 
 * `build` - a general purpose command for compiling code. Alone `build` will
   bootstrap the entire compiler, and otherwise arguments passed indicate what to
@@ -42,6 +42,15 @@ The script accepts commands, flags, and filters to determine what to do:
   ./x.py build --stage 0 src/libtest
   ```
 
+  If files are dirty that would normally be rebuilt from stage 0, that can be
+  overidden using `--keep-stage 0`. Using `--keep-stage n` will skip all steps
+  that belong to stage n or earlier:
+
+  ```
+  # keep old build products for stage 0 and build stage 1
+  ./x.py build --keep-stage 0 --stage 1
+  ```
+
 * `test` - a command for executing unit tests. Like the `build` command this
   will execute the entire test suite by default, and otherwise it can be used to
   select which test suite is run:
@@ -54,7 +63,7 @@ The script accepts commands, flags, and filters to determine what to do:
   ./x.py test src/test/run-pass
 
   # execute only some tests in the run-pass test suite
-  ./x.py test src/test/run-pass --filter my-filter
+  ./x.py test src/test/run-pass --test-args substring-of-test-name
 
   # execute tests in the standard library in stage0
   ./x.py test --stage 0 src/libstd
@@ -107,6 +116,42 @@ compiler. What actually happens when you invoke rustbuild is:
 The goal of each stage is to (a) leverage Cargo as much as possible and failing
 that (b) leverage Rust as much as possible!
 
+## Incremental builds
+
+You can configure rustbuild to use incremental compilation. Because
+incremental is new and evolving rapidly, if you want to use it, it is
+recommended that you replace the snapshot with a locally installed
+nightly build of rustc. You will want to keep this up to date.
+
+To follow this course of action, first thing you will want to do is to
+install a nightly, presumably using `rustup`. You will then want to
+configure your directory to use this build, like so:
+
+```
+# configure to use local rust instead of downloding a beta.
+# `--local-rust-root` is optional here. If elided, we will
+# use whatever rustc we find on your PATH.
+> configure --enable-rustbuild --local-rust-root=~/.cargo/ --enable-local-rebuild
+```
+
+After that, you can use the `--incremental` flag to actually do
+incremental builds:
+
+```
+> ../x.py build --incremental
+```
+
+The `--incremental` flag will store incremental compilation artifacts
+in `build/<host>/stage0-incremental`. Note that we only use incremental
+compilation for the stage0 -> stage1 compilation -- this is because
+the stage1 compiler is changing, and we don't try to cache and reuse
+incremental artifacts across different versions of the compiler. For
+this reason, `--incremental` defaults to `--stage 1` (though you can
+manually select a higher stage, if you prefer).
+
+You can always drop the `--incremental` to build as normal (but you
+will still be using the local nightly as your bootstrap).
+
 ## Directory Layout
 
 This build system houses all output under the `build` directory, which looks
diff --git a/src/bootstrap/bin/rustc.rs b/src/bootstrap/bin/rustc.rs
index 2f674a311fe..9cab6c423f5 100644
--- a/src/bootstrap/bin/rustc.rs
+++ b/src/bootstrap/bin/rustc.rs
@@ -25,12 +25,17 @@
 //! switching compilers for the bootstrap and for build scripts will probably
 //! never get replaced.
 
+#![deny(warnings)]
+
 extern crate bootstrap;
 
 use std::env;
 use std::ffi::OsString;
+use std::io;
+use std::io::prelude::*;
+use std::str::FromStr;
 use std::path::PathBuf;
-use std::process::Command;
+use std::process::{Command, ExitStatus};
 
 fn main() {
     let args = env::args_os().skip(1).collect::<Vec<_>>();
@@ -41,6 +46,11 @@ fn main() {
         .and_then(|w| w[1].to_str());
     let version = args.iter().find(|w| &**w == "-vV");
 
+    let verbose = match env::var("RUSTC_VERBOSE") {
+        Ok(s) => usize::from_str(&s).expect("RUSTC_VERBOSE should be an integer"),
+        Err(_) => 0,
+    };
+
     // Build scripts always use the snapshot compiler which is guaranteed to be
     // able to produce an executable, whereas intermediate compilers may not
     // have the standard library built yet and may not be able to produce an
@@ -95,6 +105,15 @@ fn main() {
             cmd.args(&s.split(" ").filter(|s| !s.is_empty()).collect::<Vec<_>>());
         }
 
+        // Pass down incremental directory, if any.
+        if let Ok(dir) = env::var("RUSTC_INCREMENTAL") {
+            cmd.arg(format!("-Zincremental={}", dir));
+
+            if verbose > 0 {
+                cmd.arg("-Zincremental-info");
+            }
+        }
+
         // If we're compiling specifically the `panic_abort` crate then we pass
         // the `-C panic=abort` option. Note that we do not do this for any
         // other crate intentionally as this is the only crate for now that we
@@ -158,6 +177,15 @@ fn main() {
         // to change a flag in a binary?
         if env::var("RUSTC_RPATH") == Ok("true".to_string()) {
             let rpath = if target.contains("apple") {
+
+                // Note that we need to take one extra step on OSX to also pass
+                // `-Wl,-instal_name,@rpath/...` to get things to work right. To
+                // do that we pass a weird flag to the compiler to get it to do
+                // so. Note that this is definitely a hack, and we should likely
+                // flesh out rpath support more fully in the future.
+                if stage != "0" {
+                    cmd.arg("-Z").arg("osx-rpath-install-name");
+                }
                 Some("-Wl,-rpath,@loader_path/../lib")
             } else if !target.contains("windows") {
                 Some("-Wl,-rpath,$ORIGIN/../lib")
@@ -167,12 +195,33 @@ fn main() {
             if let Some(rpath) = rpath {
                 cmd.arg("-C").arg(format!("link-args={}", rpath));
             }
+
+            if let Ok(s) = env::var("RUSTFLAGS") {
+                for flag in s.split_whitespace() {
+                    cmd.arg(flag);
+                }
+            }
         }
     }
 
+    if verbose > 1 {
+        writeln!(&mut io::stderr(), "rustc command: {:?}", cmd).unwrap();
+    }
+
     // Actually run the compiler!
-    std::process::exit(match cmd.status() {
-        Ok(s) => s.code().unwrap_or(1),
+    std::process::exit(match exec_cmd(&mut cmd) {
+        Ok(s) => s.code().unwrap_or(0xfe),
         Err(e) => panic!("\n\nfailed to run {:?}: {}\n\n", cmd, e),
     })
 }
+
+#[cfg(unix)]
+fn exec_cmd(cmd: &mut Command) -> ::std::io::Result<ExitStatus> {
+    use std::os::unix::process::CommandExt;
+    Err(cmd.exec())
+}
+
+#[cfg(not(unix))]
+fn exec_cmd(cmd: &mut Command) -> ::std::io::Result<ExitStatus> {
+    cmd.status()
+}
diff --git a/src/bootstrap/bin/rustdoc.rs b/src/bootstrap/bin/rustdoc.rs
index 67358e540da..a53bbe22eb9 100644
--- a/src/bootstrap/bin/rustdoc.rs
+++ b/src/bootstrap/bin/rustdoc.rs
@@ -12,6 +12,8 @@
 //!
 //! See comments in `src/bootstrap/rustc.rs` for more information.
 
+#![deny(warnings)]
+
 extern crate bootstrap;
 
 use std::env;
diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py
index 0dda7f12007..89d297760e2 100644
--- a/src/bootstrap/bootstrap.py
+++ b/src/bootstrap/bootstrap.py
@@ -81,7 +81,7 @@ def verify(path, sha_path, verbose):
     with open(path, "rb") as f:
         found = hashlib.sha256(f.read()).hexdigest()
     with open(sha_path, "r") as f:
-        expected, _ = f.readline().split()
+        expected = f.readline().split()[0]
     verified = found == expected
     if not verified:
         print("invalid checksum:\n"
@@ -146,7 +146,7 @@ class RustBuild(object):
     def download_stage0(self):
         cache_dst = os.path.join(self.build_dir, "cache")
         rustc_cache = os.path.join(cache_dst, self.stage0_rustc_date())
-        cargo_cache = os.path.join(cache_dst, self.stage0_cargo_date())
+        cargo_cache = os.path.join(cache_dst, self.stage0_cargo_rev())
         if not os.path.exists(rustc_cache):
             os.makedirs(rustc_cache)
         if not os.path.exists(cargo_cache):
@@ -179,21 +179,17 @@ class RustBuild(object):
         if self.cargo().startswith(self.bin_root()) and \
                 (not os.path.exists(self.cargo()) or self.cargo_out_of_date()):
             self.print_what_it_means_to_bootstrap()
-            channel = self.stage0_cargo_channel()
-            filename = "cargo-{}-{}.tar.gz".format(channel, self.build)
-            url = "https://static.rust-lang.org/cargo-dist/" + self.stage0_cargo_date()
+            filename = "cargo-nightly-{}.tar.gz".format(self.build)
+            url = "https://s3.amazonaws.com/rust-lang-ci/cargo-builds/" + self.stage0_cargo_rev()
             tarball = os.path.join(cargo_cache, filename)
             if not os.path.exists(tarball):
                 get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
             unpack(tarball, self.bin_root(), match="cargo", verbose=self.verbose)
             with open(self.cargo_stamp(), 'w') as f:
-                f.write(self.stage0_cargo_date())
+                f.write(self.stage0_cargo_rev())
 
-    def stage0_cargo_date(self):
-        return self._cargo_date
-
-    def stage0_cargo_channel(self):
-        return self._cargo_channel
+    def stage0_cargo_rev(self):
+        return self._cargo_rev
 
     def stage0_rustc_date(self):
         return self._rustc_date
@@ -217,7 +213,7 @@ class RustBuild(object):
         if not os.path.exists(self.cargo_stamp()) or self.clean:
             return True
         with open(self.cargo_stamp(), 'r') as f:
-            return self.stage0_cargo_date() != f.read()
+            return self.stage0_cargo_rev() != f.read()
 
     def bin_root(self):
         return os.path.join(self.build_dir, self.build, "stage0")
@@ -294,6 +290,8 @@ class RustBuild(object):
         env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib")
         env["PATH"] = os.path.join(self.bin_root(), "bin") + \
                       os.pathsep + env["PATH"]
+        if not os.path.isfile(self.cargo()):
+            raise Exception("no cargo executable found at `%s`" % self.cargo())
         args = [self.cargo(), "build", "--manifest-path",
                 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
         if self.use_vendored_sources:
@@ -467,7 +465,7 @@ def main():
 
     data = stage0_data(rb.rust_root)
     rb._rustc_channel, rb._rustc_date = data['rustc'].split('-', 1)
-    rb._cargo_channel, rb._cargo_date = data['cargo'].split('-', 1)
+    rb._cargo_rev = data['cargo']
 
     start_time = time()
 
diff --git a/src/bootstrap/channel.rs b/src/bootstrap/channel.rs
index b2341f59787..c38bb33aa02 100644
--- a/src/bootstrap/channel.rs
+++ b/src/bootstrap/channel.rs
@@ -15,7 +15,7 @@
 //! `package_vers`, and otherwise indicating to the compiler what it should
 //! print out as part of its version information.
 
-use std::fs::{self, File};
+use std::fs::File;
 use std::io::prelude::*;
 use std::process::Command;
 
@@ -69,7 +69,7 @@ pub fn collect(build: &mut Build) {
 
     // If we have a git directory, add in some various SHA information of what
     // commit this compiler was compiled from.
-    if fs::metadata(build.src.join(".git")).is_ok() {
+    if build.src.join(".git").is_dir() {
         let ver_date = output(Command::new("git").current_dir(&build.src)
                                       .arg("log").arg("-1")
                                       .arg("--date=short")
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index e0798860275..e7b0afeb8ce 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -13,6 +13,8 @@
 //! This file implements the various regression test suites that we execute on
 //! our CI.
 
+extern crate build_helper;
+
 use std::collections::HashSet;
 use std::env;
 use std::fmt;
@@ -190,7 +192,7 @@ pub fn compiletest(build: &Build,
 
     cmd.args(&build.flags.cmd.test_args());
 
-    if build.config.verbose || build.flags.verbose {
+    if build.config.verbose() || build.flags.verbose() {
         cmd.arg("--verbose");
     }
 
@@ -299,6 +301,7 @@ fn markdown_test(build: &Build, compiler: &Compiler, markdown: &Path) {
     build.add_rust_test_threads(&mut cmd);
     cmd.arg("--test");
     cmd.arg(markdown);
+    cmd.env("RUSTC_BOOTSTRAP", "1");
 
     let mut test_args = build.flags.cmd.test_args().join(" ");
     if build.config.quiet_tests {
@@ -542,7 +545,7 @@ pub fn distcheck(build: &Build) {
     build.run(&mut cmd);
     build.run(Command::new("./configure")
                      .current_dir(&dir));
-    build.run(Command::new("make")
+    build.run(Command::new(build_helper::make(&build.config.build))
                      .arg("check")
                      .current_dir(&dir));
 }
diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs
index 60f65f62300..6b86e537b7d 100644
--- a/src/bootstrap/config.rs
+++ b/src/bootstrap/config.rs
@@ -38,9 +38,9 @@ use util::push_exe_path;
 /// `src/bootstrap/config.toml.example`.
 #[derive(Default)]
 pub struct Config {
-    pub ccache: bool,
+    pub ccache: Option<String>,
     pub ninja: bool,
-    pub verbose: bool,
+    pub verbose: usize,
     pub submodules: bool,
     pub compiler_docs: bool,
     pub docs: bool,
@@ -113,6 +113,7 @@ pub struct Target {
 #[derive(RustcDecodable, Default)]
 struct TomlConfig {
     build: Option<Build>,
+    install: Option<Install>,
     llvm: Option<Llvm>,
     rust: Option<Rust>,
     target: Option<HashMap<String, TomlTarget>>,
@@ -135,10 +136,16 @@ struct Build {
     python: Option<String>,
 }
 
+/// TOML representation of various global install decisions.
+#[derive(RustcDecodable, Default, Clone)]
+struct Install {
+    prefix: Option<String>,
+}
+
 /// TOML representation of how the LLVM build is configured.
 #[derive(RustcDecodable, Default)]
 struct Llvm {
-    ccache: Option<bool>,
+    ccache: Option<StringOrBool>,
     ninja: Option<bool>,
     assertions: Option<bool>,
     optimize: Option<bool>,
@@ -147,6 +154,18 @@ struct Llvm {
     static_libstdcpp: Option<bool>,
 }
 
+#[derive(RustcDecodable)]
+enum StringOrBool {
+    String(String),
+    Bool(bool),
+}
+
+impl Default for StringOrBool {
+    fn default() -> StringOrBool {
+        StringOrBool::Bool(false)
+    }
+}
+
 /// TOML representation of how the Rust build is configured.
 #[derive(RustcDecodable, Default)]
 struct Rust {
@@ -246,8 +265,20 @@ impl Config {
         set(&mut config.submodules, build.submodules);
         set(&mut config.vendor, build.vendor);
 
+        if let Some(ref install) = toml.install {
+            config.prefix = install.prefix.clone();
+        }
+
         if let Some(ref llvm) = toml.llvm {
-            set(&mut config.ccache, llvm.ccache);
+            match llvm.ccache {
+                Some(StringOrBool::String(ref s)) => {
+                    config.ccache = Some(s.to_string())
+                }
+                Some(StringOrBool::Bool(true)) => {
+                    config.ccache = Some("ccache".to_string());
+                }
+                Some(StringOrBool::Bool(false)) | None => {}
+            }
             set(&mut config.ninja, llvm.ninja);
             set(&mut config.llvm_assertions, llvm.assertions);
             set(&mut config.llvm_optimize, llvm.optimize);
@@ -255,6 +286,7 @@ impl Config {
             set(&mut config.llvm_version_check, llvm.version_check);
             set(&mut config.llvm_static_stdcpp, llvm.static_libstdcpp);
         }
+
         if let Some(ref rust) = toml.rust {
             set(&mut config.rust_debug_assertions, rust.debug_assertions);
             set(&mut config.rust_debuginfo, rust.debuginfo);
@@ -338,7 +370,6 @@ impl Config {
             }
 
             check! {
-                ("CCACHE", self.ccache),
                 ("MANAGE_SUBMODULES", self.submodules),
                 ("COMPILER_DOCS", self.compiler_docs),
                 ("DOCS", self.docs),
@@ -475,10 +506,24 @@ impl Config {
                     let path = parse_configure_path(value);
                     self.python = Some(path);
                 }
+                "CFG_ENABLE_CCACHE" if value == "1" => {
+                    self.ccache = Some("ccache".to_string());
+                }
+                "CFG_ENABLE_SCCACHE" if value == "1" => {
+                    self.ccache = Some("sccache".to_string());
+                }
                 _ => {}
             }
         }
     }
+
+    pub fn verbose(&self) -> bool {
+        self.verbose > 0
+    }
+
+    pub fn very_verbose(&self) -> bool {
+        self.verbose > 1
+    }
 }
 
 #[cfg(not(windows))]
diff --git a/src/bootstrap/config.toml.example b/src/bootstrap/config.toml.example
index b6774b3af20..5fc095137c7 100644
--- a/src/bootstrap/config.toml.example
+++ b/src/bootstrap/config.toml.example
@@ -25,6 +25,8 @@
 
 # Indicates whether ccache is used when building LLVM
 #ccache = false
+# or alternatively ...
+#ccache = "/path/to/ccache"
 
 # If an external LLVM root is specified, we automatically check the version by
 # default to make sure it's within the range that we're expecting, but setting
@@ -99,6 +101,14 @@
 #vendor = false
 
 # =============================================================================
+# General install configuration options
+# =============================================================================
+[install]
+
+# Instead of installing to /usr/local, install to this path instead.
+#prefix = "/path/to/install"
+
+# =============================================================================
 # Options for compiling Rust code itself
 # =============================================================================
 [rust]
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index 1d3445a9eac..6e3174ed2f6 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -48,6 +48,11 @@ pub fn tmpdir(build: &Build) -> PathBuf {
 /// Slurps up documentation from the `stage`'s `host`.
 pub fn docs(build: &Build, stage: u32, host: &str) {
     println!("Dist docs stage{} ({})", stage, host);
+    if !build.config.docs {
+        println!("\tskipping - docs disabled");
+        return
+    }
+
     let name = format!("rust-docs-{}", package_vers(build));
     let image = tmpdir(build).join(format!("{}-{}-image", name, name));
     let _ = fs::remove_dir_all(&image);
@@ -92,6 +97,7 @@ pub fn mingw(build: &Build, host: &str) {
     let name = format!("rust-mingw-{}", package_vers(build));
     let image = tmpdir(build).join(format!("{}-{}-image", name, host));
     let _ = fs::remove_dir_all(&image);
+    t!(fs::create_dir_all(&image));
 
     // The first argument to the script is a "temporary directory" which is just
     // thrown away (this contains the runtime DLLs included in the rustc package
@@ -260,6 +266,14 @@ pub fn debugger_scripts(build: &Build,
 pub fn std(build: &Build, compiler: &Compiler, target: &str) {
     println!("Dist std stage{} ({} -> {})", compiler.stage, compiler.host,
              target);
+
+    // The only true set of target libraries came from the build triple, so
+    // let's reduce redundant work by only producing archives from that host.
+    if compiler.host != build.config.build {
+        println!("\tskipping, not a build host");
+        return
+    }
+
     let name = format!("rust-std-{}", package_vers(build));
     let image = tmpdir(build).join(format!("{}-{}-image", name, target));
     let _ = fs::remove_dir_all(&image);
@@ -294,10 +308,15 @@ pub fn analysis(build: &Build, compiler: &Compiler, target: &str) {
     println!("Dist analysis");
 
     if build.config.channel != "nightly" {
-        println!("Skipping dist-analysis - not on nightly channel");
+        println!("\tskipping - not on nightly channel");
         return;
     }
+    if compiler.host != build.config.build {
+        println!("\tskipping - not a build host");
+        return
+    }
     if compiler.stage != 2 {
+        println!("\tskipping - not stage2");
         return
     }
 
@@ -324,18 +343,17 @@ pub fn analysis(build: &Build, compiler: &Compiler, target: &str) {
        .arg("--legacy-manifest-dirs=rustlib,cargo");
     build.run(&mut cmd);
     t!(fs::remove_dir_all(&image));
-
-    // Create plain source tarball
-    let mut cmd = Command::new("tar");
-    cmd.arg("-czf").arg(sanitize_sh(&distdir(build).join(&format!("{}.tar.gz", name))))
-       .arg("analysis")
-       .current_dir(&src);
-    build.run(&mut cmd);
 }
 
 /// Creates the `rust-src` installer component and the plain source tarball
-pub fn rust_src(build: &Build) {
+pub fn rust_src(build: &Build, host: &str) {
     println!("Dist src");
+
+    if host != build.config.build {
+        println!("\tskipping, not a build host");
+        return
+    }
+
     let plain_name = format!("rustc-{}-src", package_vers(build));
     let name = format!("rust-src-{}", package_vers(build));
     let image = tmpdir(build).join(format!("{}-image", name));
diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs
index 7a2d56fc5d3..b2412fbb3c8 100644
--- a/src/bootstrap/flags.rs
+++ b/src/bootstrap/flags.rs
@@ -27,8 +27,9 @@ use step;
 
 /// Deserialized version of all flags for this compile.
 pub struct Flags {
-    pub verbose: bool,
+    pub verbose: usize, // verbosity level: 0 == not verbose, 1 == verbose, 2 == very verbose
     pub stage: Option<u32>,
+    pub keep_stage: Option<u32>,
     pub build: String,
     pub host: Vec<String>,
     pub target: Vec<String>,
@@ -36,6 +37,17 @@ pub struct Flags {
     pub src: Option<PathBuf>,
     pub jobs: Option<u32>,
     pub cmd: Subcommand,
+    pub incremental: bool,
+}
+
+impl Flags {
+    pub fn verbose(&self) -> bool {
+        self.verbose > 0
+    }
+
+    pub fn very_verbose(&self) -> bool {
+        self.verbose > 1
+    }
 }
 
 pub enum Subcommand {
@@ -62,12 +74,14 @@ pub enum Subcommand {
 impl Flags {
     pub fn parse(args: &[String]) -> Flags {
         let mut opts = Options::new();
-        opts.optflag("v", "verbose", "use verbose output");
+        opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
+        opts.optflag("i", "incremental", "use incremental compilation");
         opts.optopt("", "config", "TOML configuration file for build", "FILE");
         opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
         opts.optmulti("", "host", "host targets to build", "HOST");
         opts.optmulti("", "target", "target targets to build", "TARGET");
         opts.optopt("", "stage", "stage to build", "N");
+        opts.optopt("", "keep-stage", "stage to keep without recompiling", "N");
         opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
         opts.optopt("j", "jobs", "number of jobs to run in parallel", "JOBS");
         opts.optflag("h", "help", "print this help message");
@@ -108,7 +122,6 @@ Arguments:
     tests that should be compiled and run. For example:
 
         ./x.py test src/test/run-pass
-        ./x.py test src/test/run-pass/assert-*
         ./x.py test src/libstd --test-args hash_map
         ./x.py test src/libstd --stage 0
 
@@ -255,9 +268,20 @@ To learn more about a subcommand, run `./x.py <command> -h`
             }
         });
 
+        let mut stage = m.opt_str("stage").map(|j| j.parse().unwrap());
+
+        let incremental = m.opt_present("i");
+
+        if incremental {
+            if stage.is_none() {
+                stage = Some(1);
+            }
+        }
+
         Flags {
-            verbose: m.opt_present("v"),
-            stage: m.opt_str("stage").map(|j| j.parse().unwrap()),
+            verbose: m.opt_count("v"),
+            stage: stage,
+            keep_stage: m.opt_str("keep-stage").map(|j| j.parse().unwrap()),
             build: m.opt_str("build").unwrap_or_else(|| {
                 env::var("BUILD").unwrap()
             }),
@@ -267,6 +291,7 @@ To learn more about a subcommand, run `./x.py <command> -h`
             src: m.opt_str("src").map(PathBuf::from),
             jobs: m.opt_str("jobs").map(|j| j.parse().unwrap()),
             cmd: cmd,
+            incremental: incremental,
         }
     }
 }
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index cd80c4298dc..665e0c67b7f 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -64,6 +64,8 @@
 //! More documentation can be found in each respective module below, and you can
 //! also check out the `src/bootstrap/README.md` file for more information.
 
+#![deny(warnings)]
+
 extern crate build_helper;
 extern crate cmake;
 extern crate filetime;
@@ -74,6 +76,7 @@ extern crate rustc_serialize;
 extern crate toml;
 
 use std::collections::HashMap;
+use std::cmp;
 use std::env;
 use std::ffi::OsString;
 use std::fs::{self, File};
@@ -497,6 +500,17 @@ impl Build {
         cargo.env("RUSTC_BOOTSTRAP", "1");
         self.add_rust_test_threads(&mut cargo);
 
+        // Ignore incremental modes except for stage0, since we're
+        // not guaranteeing correctness acros builds if the compiler
+        // is changing under your feet.`
+        if self.flags.incremental && compiler.stage == 0 {
+            let incr_dir = self.incremental_dir(compiler);
+            cargo.env("RUSTC_INCREMENTAL", incr_dir);
+        }
+
+        let verbose = cmp::max(self.config.verbose, self.flags.verbose);
+        cargo.env("RUSTC_VERBOSE", format!("{}", verbose));
+
         // Specify some various options for build scripts used throughout
         // the build.
         //
@@ -516,7 +530,7 @@ impl Build {
         // FIXME: should update code to not require this env var
         cargo.env("CFG_COMPILER_HOST_TRIPLE", target);
 
-        if self.config.verbose || self.flags.verbose {
+        if self.config.verbose() || self.flags.verbose() {
             cargo.arg("-v");
         }
         // FIXME: cargo bench does not accept `--release`
@@ -630,6 +644,12 @@ impl Build {
         }
     }
 
+    /// Get the directory for incremental by-products when using the
+    /// given compiler.
+    fn incremental_dir(&self, compiler: &Compiler) -> PathBuf {
+        self.out.join(compiler.host).join(format!("stage{}-incremental", compiler.stage))
+    }
+
     /// Returns the libdir where the standard library and other artifacts are
     /// found for a compiler's sysroot.
     fn sysroot_libdir(&self, compiler: &Compiler, target: &str) -> PathBuf {
@@ -703,7 +723,8 @@ impl Build {
     fn llvm_filecheck(&self, target: &str) -> PathBuf {
         let target_config = self.config.target_config.get(target);
         if let Some(s) = target_config.and_then(|c| c.llvm_config.as_ref()) {
-            s.parent().unwrap().join(exe("FileCheck", target))
+            let llvm_bindir = output(Command::new(s).arg("--bindir"));
+            Path::new(llvm_bindir.trim()).join(exe("FileCheck", target))
         } else {
             let base = self.llvm_out(&self.config.build).join("build");
             let exe = exe("FileCheck", target);
@@ -768,7 +789,7 @@ impl Build {
 
     /// Prints a message if this build is configured in verbose mode.
     fn verbose(&self, msg: &str) {
-        if self.flags.verbose || self.config.verbose {
+        if self.flags.verbose() || self.config.verbose() {
             println!("{}", msg);
         }
     }
diff --git a/src/bootstrap/mk/Makefile.in b/src/bootstrap/mk/Makefile.in
index 1fa70081938..0d83a79cf32 100644
--- a/src/bootstrap/mk/Makefile.in
+++ b/src/bootstrap/mk/Makefile.in
@@ -56,7 +56,8 @@ check-cargotest:
 dist:
 	$(Q)$(BOOTSTRAP) dist $(BOOTSTRAP_ARGS)
 distcheck:
-	$(Q)$(BOOTSTRAP) test distcheck
+	$(Q)$(BOOTSTRAP) dist $(BOOTSTRAP_ARGS)
+	$(Q)$(BOOTSTRAP) test distcheck $(BOOTSTRAP_ARGS)
 install:
 	$(Q)$(BOOTSTRAP) dist --install $(BOOTSTRAP_ARGS)
 tidy:
@@ -65,7 +66,7 @@ tidy:
 check-stage2-T-arm-linux-androideabi-H-x86_64-unknown-linux-gnu:
 	$(Q)$(BOOTSTRAP) test --target arm-linux-androideabi
 check-stage2-T-x86_64-unknown-linux-musl-H-x86_64-unknown-linux-gnu:
-	$(Q)$(BOOTSTRAP) test --target x86_64-unknown-linux-gnu
+	$(Q)$(BOOTSTRAP) test --target x86_64-unknown-linux-musl
 
 
 .PHONY: dist
diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs
index ffa3fe1cbf2..09dbd9f8220 100644
--- a/src/bootstrap/native.rs
+++ b/src/bootstrap/native.rs
@@ -81,7 +81,7 @@ pub fn llvm(build: &Build, target: &str) {
        .profile(profile)
        .define("LLVM_ENABLE_ASSERTIONS", assertions)
        .define("LLVM_TARGETS_TO_BUILD",
-               "X86;ARM;AArch64;Mips;PowerPC;SystemZ;JSBackend;MSP430")
+               "X86;ARM;AArch64;Mips;PowerPC;SystemZ;JSBackend;MSP430;Sparc")
        .define("LLVM_INCLUDE_EXAMPLES", "OFF")
        .define("LLVM_INCLUDE_TESTS", "OFF")
        .define("LLVM_INCLUDE_DOCS", "OFF")
@@ -109,10 +109,10 @@ pub fn llvm(build: &Build, target: &str) {
 
     // MSVC handles compiler business itself
     if !target.contains("msvc") {
-        if build.config.ccache {
-           cfg.define("CMAKE_C_COMPILER", "ccache")
+        if let Some(ref ccache) = build.config.ccache {
+           cfg.define("CMAKE_C_COMPILER", ccache)
               .define("CMAKE_C_COMPILER_ARG1", build.cc(target))
-              .define("CMAKE_CXX_COMPILER", "ccache")
+              .define("CMAKE_CXX_COMPILER", ccache)
               .define("CMAKE_CXX_COMPILER_ARG1", build.cxx(target));
         } else {
            cfg.define("CMAKE_C_COMPILER", build.cc(target))
diff --git a/src/bootstrap/sanity.rs b/src/bootstrap/sanity.rs
index f3fe22698bb..5d543419fc9 100644
--- a/src/bootstrap/sanity.rs
+++ b/src/bootstrap/sanity.rs
@@ -143,7 +143,7 @@ pub fn check(build: &mut Build) {
     // Externally configured LLVM requires FileCheck to exist
     let filecheck = build.llvm_filecheck(&build.config.build);
     if !filecheck.starts_with(&build.out) && !filecheck.exists() && build.config.codegen_tests {
-        panic!("filecheck executable {:?} does not exist", filecheck);
+        panic!("FileCheck executable {:?} does not exist", filecheck);
     }
 
     for target in build.config.target.iter() {
@@ -223,4 +223,8 @@ $ pacman -R cmake && pacman -S mingw-w64-x86_64-cmake
     if build.lldb_version.is_some() {
         build.lldb_python_dir = run(Command::new("lldb").arg("-P")).ok();
     }
+
+    if let Some(ref s) = build.config.ccache {
+        need_cmd(s.as_ref());
+    }
 }
diff --git a/src/bootstrap/step.rs b/src/bootstrap/step.rs
index 884cc7da8ea..c5898c1119a 100644
--- a/src/bootstrap/step.rs
+++ b/src/bootstrap/step.rs
@@ -86,7 +86,7 @@ pub fn build_rules(build: &Build) -> Rules {
     //
     // To handle this we do a bit of dynamic dispatch to see what the dependency
     // is. If we're building a LLVM for the build triple, then we don't actually
-    // have any dependencies! To do that we return a dependency on the "dummy"
+    // have any dependencies! To do that we return a dependency on the `Step::noop()`
     // target which does nothing.
     //
     // If we're build a cross-compiled LLVM, however, we need to assemble the
@@ -104,7 +104,7 @@ pub fn build_rules(build: &Build) -> Rules {
          .host(true)
          .dep(move |s| {
              if s.target == build.config.build {
-                 dummy(s, build)
+                 Step::noop()
              } else {
                  s.target(&build.config.build)
              }
@@ -115,14 +115,11 @@ pub fn build_rules(build: &Build) -> Rules {
     // going on here. You can check out the API docs below and also see a bunch
     // more examples of rules directly below as well.
 
-    // dummy rule to do nothing, useful when a dep maps to no deps
-    rules.build("dummy", "path/to/nowhere");
-
     // the compiler with no target libraries ready to go
     rules.build("rustc", "src/rustc")
          .dep(move |s| {
              if s.stage == 0 {
-                 dummy(s, build)
+                 Step::noop()
              } else {
                  s.name("librustc")
                   .host(&build.config.build)
@@ -165,7 +162,7 @@ pub fn build_rules(build: &Build) -> Rules {
              .dep(move |s| s.name("rustc").host(&build.config.build).target(s.host))
              .dep(move |s| {
                  if s.host == build.config.build {
-                    dummy(s, build)
+                     Step::noop()
                  } else {
                     s.host(&build.config.build)
                  }
@@ -183,7 +180,7 @@ pub fn build_rules(build: &Build) -> Rules {
              .dep(|s| s.name("libstd"))
              .dep(move |s| {
                  if s.host == build.config.build {
-                    dummy(s, build)
+                    Step::noop()
                  } else {
                     s.host(&build.config.build)
                  }
@@ -203,7 +200,7 @@ pub fn build_rules(build: &Build) -> Rules {
              .dep(move |s| s.name("llvm").host(&build.config.build).stage(0))
              .dep(move |s| {
                  if s.host == build.config.build {
-                    dummy(s, build)
+                    Step::noop()
                  } else {
                     s.host(&build.config.build)
                  }
@@ -233,7 +230,7 @@ pub fn build_rules(build: &Build) -> Rules {
                      if s.target.contains("android") {
                          s.name("android-copy-libs")
                      } else {
-                         dummy(s, build)
+                         Step::noop()
                      }
                  })
                  .default(true)
@@ -270,16 +267,18 @@ pub fn build_rules(build: &Build) -> Rules {
         // nothing to do for debuginfo tests
     } else if build.config.build.contains("apple") {
         rules.test("check-debuginfo", "src/test/debuginfo")
+             .default(true)
              .dep(|s| s.name("libtest"))
-             .dep(|s| s.name("tool-compiletest").host(s.host))
+             .dep(|s| s.name("tool-compiletest").target(s.host))
              .dep(|s| s.name("test-helpers"))
              .dep(|s| s.name("debugger-scripts"))
              .run(move |s| check::compiletest(build, &s.compiler(), s.target,
                                          "debuginfo-lldb", "debuginfo"));
     } else {
         rules.test("check-debuginfo", "src/test/debuginfo")
+             .default(true)
              .dep(|s| s.name("libtest"))
-             .dep(|s| s.name("tool-compiletest").host(s.host))
+             .dep(|s| s.name("tool-compiletest").target(s.host))
              .dep(|s| s.name("test-helpers"))
              .dep(|s| s.name("debugger-scripts"))
              .run(move |s| check::compiletest(build, &s.compiler(), s.target,
@@ -458,7 +457,7 @@ pub fn build_rules(build: &Build) -> Rules {
     for (krate, path, default) in krates("test_shim") {
         rules.doc(&krate.doc_step, path)
              .dep(|s| s.name("libtest"))
-             .default(default && build.config.docs)
+             .default(default && build.config.compiler_docs)
              .run(move |s| doc::test(build, s.stage, s.target));
     }
     for (krate, path, default) in krates("rustc-main") {
@@ -490,16 +489,21 @@ pub fn build_rules(build: &Build) -> Rules {
          .default(true)
          .run(move |s| dist::std(build, &s.compiler(), s.target));
     rules.dist("dist-mingw", "path/to/nowhere")
-         .run(move |s| dist::mingw(build, s.target));
+         .default(true)
+         .run(move |s| {
+             if s.target.contains("pc-windows-gnu") {
+                 dist::mingw(build, s.target)
+             }
+         });
     rules.dist("dist-src", "src")
          .default(true)
          .host(true)
-         .run(move |_| dist::rust_src(build));
+         .run(move |s| dist::rust_src(build, s.target));
     rules.dist("dist-docs", "src/doc")
          .default(true)
          .dep(|s| s.name("default:doc"))
          .run(move |s| dist::docs(build, s.stage, s.target));
-    rules.dist("dist-analysis", "src/libstd")
+    rules.dist("dist-analysis", "analysis")
          .dep(|s| s.name("dist-std"))
          .default(true)
          .run(move |s| dist::analysis(build, &s.compiler(), s.target));
@@ -509,12 +513,6 @@ pub fn build_rules(build: &Build) -> Rules {
 
     rules.verify();
     return rules;
-
-    fn dummy<'a>(s: &Step<'a>, build: &'a Build) -> Step<'a> {
-        s.name("dummy").stage(0)
-         .target(&build.config.build)
-         .host(&build.config.build)
-    }
 }
 
 #[derive(PartialEq, Eq, Hash, Clone, Debug)]
@@ -538,6 +536,10 @@ struct Step<'a> {
 }
 
 impl<'a> Step<'a> {
+    fn noop() -> Step<'a> {
+        Step { name: "", stage: 0, host: "", target: "" }
+    }
+
     /// Creates a new step which is the same as this, except has a new name.
     fn name(&self, name: &'a str) -> Step<'a> {
         Step { name: name, ..*self }
@@ -733,6 +735,9 @@ impl<'a> Rules<'a> {
                 if self.rules.contains_key(&dep.name) || dep.name.starts_with("default:") {
                     continue
                 }
+                if dep == Step::noop() {
+                    continue
+                }
                 panic!("\
 
 invalid rule dependency graph detected, was a rule added and maybe typo'd?
@@ -817,7 +822,16 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
             let hosts = if self.build.flags.host.len() > 0 {
                 &self.build.flags.host
             } else {
-                &self.build.config.host
+                if kind == Kind::Dist {
+                    // For 'dist' steps we only distribute artifacts built from
+                    // the build platform, so only consider that in the hosts
+                    // array.
+                    // NOTE: This relies on the fact that the build triple is
+                    // always placed first, as done in `config.rs`.
+                    &self.build.config.host[..1]
+                } else {
+                    &self.build.config.host
+                }
             };
             let targets = if self.build.flags.target.len() > 0 {
                 &self.build.flags.target
@@ -859,6 +873,7 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
         // of what we need to do.
         let mut order = Vec::new();
         let mut added = HashSet::new();
+        added.insert(Step::noop());
         for step in steps.iter().cloned() {
             self.fill(step, &mut order, &mut added);
         }
@@ -871,6 +886,10 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
 
         // And finally, iterate over everything and execute it.
         for step in order.iter() {
+            if self.build.flags.keep_stage.map_or(false, |s| step.stage <= s) {
+                self.build.verbose(&format!("keeping step {:?}", step));
+                continue;
+            }
             self.build.verbose(&format!("executing step {:?}", step));
             (self.rules[step.name].run)(step);
         }
diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs
index cb5b456a0f2..c9e756b6f99 100644
--- a/src/bootstrap/util.rs
+++ b/src/bootstrap/util.rs
@@ -41,6 +41,12 @@ pub fn mtime(path: &Path) -> FileTime {
 /// Copies a file from `src` to `dst`, attempting to use hard links and then
 /// falling back to an actually filesystem copy if necessary.
 pub fn copy(src: &Path, dst: &Path) {
+    // A call to `hard_link` will fail if `dst` exists, so remove it if it
+    // already exists so we can try to help `hard_link` succeed.
+    let _ = fs::remove_file(&dst);
+
+    // Attempt to "easy copy" by creating a hard link (symlinks don't work on
+    // windows), but if that fails just fall back to a slow `copy` operation.
     let res = fs::hard_link(src, dst);
     let res = res.or_else(|_| fs::copy(src, dst).map(|_| ()));
     if let Err(e) = res {