about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2017-01-05 04:01:35 +0000
committerbors <bors@rust-lang.org>2017-01-05 04:01:35 +0000
commit80745e2a2323d47756c2afdd0d2584094ce35604 (patch)
tree0b6b797e03b03c67a6a511d46d15b01975f5a58a /src
parent5d994d8b7e482e87467d4a521911477bd8284ce3 (diff)
parent1a040b36cb5c748b1e5f0ea0a97f7ec5a51ee48d (diff)
downloadrust-80745e2a2323d47756c2afdd0d2584094ce35604.tar.gz
rust-80745e2a2323d47756c2afdd0d2584094ce35604.zip
Auto merge of #38731 - alexcrichton:supafast-cross-dist, r=brson
rustbuild: Quickly `dist` cross-host compilers

This commit optimizes the compile time for creating tarballs of cross-host
compilers and as a proof of concept adds two to the standard Travis matrix. Much
of this commit is further refactoring and refining of the `step.rs` definitions
along with the interpretation of `--target` and `--host` flags. This has gotten
confusing enough that I've also added a small test suite to
`src/bootstrap/step.rs` to ensure what we're doing works and doesn't regress.

After this commit when you execute:

    ./x.py dist --host $MY_HOST --target $MY_HOST

the build system will compile two compilers. The first is for the build platform
and the second is for the host platform. This second compiler is then packaged
up and placed into `build/dist` and is ready to go. With a fully cached LLVM and
docker image I was able to create a cross-host compiler in around 20 minutes
locally.

Eventually we plan to add a whole litany of cross-host entries to the Travis
matrix, but for now we're just adding a few before we eat up all the extra
capacity.

cc #38531
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/Cargo.toml4
-rw-r--r--src/bootstrap/check.rs11
-rw-r--r--src/bootstrap/dist.rs7
-rw-r--r--src/bootstrap/doc.rs6
-rw-r--r--src/bootstrap/step.rs447
-rw-r--r--src/ci/docker/dist-arm-unknown-linux-gnueabi/Dockerfile30
-rw-r--r--src/ci/docker/dist-x86_64-unknown-freebsd/Dockerfile (renamed from src/ci/docker/x86_64-freebsd/Dockerfile)10
-rw-r--r--src/ci/docker/dist-x86_64-unknown-freebsd/build-toolchain.sh (renamed from src/ci/docker/x86_64-freebsd/build-toolchain.sh)2
8 files changed, 484 insertions, 33 deletions
diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml
index 35f8fb43f7b..1eda1608c47 100644
--- a/src/bootstrap/Cargo.toml
+++ b/src/bootstrap/Cargo.toml
@@ -6,18 +6,22 @@ version = "0.0.0"
 [lib]
 name = "bootstrap"
 path = "lib.rs"
+doctest = false
 
 [[bin]]
 name = "bootstrap"
 path = "bin/main.rs"
+test = false
 
 [[bin]]
 name = "rustc"
 path = "bin/rustc.rs"
+test = false
 
 [[bin]]
 name = "rustdoc"
 path = "bin/rustdoc.rs"
+test = false
 
 [dependencies]
 build_helper = { path = "../build_helper" }
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index 6db1afa54a6..f2fddf6e2ef 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -568,3 +568,14 @@ pub fn distcheck(build: &Build) {
                      .arg("check")
                      .current_dir(&dir));
 }
+
+/// Test the build system itself
+pub fn bootstrap(build: &Build) {
+    let mut cmd = Command::new(&build.cargo);
+    cmd.arg("test")
+       .current_dir(build.src.join("src/bootstrap"))
+       .env("CARGO_TARGET_DIR", build.out.join("bootstrap"))
+       .env("RUSTC", &build.rustc);
+    cmd.arg("--").args(&build.flags.cmd.test_args());
+    build.run(&mut cmd);
+}
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index 9b0c7a04d6b..ad851e448ea 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -354,14 +354,9 @@ pub fn analysis(build: &Build, compiler: &Compiler, target: &str) {
 }
 
 /// Creates the `rust-src` installer component and the plain source tarball
-pub fn rust_src(build: &Build, host: &str) {
+pub fn rust_src(build: &Build) {
     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/doc.rs b/src/bootstrap/doc.rs
index bbbf5cba8a1..42eae6d24f1 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -57,12 +57,12 @@ pub fn rustbook(build: &Build, target: &str, name: &str) {
 /// `STAMP` alongw ith providing the various header/footer HTML we've cutomized.
 ///
 /// In the end, this is just a glorified wrapper around rustdoc!
-pub fn standalone(build: &Build, stage: u32, target: &str) {
-    println!("Documenting stage{} standalone ({})", stage, target);
+pub fn standalone(build: &Build, target: &str) {
+    println!("Documenting standalone ({})", target);
     let out = build.doc_out(target);
     t!(fs::create_dir_all(&out));
 
-    let compiler = Compiler::new(stage, &build.config.build);
+    let compiler = Compiler::new(0, &build.config.build);
 
     let favicon = build.src.join("src/doc/favicon.inc");
     let footer = build.src.join("src/doc/footer.inc");
diff --git a/src/bootstrap/step.rs b/src/bootstrap/step.rs
index 6a81f759dc7..bf815a817ed 100644
--- a/src/bootstrap/step.rs
+++ b/src/bootstrap/step.rs
@@ -365,6 +365,8 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
 
         suite("check-rpass-full", "src/test/run-pass-fulldeps",
               "run-pass", "run-pass-fulldeps");
+        suite("check-rfail-full", "src/test/run-fail-fulldeps",
+              "run-fail", "run-fail-fulldeps");
         suite("check-cfail-full", "src/test/compile-fail-fulldeps",
               "compile-fail", "compile-fail-fulldeps");
         suite("check-rmake", "src/test/run-make", "run-make", "run-make");
@@ -459,6 +461,7 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
          .dep(|s| s.name("tool-tidy").stage(0))
          .default(true)
          .host(true)
+         .only_build(true)
          .run(move |s| check::tidy(build, s.target));
     rules.test("check-error-index", "src/tools/error_index_generator")
          .dep(|s| s.name("libstd"))
@@ -482,6 +485,12 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
          .dep(|s| s.name("libtest"))
          .run(move |s| check::android_copy_libs(build, &s.compiler(), s.target));
 
+    rules.test("check-bootstrap", "src/bootstrap")
+         .default(true)
+         .host(true)
+         .only_build(true)
+         .run(move |_| check::bootstrap(build));
+
     // ========================================================================
     // Build tools
     //
@@ -516,9 +525,14 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
          .default(build.config.docs)
          .run(move |s| doc::rustbook(build, s.target, "nomicon"));
     rules.doc("doc-standalone", "src/doc")
-         .dep(move |s| s.name("rustc").host(&build.config.build).target(&build.config.build))
+         .dep(move |s| {
+             s.name("rustc")
+              .host(&build.config.build)
+              .target(&build.config.build)
+              .stage(0)
+         })
          .default(build.config.docs)
-         .run(move |s| doc::standalone(build, s.stage, s.target));
+         .run(move |s| doc::standalone(build, s.target));
     rules.doc("doc-error-index", "src/tools/error_index_generator")
          .dep(move |s| s.name("tool-error-index").target(&build.config.build).stage(0))
          .dep(move |s| s.name("librustc-link").stage(0))
@@ -550,6 +564,7 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
     rules.dist("dist-rustc", "src/librustc")
          .dep(move |s| s.name("rustc").host(&build.config.build))
          .host(true)
+         .only_host_build(true)
          .default(true)
          .run(move |s| dist::rustc(build, s.stage, s.target));
     rules.dist("dist-std", "src/libstd")
@@ -564,9 +579,11 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
              }
          })
          .default(true)
+         .only_host_build(true)
          .run(move |s| dist::std(build, &s.compiler(), s.target));
     rules.dist("dist-mingw", "path/to/nowhere")
          .default(true)
+         .only_host_build(true)
          .run(move |s| {
              if s.target.contains("pc-windows-gnu") {
                  dist::mingw(build, s.target)
@@ -575,14 +592,18 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
     rules.dist("dist-src", "src")
          .default(true)
          .host(true)
-         .run(move |s| dist::rust_src(build, s.target));
+         .only_build(true)
+         .only_host_build(true)
+         .run(move |_| dist::rust_src(build));
     rules.dist("dist-docs", "src/doc")
          .default(true)
+         .only_host_build(true)
          .dep(|s| s.name("default:doc"))
          .run(move |s| dist::docs(build, s.stage, s.target));
     rules.dist("dist-analysis", "analysis")
          .dep(|s| s.name("dist-std"))
          .default(true)
+         .only_host_build(true)
          .run(move |s| dist::analysis(build, &s.compiler(), s.target));
     rules.dist("install", "src")
          .dep(|s| s.name("default:dist"))
@@ -671,6 +692,14 @@ struct Rule<'a> {
     /// only intended for compiler hosts and not for targets that are being
     /// generated.
     host: bool,
+
+    /// Whether this rule is only for steps where the host is the build triple,
+    /// not anything in hosts or targets.
+    only_host_build: bool,
+
+    /// Whether this rule is only for the build triple, not anything in hosts or
+    /// targets.
+    only_build: bool,
 }
 
 #[derive(PartialEq)]
@@ -692,6 +721,8 @@ impl<'a> Rule<'a> {
             kind: kind,
             default: false,
             host: false,
+            only_host_build: false,
+            only_build: false,
         }
     }
 }
@@ -727,6 +758,16 @@ impl<'a, 'b> RuleBuilder<'a, 'b> {
         self.rule.host = host;
         self
     }
+
+    fn only_build(&mut self, only_build: bool) -> &mut Self {
+        self.rule.only_build = only_build;
+        self
+    }
+
+    fn only_host_build(&mut self, only_host_build: bool) -> &mut Self {
+        self.rule.only_host_build = only_host_build;
+        self
+    }
 }
 
 impl<'a, 'b> Drop for RuleBuilder<'a, 'b> {
@@ -896,19 +937,12 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
                 path.ends_with(rule.path)
             })
         }).flat_map(|rule| {
-            let hosts = if self.build.flags.host.len() > 0 {
+            let hosts = if rule.only_host_build || rule.only_build {
+                &self.build.config.host[..1]
+            } else if self.build.flags.host.len() > 0 {
                 &self.build.flags.host
             } else {
-                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
-                }
+                &self.build.config.host
             };
             let targets = if self.build.flags.target.len() > 0 {
                 &self.build.flags.target
@@ -928,6 +962,8 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
                     &self.build.flags.host[..]
                 } else if self.build.flags.target.len() > 0 {
                     &[]
+                } else if rule.only_build {
+                    &self.build.config.host[..1]
                 } else {
                     &self.build.config.host[..]
                 }
@@ -955,12 +991,7 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
 
         // Using `steps` as the top-level targets, make a topological ordering
         // 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);
-        }
+        let order = self.expand(steps);
 
         // Print out what we're doing for debugging
         self.build.verbose("bootstrap build plan:");
@@ -979,6 +1010,18 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
         }
     }
 
+    /// From the top level targets `steps` generate a topological ordering of
+    /// all steps needed to run those steps.
+    fn expand(&self, steps: &[Step<'a>]) -> Vec<Step<'a>> {
+        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);
+        }
+        return order
+    }
+
     /// Performs topological sort of dependencies rooted at the `step`
     /// specified, pushing all results onto the `order` vector provided.
     ///
@@ -1015,3 +1058,367 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
         order.push(step);
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use std::env;
+
+    use Build;
+    use config::Config;
+    use flags::Flags;
+
+    macro_rules! a {
+        ($($a:expr),*) => (vec![$($a.to_string()),*])
+    }
+
+    fn build(args: &[&str],
+             extra_host: &[&str],
+             extra_target: &[&str]) -> Build {
+        let mut args = args.iter().map(|s| s.to_string()).collect::<Vec<_>>();
+        args.push("--build".to_string());
+        args.push("A".to_string());
+        let flags = Flags::parse(&args);
+
+        let mut config = Config::default();
+        config.docs = true;
+        config.build = "A".to_string();
+        config.host = vec![config.build.clone()];
+        config.host.extend(extra_host.iter().map(|s| s.to_string()));
+        config.target = config.host.clone();
+        config.target.extend(extra_target.iter().map(|s| s.to_string()));
+
+        let mut build = Build::new(flags, config);
+        let cwd = env::current_dir().unwrap();
+        build.crates.insert("std_shim".to_string(), ::Crate {
+            name: "std_shim".to_string(),
+            deps: Vec::new(),
+            path: cwd.join("src/std_shim"),
+            doc_step: "doc-std_shim".to_string(),
+            build_step: "build-crate-std_shim".to_string(),
+            test_step: "test-std_shim".to_string(),
+            bench_step: "bench-std_shim".to_string(),
+        });
+        build.crates.insert("test_shim".to_string(), ::Crate {
+            name: "test_shim".to_string(),
+            deps: Vec::new(),
+            path: cwd.join("src/test_shim"),
+            doc_step: "doc-test_shim".to_string(),
+            build_step: "build-crate-test_shim".to_string(),
+            test_step: "test-test_shim".to_string(),
+            bench_step: "bench-test_shim".to_string(),
+        });
+        build.crates.insert("rustc-main".to_string(), ::Crate {
+            name: "rustc-main".to_string(),
+            deps: Vec::new(),
+            path: cwd.join("src/rustc-main"),
+            doc_step: "doc-rustc-main".to_string(),
+            build_step: "build-crate-rustc-main".to_string(),
+            test_step: "test-rustc-main".to_string(),
+            bench_step: "bench-rustc-main".to_string(),
+        });
+        return build
+    }
+
+    #[test]
+    fn dist_baseline() {
+        let build = build(&["dist"], &[], &[]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+        assert!(plan.iter().all(|s| s.host == "A" ));
+        assert!(plan.iter().all(|s| s.target == "A" ));
+
+        let step = super::Step {
+            name: "",
+            stage: 2,
+            host: &build.config.build,
+            target: &build.config.build,
+        };
+
+        assert!(plan.contains(&step.name("dist-docs")));
+        assert!(plan.contains(&step.name("dist-mingw")));
+        assert!(plan.contains(&step.name("dist-rustc")));
+        assert!(plan.contains(&step.name("dist-std")));
+        assert!(plan.contains(&step.name("dist-src")));
+    }
+
+    #[test]
+    fn dist_with_targets() {
+        let build = build(&["dist"], &[], &["B"]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+        assert!(plan.iter().all(|s| s.host == "A" ));
+
+        let step = super::Step {
+            name: "",
+            stage: 2,
+            host: &build.config.build,
+            target: &build.config.build,
+        };
+
+        assert!(plan.contains(&step.name("dist-docs")));
+        assert!(plan.contains(&step.name("dist-mingw")));
+        assert!(plan.contains(&step.name("dist-rustc")));
+        assert!(plan.contains(&step.name("dist-std")));
+        assert!(plan.contains(&step.name("dist-src")));
+
+        assert!(plan.contains(&step.target("B").name("dist-docs")));
+        assert!(plan.contains(&step.target("B").name("dist-mingw")));
+        assert!(!plan.contains(&step.target("B").name("dist-rustc")));
+        assert!(plan.contains(&step.target("B").name("dist-std")));
+        assert!(!plan.contains(&step.target("B").name("dist-src")));
+    }
+
+    #[test]
+    fn dist_with_hosts() {
+        let build = build(&["dist"], &["B"], &[]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+
+        let step = super::Step {
+            name: "",
+            stage: 2,
+            host: &build.config.build,
+            target: &build.config.build,
+        };
+
+        assert!(!plan.iter().any(|s| s.host == "B"));
+
+        assert!(plan.contains(&step.name("dist-docs")));
+        assert!(plan.contains(&step.name("dist-mingw")));
+        assert!(plan.contains(&step.name("dist-rustc")));
+        assert!(plan.contains(&step.name("dist-std")));
+        assert!(plan.contains(&step.name("dist-src")));
+
+        assert!(plan.contains(&step.target("B").name("dist-docs")));
+        assert!(plan.contains(&step.target("B").name("dist-mingw")));
+        assert!(plan.contains(&step.target("B").name("dist-rustc")));
+        assert!(plan.contains(&step.target("B").name("dist-std")));
+        assert!(!plan.contains(&step.target("B").name("dist-src")));
+    }
+
+    #[test]
+    fn dist_with_targets_and_hosts() {
+        let build = build(&["dist"], &["B"], &["C"]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+
+        let step = super::Step {
+            name: "",
+            stage: 2,
+            host: &build.config.build,
+            target: &build.config.build,
+        };
+
+        assert!(!plan.iter().any(|s| s.host == "B"));
+        assert!(!plan.iter().any(|s| s.host == "C"));
+
+        assert!(plan.contains(&step.name("dist-docs")));
+        assert!(plan.contains(&step.name("dist-mingw")));
+        assert!(plan.contains(&step.name("dist-rustc")));
+        assert!(plan.contains(&step.name("dist-std")));
+        assert!(plan.contains(&step.name("dist-src")));
+
+        assert!(plan.contains(&step.target("B").name("dist-docs")));
+        assert!(plan.contains(&step.target("B").name("dist-mingw")));
+        assert!(plan.contains(&step.target("B").name("dist-rustc")));
+        assert!(plan.contains(&step.target("B").name("dist-std")));
+        assert!(!plan.contains(&step.target("B").name("dist-src")));
+
+        assert!(plan.contains(&step.target("C").name("dist-docs")));
+        assert!(plan.contains(&step.target("C").name("dist-mingw")));
+        assert!(!plan.contains(&step.target("C").name("dist-rustc")));
+        assert!(plan.contains(&step.target("C").name("dist-std")));
+        assert!(!plan.contains(&step.target("C").name("dist-src")));
+    }
+
+    #[test]
+    fn dist_target_with_target_flag() {
+        let build = build(&["dist", "--target=C"], &["B"], &["C"]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+
+        let step = super::Step {
+            name: "",
+            stage: 2,
+            host: &build.config.build,
+            target: &build.config.build,
+        };
+
+        assert!(!plan.iter().any(|s| s.target == "A"));
+        assert!(!plan.iter().any(|s| s.target == "B"));
+        assert!(!plan.iter().any(|s| s.host == "B"));
+        assert!(!plan.iter().any(|s| s.host == "C"));
+
+        assert!(plan.contains(&step.target("C").name("dist-docs")));
+        assert!(plan.contains(&step.target("C").name("dist-mingw")));
+        assert!(!plan.contains(&step.target("C").name("dist-rustc")));
+        assert!(plan.contains(&step.target("C").name("dist-std")));
+        assert!(!plan.contains(&step.target("C").name("dist-src")));
+    }
+
+    #[test]
+    fn dist_host_with_target_flag() {
+        let build = build(&["dist", "--host=B", "--target=B"], &["B"], &["C"]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+
+        let step = super::Step {
+            name: "",
+            stage: 2,
+            host: &build.config.build,
+            target: &build.config.build,
+        };
+
+        assert!(!plan.iter().any(|s| s.target == "A"));
+        assert!(!plan.iter().any(|s| s.target == "C"));
+        assert!(!plan.iter().any(|s| s.host == "B"));
+        assert!(!plan.iter().any(|s| s.host == "C"));
+
+        assert!(plan.contains(&step.target("B").name("dist-docs")));
+        assert!(plan.contains(&step.target("B").name("dist-mingw")));
+        assert!(plan.contains(&step.target("B").name("dist-rustc")));
+        assert!(plan.contains(&step.target("B").name("dist-std")));
+        assert!(plan.contains(&step.target("B").name("dist-src")));
+
+        let all = rules.expand(&plan);
+        println!("all rules: {:#?}", all);
+        assert!(!all.contains(&step.name("rustc")));
+        assert!(!all.contains(&step.name("build-crate-std_shim").stage(1)));
+    }
+
+    #[test]
+    fn build_default() {
+        let build = build(&["build"], &["B"], &["C"]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+
+        let step = super::Step {
+            name: "",
+            stage: 2,
+            host: &build.config.build,
+            target: &build.config.build,
+        };
+
+        // rustc built for all for of (A, B) x (A, B)
+        assert!(plan.contains(&step.name("librustc")));
+        assert!(plan.contains(&step.target("B").name("librustc")));
+        assert!(plan.contains(&step.host("B").target("A").name("librustc")));
+        assert!(plan.contains(&step.host("B").target("B").name("librustc")));
+
+        // rustc never built for C
+        assert!(!plan.iter().any(|s| {
+            s.name.contains("rustc") && (s.host == "C" || s.target == "C")
+        }));
+
+        // test built for everything
+        assert!(plan.contains(&step.name("libtest")));
+        assert!(plan.contains(&step.target("B").name("libtest")));
+        assert!(plan.contains(&step.host("B").target("A").name("libtest")));
+        assert!(plan.contains(&step.host("B").target("B").name("libtest")));
+        assert!(plan.contains(&step.host("A").target("C").name("libtest")));
+        assert!(plan.contains(&step.host("B").target("C").name("libtest")));
+
+        let all = rules.expand(&plan);
+        println!("all rules: {:#?}", all);
+        assert!(all.contains(&step.name("rustc")));
+        assert!(all.contains(&step.name("libstd")));
+    }
+
+    #[test]
+    fn build_filtered() {
+        let build = build(&["build", "--target=C"], &["B"], &["C"]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+
+        assert!(!plan.iter().any(|s| s.name.contains("rustc")));
+        assert!(plan.iter().all(|s| {
+            !s.name.contains("test_shim") || s.target == "C"
+        }));
+    }
+
+    #[test]
+    fn test_default() {
+        let build = build(&["test"], &[], &[]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+        assert!(plan.iter().all(|s| s.host == "A"));
+        assert!(plan.iter().all(|s| s.target == "A"));
+
+        assert!(plan.iter().any(|s| s.name.contains("-ui")));
+        assert!(plan.iter().any(|s| s.name.contains("cfail")));
+        assert!(plan.iter().any(|s| s.name.contains("cfail")));
+        assert!(plan.iter().any(|s| s.name.contains("cfail-full")));
+        assert!(plan.iter().any(|s| s.name.contains("codegen-units")));
+        assert!(plan.iter().any(|s| s.name.contains("debuginfo")));
+        assert!(plan.iter().any(|s| s.name.contains("docs")));
+        assert!(plan.iter().any(|s| s.name.contains("error-index")));
+        assert!(plan.iter().any(|s| s.name.contains("incremental")));
+        assert!(plan.iter().any(|s| s.name.contains("linkchecker")));
+        assert!(plan.iter().any(|s| s.name.contains("mir-opt")));
+        assert!(plan.iter().any(|s| s.name.contains("pfail")));
+        assert!(plan.iter().any(|s| s.name.contains("rfail")));
+        assert!(plan.iter().any(|s| s.name.contains("rfail-full")));
+        assert!(plan.iter().any(|s| s.name.contains("rmake")));
+        assert!(plan.iter().any(|s| s.name.contains("rpass")));
+        assert!(plan.iter().any(|s| s.name.contains("rpass-full")));
+        assert!(plan.iter().any(|s| s.name.contains("rustc-all")));
+        assert!(plan.iter().any(|s| s.name.contains("rustdoc")));
+        assert!(plan.iter().any(|s| s.name.contains("std-all")));
+        assert!(plan.iter().any(|s| s.name.contains("test-all")));
+        assert!(plan.iter().any(|s| s.name.contains("tidy")));
+        assert!(plan.iter().any(|s| s.name.contains("valgrind")));
+    }
+
+    #[test]
+    fn test_with_a_target() {
+        let build = build(&["test", "--target=C"], &[], &["C"]);
+        let rules = super::build_rules(&build);
+        let plan = rules.plan();
+        println!("rules: {:#?}", plan);
+        assert!(plan.iter().all(|s| s.stage == 2));
+        assert!(plan.iter().all(|s| s.host == "A"));
+        assert!(plan.iter().all(|s| s.target == "C"));
+
+        assert!(plan.iter().any(|s| s.name.contains("-ui")));
+        assert!(plan.iter().any(|s| s.name.contains("cfail")));
+        assert!(plan.iter().any(|s| s.name.contains("cfail")));
+        assert!(!plan.iter().any(|s| s.name.contains("cfail-full")));
+        assert!(plan.iter().any(|s| s.name.contains("codegen-units")));
+        assert!(plan.iter().any(|s| s.name.contains("debuginfo")));
+        assert!(!plan.iter().any(|s| s.name.contains("docs")));
+        assert!(!plan.iter().any(|s| s.name.contains("error-index")));
+        assert!(plan.iter().any(|s| s.name.contains("incremental")));
+        assert!(!plan.iter().any(|s| s.name.contains("linkchecker")));
+        assert!(plan.iter().any(|s| s.name.contains("mir-opt")));
+        assert!(plan.iter().any(|s| s.name.contains("pfail")));
+        assert!(plan.iter().any(|s| s.name.contains("rfail")));
+        assert!(!plan.iter().any(|s| s.name.contains("rfail-full")));
+        assert!(!plan.iter().any(|s| s.name.contains("rmake")));
+        assert!(plan.iter().any(|s| s.name.contains("rpass")));
+        assert!(!plan.iter().any(|s| s.name.contains("rpass-full")));
+        assert!(!plan.iter().any(|s| s.name.contains("rustc-all")));
+        assert!(!plan.iter().any(|s| s.name.contains("rustdoc")));
+        assert!(plan.iter().any(|s| s.name.contains("std-all")));
+        assert!(plan.iter().any(|s| s.name.contains("test-all")));
+        assert!(!plan.iter().any(|s| s.name.contains("tidy")));
+        assert!(plan.iter().any(|s| s.name.contains("valgrind")));
+    }
+}
diff --git a/src/ci/docker/dist-arm-unknown-linux-gnueabi/Dockerfile b/src/ci/docker/dist-arm-unknown-linux-gnueabi/Dockerfile
new file mode 100644
index 00000000000..9b0f1b7a0a7
--- /dev/null
+++ b/src/ci/docker/dist-arm-unknown-linux-gnueabi/Dockerfile
@@ -0,0 +1,30 @@
+FROM ubuntu:16.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+  g++ \
+  make \
+  file \
+  curl \
+  ca-certificates \
+  python2.7 \
+  git \
+  cmake \
+  sudo \
+  gdb \
+  xz-utils \
+  g++-arm-linux-gnueabi
+
+ENV SCCACHE_DIGEST=7237e38e029342fa27b7ac25412cb9d52554008b12389727320bd533fd7f05b6a96d55485f305caf95e5c8f5f97c3313e10012ccad3e752aba2518f3522ba783
+RUN curl -L https://api.pub.build.mozilla.org/tooltool/sha512/$SCCACHE_DIGEST | \
+      tar xJf - -C /usr/local/bin --strip-components=1
+
+RUN curl -OL https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64.deb && \
+    dpkg -i dumb-init_*.deb && \
+    rm dumb-init_*.deb
+ENTRYPOINT ["/usr/bin/dumb-init", "--"]
+
+ENV RUST_CONFIGURE_ARGS --host=arm-unknown-linux-gnueabi
+ENV XPY_RUN \
+      dist \
+      --host arm-unknown-linux-gnueabi \
+      --target arm-unknown-linux-gnueabi
diff --git a/src/ci/docker/x86_64-freebsd/Dockerfile b/src/ci/docker/dist-x86_64-unknown-freebsd/Dockerfile
index 86efa74ba3b..f1a6ccf9ebc 100644
--- a/src/ci/docker/x86_64-freebsd/Dockerfile
+++ b/src/ci/docker/dist-x86_64-unknown-freebsd/Dockerfile
@@ -28,7 +28,11 @@ RUN curl -L https://api.pub.build.mozilla.org/tooltool/sha512/$SCCACHE_DIGEST |
 
 ENV \
     AR_x86_64_unknown_freebsd=x86_64-unknown-freebsd10-ar \
-    CC_x86_64_unknown_freebsd=x86_64-unknown-freebsd10-gcc
+    CC_x86_64_unknown_freebsd=x86_64-unknown-freebsd10-gcc \
+    CXX_x86_64_unknown_freebsd=x86_64-unknown-freebsd10-g++
 
-ENV RUST_CONFIGURE_ARGS --target=x86_64-unknown-freebsd
-ENV RUST_CHECK_TARGET ""
+ENV RUST_CONFIGURE_ARGS --host=x86_64-unknown-freebsd
+ENV XPY_RUN \
+      dist \
+      --host x86_64-unknown-freebsd \
+      --target x86_64-unknown-freebsd
diff --git a/src/ci/docker/x86_64-freebsd/build-toolchain.sh b/src/ci/docker/dist-x86_64-unknown-freebsd/build-toolchain.sh
index d4bc886d50e..0fd6beaf4c1 100644
--- a/src/ci/docker/x86_64-freebsd/build-toolchain.sh
+++ b/src/ci/docker/dist-x86_64-unknown-freebsd/build-toolchain.sh
@@ -77,7 +77,7 @@ cd gcc-$GCC
 mkdir ../gcc-build
 cd ../gcc-build
 ../gcc-$GCC/configure                            \
-  --enable-languages=c                           \
+  --enable-languages=c,c++                       \
   --target=$ARCH-unknown-freebsd10               \
   --disable-multilib                             \
   --disable-nls                                  \