about summary refs log tree commit diff
path: root/src/bootstrap
diff options
context:
space:
mode:
authorManish Goregaokar <manishsmail@gmail.com>2020-07-17 14:09:11 -0700
committerGitHub <noreply@github.com>2020-07-17 14:09:11 -0700
commit7d4e6c0aca1365ac2adb6a9fa28f91dc15da32db (patch)
tree6a8d3dc2a7c0f96d062ae050b115cce2284bdfe0 /src/bootstrap
parent10b7eecbdd7cee00349dfc30b9e2dcf8b4f688af (diff)
parente2b337dc57b5309d19e25811d3de340cc7567285 (diff)
downloadrust-7d4e6c0aca1365ac2adb6a9fa28f91dc15da32db.tar.gz
rust-7d4e6c0aca1365ac2adb6a9fa28f91dc15da32db.zip
Rollup merge of #74251 - shepmaster:bootstrap-target-files, r=Mark-Simulacrum
Teach bootstrap about target files vs target triples

`rustc` allows passing in predefined target triples as well as JSON
target specification files. This change allows bootstrap to have the
first inkling about those differences. This allows building a
cross-compiler for an out-of-tree architecture (even though that
compiler won't work for other reasons).

Even if no one ever uses this functionality, I think the newtype
around the `Interned<String>` improves the readability of the code.
Diffstat (limited to 'src/bootstrap')
-rw-r--r--src/bootstrap/builder.rs61
-rw-r--r--src/bootstrap/builder/tests.rs56
-rw-r--r--src/bootstrap/cc_detect.rs34
-rw-r--r--src/bootstrap/check.rs18
-rw-r--r--src/bootstrap/clean.rs2
-rw-r--r--src/bootstrap/compile.rs45
-rw-r--r--src/bootstrap/config.rs84
-rw-r--r--src/bootstrap/dist.rs79
-rw-r--r--src/bootstrap/doc.rs30
-rw-r--r--src/bootstrap/flags.rs11
-rw-r--r--src/bootstrap/install.rs29
-rw-r--r--src/bootstrap/lib.rs106
-rw-r--r--src/bootstrap/native.rs39
-rw-r--r--src/bootstrap/sanity.rs6
-rw-r--r--src/bootstrap/test.rs69
-rw-r--r--src/bootstrap/tool.rs26
-rw-r--r--src/bootstrap/util.rs11
17 files changed, 390 insertions, 316 deletions
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index a1659826707..a8161c9bfb8 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -16,6 +16,7 @@ use build_helper::{output, t};
 use crate::cache::{Cache, Interned, INTERNER};
 use crate::check;
 use crate::compile;
+use crate::config::TargetSelection;
 use crate::dist;
 use crate::doc;
 use crate::flags::Subcommand;
@@ -86,8 +87,8 @@ pub trait Step: 'static + Clone + Debug + PartialEq + Eq + Hash {
 
 pub struct RunConfig<'a> {
     pub builder: &'a Builder<'a>,
-    pub host: Interned<String>,
-    pub target: Interned<String>,
+    pub host: TargetSelection,
+    pub target: TargetSelection,
     pub path: PathBuf,
 }
 
@@ -576,7 +577,7 @@ impl<'a> Builder<'a> {
     /// not take `Compiler` since all `Compiler` instances are meant to be
     /// obtained through this function, since it ensures that they are valid
     /// (i.e., built and assembled).
-    pub fn compiler(&self, stage: u32, host: Interned<String>) -> Compiler {
+    pub fn compiler(&self, stage: u32, host: TargetSelection) -> Compiler {
         self.ensure(compile::Assemble { target_compiler: Compiler { stage, host } })
     }
 
@@ -594,8 +595,8 @@ impl<'a> Builder<'a> {
     pub fn compiler_for(
         &self,
         stage: u32,
-        host: Interned<String>,
-        target: Interned<String>,
+        host: TargetSelection,
+        target: TargetSelection,
     ) -> Compiler {
         if self.build.force_use_stage1(Compiler { stage, host }, target) {
             self.compiler(1, self.config.build)
@@ -610,15 +611,11 @@ impl<'a> Builder<'a> {
 
     /// Returns the libdir where the standard library and other artifacts are
     /// found for a compiler's sysroot.
-    pub fn sysroot_libdir(
-        &self,
-        compiler: Compiler,
-        target: Interned<String>,
-    ) -> Interned<PathBuf> {
+    pub fn sysroot_libdir(&self, compiler: Compiler, target: TargetSelection) -> Interned<PathBuf> {
         #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
         struct Libdir {
             compiler: Compiler,
-            target: Interned<String>,
+            target: TargetSelection,
         }
         impl Step for Libdir {
             type Output = Interned<PathBuf>;
@@ -633,7 +630,7 @@ impl<'a> Builder<'a> {
                     .sysroot(self.compiler)
                     .join(lib)
                     .join("rustlib")
-                    .join(self.target)
+                    .join(self.target.triple)
                     .join("lib");
                 let _ = fs::remove_dir_all(&sysroot);
                 t!(fs::create_dir_all(&sysroot));
@@ -656,7 +653,7 @@ impl<'a> Builder<'a> {
                 Some(relative_libdir) if compiler.stage >= 1 => {
                     self.sysroot(compiler).join(relative_libdir)
                 }
-                _ => self.sysroot(compiler).join(libdir(&compiler.host)),
+                _ => self.sysroot(compiler).join(libdir(compiler.host)),
             }
         }
     }
@@ -668,11 +665,11 @@ impl<'a> Builder<'a> {
     /// Windows.
     pub fn libdir_relative(&self, compiler: Compiler) -> &Path {
         if compiler.is_snapshot(self) {
-            libdir(&self.config.build).as_ref()
+            libdir(self.config.build).as_ref()
         } else {
             match self.config.libdir_relative() {
                 Some(relative_libdir) if compiler.stage >= 1 => relative_libdir,
-                _ => libdir(&compiler.host).as_ref(),
+                _ => libdir(compiler.host).as_ref(),
             }
         }
     }
@@ -707,7 +704,7 @@ impl<'a> Builder<'a> {
         if compiler.is_snapshot(self) {
             self.initial_rustc.clone()
         } else {
-            self.sysroot(compiler).join("bin").join(exe("rustc", &compiler.host))
+            self.sysroot(compiler).join("bin").join(exe("rustc", compiler.host))
         }
     }
 
@@ -745,7 +742,7 @@ impl<'a> Builder<'a> {
     ///
     /// Note that this returns `None` if LLVM is disabled, or if we're in a
     /// check build or dry-run, where there's no need to build all of LLVM.
-    fn llvm_config(&self, target: Interned<String>) -> Option<PathBuf> {
+    fn llvm_config(&self, target: TargetSelection) -> Option<PathBuf> {
         if self.config.llvm_enabled() && self.kind != Kind::Check && !self.config.dry_run {
             let llvm_config = self.ensure(native::Llvm { target });
             if llvm_config.is_file() {
@@ -767,7 +764,7 @@ impl<'a> Builder<'a> {
         compiler: Compiler,
         mode: Mode,
         source_type: SourceType,
-        target: Interned<String>,
+        target: TargetSelection,
         cmd: &str,
     ) -> Cargo {
         let mut cargo = Command::new(&self.initial_cargo);
@@ -777,7 +774,7 @@ impl<'a> Builder<'a> {
             let my_out = match mode {
                 // This is the intended out directory for compiler documentation.
                 Mode::Rustc | Mode::ToolRustc | Mode::Codegen => self.compiler_doc_out(target),
-                Mode::Std => out_dir.join(target).join("doc"),
+                Mode::Std => out_dir.join(target.triple).join("doc"),
                 _ => panic!("doc mode {:?} not expected", mode),
             };
             let rustdoc = self.rustdoc(compiler);
@@ -799,7 +796,7 @@ impl<'a> Builder<'a> {
         }
 
         if cmd != "install" {
-            cargo.arg("--target").arg(target);
+            cargo.arg("--target").arg(target.rustc_target_arg());
         } else {
             assert_eq!(target, compiler.host);
         }
@@ -825,7 +822,7 @@ impl<'a> Builder<'a> {
             compiler.stage
         };
 
-        let mut rustflags = Rustflags::new(&target);
+        let mut rustflags = Rustflags::new(target);
         if stage != 0 {
             if let Ok(s) = env::var("CARGOFLAGS_NOT_BOOTSTRAP") {
                 cargo.args(s.split_whitespace());
@@ -998,7 +995,7 @@ impl<'a> Builder<'a> {
         // argument manually via `-C link-args=-Wl,-rpath,...`. Plus isn't it
         // fun to pass a flag to a tool to pass a flag to pass a flag to a tool
         // to change a flag in a binary?
-        if self.config.rust_rpath && util::use_host_linker(&target) {
+        if self.config.rust_rpath && util::use_host_linker(target) {
             let rpath = if target.contains("apple") {
                 // Note that we need to take one extra step on macOS to also pass
                 // `-Wl,-instal_name,@rpath/...` to get things to work right. To
@@ -1026,7 +1023,7 @@ impl<'a> Builder<'a> {
         }
 
         if let Some(target_linker) = self.linker(target, can_use_lld) {
-            let target = crate::envify(&target);
+            let target = crate::envify(&target.triple);
             cargo.env(&format!("CARGO_TARGET_{}_LINKER", target), target_linker);
         }
         if !(["build", "check", "clippy", "fix", "rustc"].contains(&cmd)) && want_rustdoc {
@@ -1200,21 +1197,23 @@ impl<'a> Builder<'a> {
                 }
             };
             let cc = ccacheify(&self.cc(target));
-            cargo.env(format!("CC_{}", target), &cc);
+            cargo.env(format!("CC_{}", target.triple), &cc);
 
             let cflags = self.cflags(target, GitRepo::Rustc).join(" ");
-            cargo.env(format!("CFLAGS_{}", target), cflags.clone());
+            cargo.env(format!("CFLAGS_{}", target.triple), cflags.clone());
 
             if let Some(ar) = self.ar(target) {
                 let ranlib = format!("{} s", ar.display());
-                cargo.env(format!("AR_{}", target), ar).env(format!("RANLIB_{}", target), ranlib);
+                cargo
+                    .env(format!("AR_{}", target.triple), ar)
+                    .env(format!("RANLIB_{}", target.triple), ranlib);
             }
 
             if let Ok(cxx) = self.cxx(target) {
                 let cxx = ccacheify(&cxx);
                 cargo
-                    .env(format!("CXX_{}", target), &cxx)
-                    .env(format!("CXXFLAGS_{}", target), cflags);
+                    .env(format!("CXX_{}", target.triple), &cxx)
+                    .env(format!("CXXFLAGS_{}", target.triple), cflags);
             }
         }
 
@@ -1248,7 +1247,7 @@ impl<'a> Builder<'a> {
         // Environment variables *required* throughout the build
         //
         // FIXME: should update code to not require this env var
-        cargo.env("CFG_COMPILER_HOST_TRIPLE", target);
+        cargo.env("CFG_COMPILER_HOST_TRIPLE", target.triple);
 
         // Set this for all builds to make sure doc builds also get it.
         cargo.env("CFG_RELEASE_CHANNEL", &self.config.channel);
@@ -1404,7 +1403,7 @@ mod tests;
 struct Rustflags(String);
 
 impl Rustflags {
-    fn new(target: &str) -> Rustflags {
+    fn new(target: TargetSelection) -> Rustflags {
         let mut ret = Rustflags(String::new());
 
         // Inherit `RUSTFLAGS` by default ...
@@ -1412,7 +1411,7 @@ impl Rustflags {
 
         // ... and also handle target-specific env RUSTFLAGS if they're
         // configured.
-        let target_specific = format!("CARGO_TARGET_{}_RUSTFLAGS", crate::envify(target));
+        let target_specific = format!("CARGO_TARGET_{}_RUSTFLAGS", crate::envify(&target.triple));
         ret.env(&target_specific);
 
         ret
diff --git a/src/bootstrap/builder/tests.rs b/src/bootstrap/builder/tests.rs
index 1e75e67af0c..69a54bec33b 100644
--- a/src/bootstrap/builder/tests.rs
+++ b/src/bootstrap/builder/tests.rs
@@ -1,5 +1,5 @@
 use super::*;
-use crate::config::Config;
+use crate::config::{Config, TargetSelection};
 use std::thread;
 
 use pretty_assertions::assert_eq;
@@ -17,16 +17,16 @@ fn configure(host: &[&str], target: &[&str]) -> Config {
         .join(&thread::current().name().unwrap_or("unknown").replace(":", "-"));
     t!(fs::create_dir_all(&dir));
     config.out = dir;
-    config.build = INTERNER.intern_str("A");
+    config.build = TargetSelection::from_user("A");
     config.hosts = vec![config.build]
         .into_iter()
-        .chain(host.iter().map(|s| INTERNER.intern_str(s)))
+        .chain(host.iter().map(|s| TargetSelection::from_user(s)))
         .collect::<Vec<_>>();
     config.targets = config
         .hosts
         .clone()
         .into_iter()
-        .chain(target.iter().map(|s| INTERNER.intern_str(s)))
+        .chain(target.iter().map(|s| TargetSelection::from_user(s)))
         .collect::<Vec<_>>();
     config
 }
@@ -41,7 +41,7 @@ fn dist_baseline() {
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]);
 
-    let a = INTERNER.intern_str("A");
+    let a = TargetSelection::from_user("A");
 
     assert_eq!(first(builder.cache.all::<dist::Docs>()), &[dist::Docs { host: a },]);
     assert_eq!(first(builder.cache.all::<dist::Mingw>()), &[dist::Mingw { host: a },]);
@@ -67,8 +67,8 @@ fn dist_with_targets() {
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]);
 
-    let a = INTERNER.intern_str("A");
-    let b = INTERNER.intern_str("B");
+    let a = TargetSelection::from_user("A");
+    let b = TargetSelection::from_user("B");
 
     assert_eq!(
         first(builder.cache.all::<dist::Docs>()),
@@ -98,8 +98,8 @@ fn dist_with_hosts() {
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]);
 
-    let a = INTERNER.intern_str("A");
-    let b = INTERNER.intern_str("B");
+    let a = TargetSelection::from_user("A");
+    let b = TargetSelection::from_user("B");
 
     assert_eq!(
         first(builder.cache.all::<dist::Docs>()),
@@ -128,8 +128,8 @@ fn dist_with_hosts() {
 
 #[test]
 fn dist_only_cross_host() {
-    let a = INTERNER.intern_str("A");
-    let b = INTERNER.intern_str("B");
+    let a = TargetSelection::from_user("A");
+    let b = TargetSelection::from_user("B");
     let mut build = Build::new(configure(&["B"], &[]));
     build.config.docs = false;
     build.config.extended = true;
@@ -156,9 +156,9 @@ fn dist_with_targets_and_hosts() {
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]);
 
-    let a = INTERNER.intern_str("A");
-    let b = INTERNER.intern_str("B");
-    let c = INTERNER.intern_str("C");
+    let a = TargetSelection::from_user("A");
+    let b = TargetSelection::from_user("B");
+    let c = TargetSelection::from_user("C");
 
     assert_eq!(
         first(builder.cache.all::<dist::Docs>()),
@@ -194,9 +194,9 @@ fn dist_with_target_flag() {
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]);
 
-    let a = INTERNER.intern_str("A");
-    let b = INTERNER.intern_str("B");
-    let c = INTERNER.intern_str("C");
+    let a = TargetSelection::from_user("A");
+    let b = TargetSelection::from_user("B");
+    let c = TargetSelection::from_user("C");
 
     assert_eq!(
         first(builder.cache.all::<dist::Docs>()),
@@ -224,8 +224,8 @@ fn dist_with_same_targets_and_hosts() {
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]);
 
-    let a = INTERNER.intern_str("A");
-    let b = INTERNER.intern_str("B");
+    let a = TargetSelection::from_user("A");
+    let b = TargetSelection::from_user("B");
 
     assert_eq!(
         first(builder.cache.all::<dist::Docs>()),
@@ -277,9 +277,9 @@ fn build_default() {
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Build), &[]);
 
-    let a = INTERNER.intern_str("A");
-    let b = INTERNER.intern_str("B");
-    let c = INTERNER.intern_str("C");
+    let a = TargetSelection::from_user("A");
+    let b = TargetSelection::from_user("B");
+    let c = TargetSelection::from_user("C");
 
     assert_eq!(
         first(builder.cache.all::<compile::Std>()),
@@ -318,9 +318,9 @@ fn build_with_target_flag() {
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Build), &[]);
 
-    let a = INTERNER.intern_str("A");
-    let b = INTERNER.intern_str("B");
-    let c = INTERNER.intern_str("C");
+    let a = TargetSelection::from_user("A");
+    let b = TargetSelection::from_user("B");
+    let c = TargetSelection::from_user("C");
 
     assert_eq!(
         first(builder.cache.all::<compile::Std>()),
@@ -374,7 +374,7 @@ fn test_with_no_doc_stage0() {
     let build = Build::new(config);
     let mut builder = Builder::new(&build);
 
-    let host = INTERNER.intern_str("A");
+    let host = TargetSelection::from_user("A");
 
     builder
         .run_step_descriptions(&[StepDescription::from::<test::Crate>()], &["src/libstd".into()]);
@@ -428,7 +428,7 @@ fn doc_default() {
     let build = Build::new(config);
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Doc), &[]);
-    let a = INTERNER.intern_str("A");
+    let a = TargetSelection::from_user("A");
 
     // error_index_generator uses stage 1 to share rustdoc artifacts with the
     // rustdoc tool.
@@ -466,7 +466,7 @@ fn test_docs() {
     let build = Build::new(config);
     let mut builder = Builder::new(&build);
     builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Test), &[]);
-    let a = INTERNER.intern_str("A");
+    let a = TargetSelection::from_user("A");
 
     // error_index_generator uses stage 1 to share rustdoc artifacts with the
     // rustdoc tool.
diff --git a/src/bootstrap/cc_detect.rs b/src/bootstrap/cc_detect.rs
index ab16ca3732c..7ff00d85dd2 100644
--- a/src/bootstrap/cc_detect.rs
+++ b/src/bootstrap/cc_detect.rs
@@ -28,16 +28,15 @@ use std::{env, iter};
 
 use build_helper::output;
 
-use crate::cache::Interned;
-use crate::config::Target;
+use crate::config::{Target, TargetSelection};
 use crate::{Build, GitRepo};
 
 // The `cc` crate doesn't provide a way to obtain a path to the detected archiver,
 // so use some simplified logic here. First we respect the environment variable `AR`, then
 // try to infer the archiver path from the C compiler path.
 // In the future this logic should be replaced by calling into the `cc` crate.
-fn cc2ar(cc: &Path, target: &str) -> Option<PathBuf> {
-    if let Some(ar) = env::var_os(format!("AR_{}", target.replace("-", "_"))) {
+fn cc2ar(cc: &Path, target: TargetSelection) -> Option<PathBuf> {
+    if let Some(ar) = env::var_os(format!("AR_{}", target.triple.replace("-", "_"))) {
         Some(PathBuf::from(ar))
     } else if let Some(ar) = env::var_os("AR") {
         Some(PathBuf::from(ar))
@@ -79,8 +78,8 @@ pub fn find(build: &mut Build) {
             .opt_level(2)
             .warnings(false)
             .debug(false)
-            .target(&target)
-            .host(&build.build);
+            .target(&target.triple)
+            .host(&build.build.triple);
         match build.crt_static(target) {
             Some(a) => {
                 cfg.static_crt(a);
@@ -106,10 +105,10 @@ pub fn find(build: &mut Build) {
         let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) {
             ar
         } else {
-            cc2ar(compiler.path(), &target)
+            cc2ar(compiler.path(), target)
         };
 
-        build.cc.insert(target, compiler);
+        build.cc.insert(target, compiler.clone());
         let cflags = build.cflags(target, GitRepo::Rustc);
 
         // If we use llvm-libunwind, we will need a C++ compiler as well for all targets
@@ -120,8 +119,8 @@ pub fn find(build: &mut Build) {
             .warnings(false)
             .debug(false)
             .cpp(true)
-            .target(&target)
-            .host(&build.build);
+            .target(&target.triple)
+            .host(&build.build.triple);
 
         let cxx_configured = if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) {
             cfg.compiler(cxx);
@@ -138,14 +137,14 @@ pub fn find(build: &mut Build) {
             build.cxx.insert(target, compiler);
         }
 
-        build.verbose(&format!("CC_{} = {:?}", &target, build.cc(target)));
-        build.verbose(&format!("CFLAGS_{} = {:?}", &target, cflags));
+        build.verbose(&format!("CC_{} = {:?}", &target.triple, build.cc(target)));
+        build.verbose(&format!("CFLAGS_{} = {:?}", &target.triple, cflags));
         if let Ok(cxx) = build.cxx(target) {
-            build.verbose(&format!("CXX_{} = {:?}", &target, cxx));
-            build.verbose(&format!("CXXFLAGS_{} = {:?}", &target, cflags));
+            build.verbose(&format!("CXX_{} = {:?}", &target.triple, cxx));
+            build.verbose(&format!("CXXFLAGS_{} = {:?}", &target.triple, cflags));
         }
         if let Some(ar) = ar {
-            build.verbose(&format!("AR_{} = {:?}", &target, ar));
+            build.verbose(&format!("AR_{} = {:?}", &target.triple, ar));
             build.ar.insert(target, ar);
         }
     }
@@ -154,17 +153,18 @@ pub fn find(build: &mut Build) {
 fn set_compiler(
     cfg: &mut cc::Build,
     compiler: Language,
-    target: Interned<String>,
+    target: TargetSelection,
     config: Option<&Target>,
     build: &Build,
 ) {
-    match &*target {
+    match &*target.triple {
         // When compiling for android we may have the NDK configured in the
         // config.toml in which case we look there. Otherwise the default
         // compiler already takes into account the triple in question.
         t if t.contains("android") => {
             if let Some(ndk) = config.and_then(|c| c.ndk.as_ref()) {
                 let target = target
+                    .triple
                     .replace("armv7neon", "arm")
                     .replace("armv7", "arm")
                     .replace("thumbv7neon", "arm")
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index 0d38d2eebe7..9f34bb4e6cc 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -1,15 +1,15 @@
 //! Implementation of compiling the compiler and standard library, in "check"-based modes.
 
 use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step};
-use crate::cache::Interned;
 use crate::compile::{add_to_sysroot, run_cargo, rustc_cargo, std_cargo};
+use crate::config::TargetSelection;
 use crate::tool::{prepare_tool_cargo, SourceType};
 use crate::{Compiler, Mode};
 use std::path::PathBuf;
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Std {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 fn args(kind: Kind) -> Vec<String> {
@@ -71,7 +71,7 @@ impl Step for Std {
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Rustc {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Rustc {
@@ -127,7 +127,7 @@ macro_rules! tool_check_step {
     ($name:ident, $path:expr, $source_type:expr) => {
         #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
         pub struct $name {
-            pub target: Interned<String>,
+            pub target: TargetSelection,
         }
 
         impl Step for $name {
@@ -163,8 +163,8 @@ macro_rules! tool_check_step {
                 println!(
                     "Checking {} artifacts ({} -> {})",
                     stringify!($name).to_lowercase(),
-                    &compiler.host,
-                    target
+                    &compiler.host.triple,
+                    target.triple
                 );
                 run_cargo(
                     builder,
@@ -184,7 +184,7 @@ macro_rules! tool_check_step {
                 fn stamp(
                     builder: &Builder<'_>,
                     compiler: Compiler,
-                    target: Interned<String>,
+                    target: TargetSelection,
                 ) -> PathBuf {
                     builder
                         .cargo_out(compiler, Mode::ToolRustc, target)
@@ -204,12 +204,12 @@ tool_check_step!(Clippy, "src/tools/clippy", SourceType::InTree);
 
 /// Cargo's output path for the standard library in a given stage, compiled
 /// by a particular compiler for the specified target.
-fn libstd_stamp(builder: &Builder<'_>, compiler: Compiler, target: Interned<String>) -> PathBuf {
+fn libstd_stamp(builder: &Builder<'_>, compiler: Compiler, target: TargetSelection) -> PathBuf {
     builder.cargo_out(compiler, Mode::Std, target).join(".libstd-check.stamp")
 }
 
 /// Cargo's output path for librustc in a given stage, compiled by a particular
 /// compiler for the specified target.
-fn librustc_stamp(builder: &Builder<'_>, compiler: Compiler, target: Interned<String>) -> PathBuf {
+fn librustc_stamp(builder: &Builder<'_>, compiler: Compiler, target: TargetSelection) -> PathBuf {
     builder.cargo_out(compiler, Mode::Rustc, target).join(".librustc-check.stamp")
 }
diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs
index b4e58c06fde..f83dfe8e635 100644
--- a/src/bootstrap/clean.rs
+++ b/src/bootstrap/clean.rs
@@ -23,7 +23,7 @@ pub fn clean(build: &Build, all: bool) {
         rm_rf(&build.out.join("dist"));
 
         for host in &build.hosts {
-            let entries = match build.out.join(host).read_dir() {
+            let entries = match build.out.join(host.triple).read_dir() {
                 Ok(iter) => iter,
                 Err(_) => continue,
             };
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index 68a3f369d16..89b070e15e2 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -22,6 +22,7 @@ use serde::Deserialize;
 use crate::builder::Cargo;
 use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::{Interned, INTERNER};
+use crate::config::TargetSelection;
 use crate::dist;
 use crate::native;
 use crate::tool::SourceType;
@@ -30,7 +31,7 @@ use crate::{Compiler, DependencyType, GitRepo, Mode};
 
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Std {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
     pub compiler: Compiler,
 }
 
@@ -129,7 +130,7 @@ fn copy_and_stamp(
 fn copy_third_party_objects(
     builder: &Builder<'_>,
     compiler: &Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
 ) -> Vec<(PathBuf, DependencyType)> {
     let mut target_deps = vec![];
 
@@ -157,7 +158,7 @@ fn copy_third_party_objects(
 fn copy_self_contained_objects(
     builder: &Builder<'_>,
     compiler: &Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
 ) -> Vec<(PathBuf, DependencyType)> {
     let libdir_self_contained = builder.sysroot_libdir(*compiler, target).join("self-contained");
     t!(fs::create_dir_all(&libdir_self_contained));
@@ -206,7 +207,7 @@ fn copy_self_contained_objects(
 
 /// Configure cargo to compile the standard library, adding appropriate env vars
 /// and such.
-pub fn std_cargo(builder: &Builder<'_>, target: Interned<String>, stage: u32, cargo: &mut Cargo) {
+pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, stage: u32, cargo: &mut Cargo) {
     if let Some(target) = env::var_os("MACOSX_STD_DEPLOYMENT_TARGET") {
         cargo.env("MACOSX_DEPLOYMENT_TARGET", target);
     }
@@ -301,7 +302,7 @@ pub fn std_cargo(builder: &Builder<'_>, target: Interned<String>, stage: u32, ca
 struct StdLink {
     pub compiler: Compiler,
     pub target_compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for StdLink {
@@ -337,7 +338,7 @@ impl Step for StdLink {
 fn copy_sanitizers(
     builder: &Builder<'_>,
     compiler: &Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
 ) -> Vec<PathBuf> {
     let runtimes: Vec<native::SanitizerRuntime> = builder.ensure(native::Sanitizers { target });
 
@@ -372,7 +373,7 @@ fn copy_sanitizers(
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct StartupObjects {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for StartupObjects {
@@ -419,7 +420,7 @@ impl Step for StartupObjects {
                         .arg("--cfg")
                         .arg("bootstrap")
                         .arg("--target")
-                        .arg(target)
+                        .arg(target.rustc_target_arg())
                         .arg("--emit=obj")
                         .arg("-o")
                         .arg(dst_file)
@@ -438,7 +439,7 @@ impl Step for StartupObjects {
 
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Rustc {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
     pub compiler: Compiler,
 }
 
@@ -518,7 +519,7 @@ impl Step for Rustc {
     }
 }
 
-pub fn rustc_cargo(builder: &Builder<'_>, cargo: &mut Cargo, target: Interned<String>) {
+pub fn rustc_cargo(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelection) {
     cargo
         .arg("--features")
         .arg(builder.rustc_features())
@@ -527,7 +528,7 @@ pub fn rustc_cargo(builder: &Builder<'_>, cargo: &mut Cargo, target: Interned<St
     rustc_cargo_env(builder, cargo, target);
 }
 
-pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: Interned<String>) {
+pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelection) {
     // Set some configuration variables picked up by build scripts and
     // the compiler alike
     cargo
@@ -608,7 +609,7 @@ pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: Interne
 struct RustcLink {
     pub compiler: Compiler,
     pub target_compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for RustcLink {
@@ -638,11 +639,7 @@ impl Step for RustcLink {
 
 /// Cargo's output path for the standard library in a given stage, compiled
 /// by a particular compiler for the specified target.
-pub fn libstd_stamp(
-    builder: &Builder<'_>,
-    compiler: Compiler,
-    target: Interned<String>,
-) -> PathBuf {
+pub fn libstd_stamp(builder: &Builder<'_>, compiler: Compiler, target: TargetSelection) -> PathBuf {
     builder.cargo_out(compiler, Mode::Std, target).join(".libstd.stamp")
 }
 
@@ -651,7 +648,7 @@ pub fn libstd_stamp(
 pub fn librustc_stamp(
     builder: &Builder<'_>,
     compiler: Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
 ) -> PathBuf {
     builder.cargo_out(compiler, Mode::Rustc, target).join(".librustc.stamp")
 }
@@ -659,7 +656,7 @@ pub fn librustc_stamp(
 pub fn compiler_file(
     builder: &Builder<'_>,
     compiler: &Path,
-    target: Interned<String>,
+    target: TargetSelection,
     file: &str,
 ) -> PathBuf {
     let mut cmd = Command::new(compiler);
@@ -690,9 +687,9 @@ impl Step for Sysroot {
     fn run(self, builder: &Builder<'_>) -> Interned<PathBuf> {
         let compiler = self.compiler;
         let sysroot = if compiler.stage == 0 {
-            builder.out.join(&compiler.host).join("stage0-sysroot")
+            builder.out.join(&compiler.host.triple).join("stage0-sysroot")
         } else {
-            builder.out.join(&compiler.host).join(format!("stage{}", compiler.stage))
+            builder.out.join(&compiler.host.triple).join(format!("stage{}", compiler.stage))
         };
         let _ = fs::remove_dir_all(&sysroot);
         t!(fs::create_dir_all(&sysroot));
@@ -806,8 +803,8 @@ impl Step for Assemble {
 
         let libdir = builder.sysroot_libdir(target_compiler, target_compiler.host);
         if let Some(lld_install) = lld_install {
-            let src_exe = exe("lld", &target_compiler.host);
-            let dst_exe = exe("rust-lld", &target_compiler.host);
+            let src_exe = exe("lld", target_compiler.host);
+            let dst_exe = exe("rust-lld", target_compiler.host);
             // we prepend this bin directory to the user PATH when linking Rust binaries. To
             // avoid shadowing the system LLD we rename the LLD we provide to `rust-lld`.
             let dst = libdir.parent().unwrap().join("bin");
@@ -822,7 +819,7 @@ impl Step for Assemble {
 
         // Link the compiler binary itself into place
         let out_dir = builder.cargo_out(build_compiler, Mode::Rustc, host);
-        let rustc = out_dir.join(exe("rustc_binary", &*host));
+        let rustc = out_dir.join(exe("rustc_binary", host));
         let bindir = sysroot.join("bin");
         t!(fs::create_dir_all(&bindir));
         let compiler = builder.rustc(target_compiler);
diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs
index ff545a6ddcf..d71f3170420 100644
--- a/src/bootstrap/config.rs
+++ b/src/bootstrap/config.rs
@@ -7,6 +7,7 @@ use std::cmp;
 use std::collections::{HashMap, HashSet};
 use std::env;
 use std::ffi::OsString;
+use std::fmt;
 use std::fs;
 use std::path::{Path, PathBuf};
 use std::process;
@@ -39,7 +40,7 @@ pub struct Config {
     pub docs: bool,
     pub locked_deps: bool,
     pub vendor: bool,
-    pub target_config: HashMap<Interned<String>, Target>,
+    pub target_config: HashMap<TargetSelection, Target>,
     pub full_bootstrap: bool,
     pub extended: bool,
     pub tools: Option<HashSet<String>>,
@@ -112,9 +113,9 @@ pub struct Config {
     pub rust_thin_lto_import_instr_limit: Option<u32>,
     pub rust_remap_debuginfo: bool,
 
-    pub build: Interned<String>,
-    pub hosts: Vec<Interned<String>>,
-    pub targets: Vec<Interned<String>>,
+    pub build: TargetSelection,
+    pub hosts: Vec<TargetSelection>,
+    pub targets: Vec<TargetSelection>,
     pub local_rebuild: bool,
     pub jemalloc: bool,
     pub control_flow_guard: bool,
@@ -158,6 +159,67 @@ pub struct Config {
     pub out: PathBuf,
 }
 
+#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct TargetSelection {
+    pub triple: Interned<String>,
+    file: Option<Interned<String>>,
+}
+
+impl TargetSelection {
+    pub fn from_user(selection: &str) -> Self {
+        let path = Path::new(selection);
+
+        let (triple, file) = if path.exists() {
+            let triple = path
+                .file_stem()
+                .expect("Target specification file has no file stem")
+                .to_str()
+                .expect("Target specification file stem is not UTF-8");
+
+            (triple, Some(selection))
+        } else {
+            (selection, None)
+        };
+
+        let triple = INTERNER.intern_str(triple);
+        let file = file.map(|f| INTERNER.intern_str(f));
+
+        Self { triple, file }
+    }
+
+    pub fn rustc_target_arg(&self) -> &str {
+        self.file.as_ref().unwrap_or(&self.triple)
+    }
+
+    pub fn contains(&self, needle: &str) -> bool {
+        self.triple.contains(needle)
+    }
+
+    pub fn starts_with(&self, needle: &str) -> bool {
+        self.triple.starts_with(needle)
+    }
+
+    pub fn ends_with(&self, needle: &str) -> bool {
+        self.triple.ends_with(needle)
+    }
+}
+
+impl fmt::Display for TargetSelection {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.triple)?;
+        if let Some(file) = self.file {
+            write!(f, "({})", file)?;
+        }
+        Ok(())
+    }
+}
+
+impl PartialEq<&str> for TargetSelection {
+    fn eq(&self, other: &&str) -> bool {
+        self.triple == *other
+    }
+}
+
 /// Per-target configuration stored in the global configuration structure.
 #[derive(Default)]
 pub struct Target {
@@ -403,7 +465,7 @@ impl Config {
         config.missing_tools = false;
 
         // set by bootstrap.py
-        config.build = INTERNER.intern_str(&env::var("BUILD").expect("'BUILD' to be set"));
+        config.build = TargetSelection::from_user(&env::var("BUILD").expect("'BUILD' to be set"));
         config.src = Config::path_from_python("SRC");
         config.out = Config::path_from_python("BUILD_DIR");
 
@@ -464,14 +526,16 @@ impl Config {
         let build = toml.build.clone().unwrap_or_default();
         // set by bootstrap.py
         config.hosts.push(config.build.clone());
-        for host in build.host.iter() {
-            let host = INTERNER.intern_str(host);
+        for host in build.host.iter().map(|h| TargetSelection::from_user(h)) {
             if !config.hosts.contains(&host) {
                 config.hosts.push(host);
             }
         }
-        for target in
-            config.hosts.iter().cloned().chain(build.target.iter().map(|s| INTERNER.intern_str(s)))
+        for target in config
+            .hosts
+            .iter()
+            .copied()
+            .chain(build.target.iter().map(|h| TargetSelection::from_user(h)))
         {
             if !config.targets.contains(&target) {
                 config.targets.push(target);
@@ -637,7 +701,7 @@ impl Config {
                 target.wasi_root = cfg.wasi_root.clone().map(PathBuf::from);
                 target.qemu_rootfs = cfg.qemu_rootfs.clone().map(PathBuf::from);
 
-                config.target_config.insert(INTERNER.intern_string(triple.clone()), target);
+                config.target_config.insert(TargetSelection::from_user(triple), target);
             }
         }
 
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index 5d2fcba7fea..af30747a959 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -20,6 +20,7 @@ use crate::builder::{Builder, RunConfig, ShouldRun, Step};
 use crate::cache::{Interned, INTERNER};
 use crate::channel;
 use crate::compile;
+use crate::config::TargetSelection;
 use crate::tool::{self, Tool};
 use crate::util::{exe, is_dylib, timeit};
 use crate::{Compiler, DependencyType, Mode, LLVM_TOOLS};
@@ -68,7 +69,7 @@ fn missing_tool(tool_name: &str, skip: bool) {
 
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Docs {
-    pub host: Interned<String>,
+    pub host: TargetSelection,
 }
 
 impl Step for Docs {
@@ -131,7 +132,7 @@ impl Step for Docs {
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct RustcDocs {
-    pub host: Interned<String>,
+    pub host: TargetSelection,
 }
 
 impl Step for RustcDocs {
@@ -210,11 +211,11 @@ fn find_files(files: &[&str], path: &[PathBuf]) -> Vec<PathBuf> {
 fn make_win_dist(
     rust_root: &Path,
     plat_root: &Path,
-    target_triple: Interned<String>,
+    target: TargetSelection,
     builder: &Builder<'_>,
 ) {
     //Ask gcc where it keeps its stuff
-    let mut cmd = Command::new(builder.cc(target_triple));
+    let mut cmd = Command::new(builder.cc(target));
     cmd.arg("-print-search-dirs");
     let gcc_out = output(&mut cmd);
 
@@ -234,16 +235,16 @@ fn make_win_dist(
         }
     }
 
-    let compiler = if target_triple == "i686-pc-windows-gnu" {
+    let compiler = if target == "i686-pc-windows-gnu" {
         "i686-w64-mingw32-gcc.exe"
-    } else if target_triple == "x86_64-pc-windows-gnu" {
+    } else if target == "x86_64-pc-windows-gnu" {
         "x86_64-w64-mingw32-gcc.exe"
     } else {
         "gcc.exe"
     };
     let target_tools = [compiler, "ld.exe", "dlltool.exe", "libwinpthread-1.dll"];
     let mut rustc_dlls = vec!["libwinpthread-1.dll"];
-    if target_triple.starts_with("i686-") {
+    if target.starts_with("i686-") {
         rustc_dlls.push("libgcc_s_dw2-1.dll");
     } else {
         rustc_dlls.push("libgcc_s_seh-1.dll");
@@ -311,7 +312,7 @@ fn make_win_dist(
     let target_bin_dir = plat_root
         .join("lib")
         .join("rustlib")
-        .join(target_triple)
+        .join(target.triple)
         .join("bin")
         .join("self-contained");
     fs::create_dir_all(&target_bin_dir).expect("creating target_bin_dir failed");
@@ -331,7 +332,7 @@ fn make_win_dist(
     let target_lib_dir = plat_root
         .join("lib")
         .join("rustlib")
-        .join(target_triple)
+        .join(target.triple)
         .join("lib")
         .join("self-contained");
     fs::create_dir_all(&target_lib_dir).expect("creating target_lib_dir failed");
@@ -342,7 +343,7 @@ fn make_win_dist(
 
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Mingw {
-    pub host: Interned<String>,
+    pub host: TargetSelection,
 }
 
 impl Step for Mingw {
@@ -530,11 +531,11 @@ impl Step for Rustc {
 
             // Copy over lld if it's there
             if builder.config.lld_enabled {
-                let exe = exe("rust-lld", &compiler.host);
+                let exe = exe("rust-lld", compiler.host);
                 let src =
                     builder.sysroot_libdir(compiler, host).parent().unwrap().join("bin").join(&exe);
                 // for the rationale about this rename check `compile::copy_lld_to_sysroot`
-                let dst = image.join("lib/rustlib").join(&*host).join("bin").join(&exe);
+                let dst = image.join("lib/rustlib").join(&*host.triple).join("bin").join(&exe);
                 t!(fs::create_dir_all(&dst.parent().unwrap()));
                 builder.copy(&src, &dst);
             }
@@ -592,7 +593,7 @@ impl Step for Rustc {
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct DebuggerScripts {
     pub sysroot: Interned<PathBuf>,
-    pub host: Interned<String>,
+    pub host: TargetSelection,
 }
 
 impl Step for DebuggerScripts {
@@ -662,8 +663,8 @@ fn skip_host_target_lib(builder: &Builder<'_>, compiler: Compiler) -> bool {
 }
 
 /// Copy stamped files into an image's `target/lib` directory.
-fn copy_target_libs(builder: &Builder<'_>, target: &str, image: &Path, stamp: &Path) {
-    let dst = image.join("lib/rustlib").join(target).join("lib");
+fn copy_target_libs(builder: &Builder<'_>, target: TargetSelection, image: &Path, stamp: &Path) {
+    let dst = image.join("lib/rustlib").join(target.triple).join("lib");
     let self_contained_dst = dst.join("self-contained");
     t!(fs::create_dir_all(&dst));
     t!(fs::create_dir_all(&self_contained_dst));
@@ -679,7 +680,7 @@ fn copy_target_libs(builder: &Builder<'_>, target: &str, image: &Path, stamp: &P
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Std {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Std {
@@ -718,7 +719,7 @@ impl Step for Std {
 
         let compiler_to_use = builder.compiler_for(compiler.stage, compiler.host, target);
         let stamp = compile::libstd_stamp(builder, compiler_to_use, target);
-        copy_target_libs(builder, &target, &image, &stamp);
+        copy_target_libs(builder, target, &image, &stamp);
 
         let mut cmd = rust_installer(builder);
         cmd.arg("generate")
@@ -747,7 +748,7 @@ impl Step for Std {
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct RustcDev {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for RustcDev {
@@ -787,7 +788,7 @@ impl Step for RustcDev {
 
         let compiler_to_use = builder.compiler_for(compiler.stage, compiler.host, target);
         let stamp = compile::librustc_stamp(builder, compiler_to_use, target);
-        copy_target_libs(builder, &target, &image, &stamp);
+        copy_target_libs(builder, target, &image, &stamp);
 
         let mut cmd = rust_installer(builder);
         cmd.arg("generate")
@@ -818,7 +819,7 @@ impl Step for RustcDev {
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Analysis {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Analysis {
@@ -861,12 +862,12 @@ impl Step for Analysis {
 
         let src = builder
             .stage_out(compiler, Mode::Std)
-            .join(target)
+            .join(target.triple)
             .join(builder.cargo_dir())
             .join("deps");
 
         let image_src = src.join("save-analysis");
-        let dst = image.join("lib/rustlib").join(target).join("analysis");
+        let dst = image.join("lib/rustlib").join(target.triple).join("analysis");
         t!(fs::create_dir_all(&dst));
         builder.info(&format!("image_src: {:?}, dst: {:?}", image_src, dst));
         builder.cp_r(&image_src, &dst);
@@ -1163,7 +1164,7 @@ pub fn sanitize_sh(path: &Path) -> String {
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Cargo {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Cargo {
@@ -1255,7 +1256,7 @@ impl Step for Cargo {
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Rls {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Rls {
@@ -1345,7 +1346,7 @@ impl Step for Rls {
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct RustAnalyzer {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for RustAnalyzer {
@@ -1432,7 +1433,7 @@ impl Step for RustAnalyzer {
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Clippy {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Clippy {
@@ -1523,7 +1524,7 @@ impl Step for Clippy {
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Miri {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Miri {
@@ -1620,7 +1621,7 @@ impl Step for Miri {
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Rustfmt {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Rustfmt {
@@ -1714,8 +1715,8 @@ impl Step for Rustfmt {
 #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Extended {
     stage: u32,
-    host: Interned<String>,
-    target: Interned<String>,
+    host: TargetSelection,
+    target: TargetSelection,
 }
 
 impl Step for Extended {
@@ -2255,7 +2256,7 @@ impl Step for Extended {
     }
 }
 
-fn add_env(builder: &Builder<'_>, cmd: &mut Command, target: Interned<String>) {
+fn add_env(builder: &Builder<'_>, cmd: &mut Command, target: TargetSelection) {
     let mut parts = channel::CFG_RELEASE_NUM.split('.');
     cmd.env("CFG_RELEASE_INFO", builder.rust_version())
         .env("CFG_RELEASE_NUM", channel::CFG_RELEASE_NUM)
@@ -2266,7 +2267,7 @@ fn add_env(builder: &Builder<'_>, cmd: &mut Command, target: Interned<String>) {
         .env("CFG_VER_BUILD", "0") // just needed to build
         .env("CFG_PACKAGE_VERS", builder.rust_package_vers())
         .env("CFG_PACKAGE_NAME", pkgname(builder, "rust"))
-        .env("CFG_BUILD", target)
+        .env("CFG_BUILD", target.triple)
         .env("CFG_CHANNEL", &builder.config.channel);
 
     if target.contains("windows-gnu") {
@@ -2348,7 +2349,7 @@ impl Step for HashSign {
 ///
 /// Note: This function does not yet support Windows, but we also don't support
 ///       linking LLVM tools dynamically on Windows yet.
-fn maybe_install_llvm(builder: &Builder<'_>, target: Interned<String>, dst_libdir: &Path) {
+fn maybe_install_llvm(builder: &Builder<'_>, target: TargetSelection, dst_libdir: &Path) {
     let src_libdir = builder.llvm_out(target).join("lib");
 
     if target.contains("apple-darwin") {
@@ -2373,13 +2374,13 @@ fn maybe_install_llvm(builder: &Builder<'_>, target: Interned<String>, dst_libdi
 }
 
 /// Maybe add libLLVM.so to the target lib-dir for linking.
-pub fn maybe_install_llvm_target(builder: &Builder<'_>, target: Interned<String>, sysroot: &Path) {
-    let dst_libdir = sysroot.join("lib/rustlib").join(&*target).join("lib");
+pub fn maybe_install_llvm_target(builder: &Builder<'_>, target: TargetSelection, sysroot: &Path) {
+    let dst_libdir = sysroot.join("lib/rustlib").join(&*target.triple).join("lib");
     maybe_install_llvm(builder, target, &dst_libdir);
 }
 
 /// Maybe add libLLVM.so to the runtime lib-dir for rustc itself.
-pub fn maybe_install_llvm_runtime(builder: &Builder<'_>, target: Interned<String>, sysroot: &Path) {
+pub fn maybe_install_llvm_runtime(builder: &Builder<'_>, target: TargetSelection, sysroot: &Path) {
     let dst_libdir =
         sysroot.join(builder.sysroot_libdir_relative(Compiler { stage: 1, host: target }));
     maybe_install_llvm(builder, target, &dst_libdir);
@@ -2387,7 +2388,7 @@ pub fn maybe_install_llvm_runtime(builder: &Builder<'_>, target: Interned<String
 
 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
 pub struct LlvmTools {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for LlvmTools {
@@ -2425,10 +2426,10 @@ impl Step for LlvmTools {
 
         // Prepare the image directory
         let src_bindir = builder.llvm_out(target).join("bin");
-        let dst_bindir = image.join("lib/rustlib").join(&*target).join("bin");
+        let dst_bindir = image.join("lib/rustlib").join(&*target.triple).join("bin");
         t!(fs::create_dir_all(&dst_bindir));
         for tool in LLVM_TOOLS {
-            let exe = src_bindir.join(exe(tool, &target));
+            let exe = src_bindir.join(exe(tool, target));
             builder.install(&exe, &dst_bindir, 0o755);
         }
 
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index 31216902858..b051390fc26 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -18,7 +18,7 @@ use build_helper::{t, up_to_date};
 use crate::builder::{Builder, Compiler, RunConfig, ShouldRun, Step};
 use crate::cache::{Interned, INTERNER};
 use crate::compile;
-use crate::config::Config;
+use crate::config::{Config, TargetSelection};
 use crate::tool::{self, prepare_tool_cargo, SourceType, Tool};
 use crate::util::symlink_dir;
 
@@ -27,7 +27,7 @@ macro_rules! book {
         $(
             #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
         pub struct $name {
-            target: Interned<String>,
+            target: TargetSelection,
         }
 
         impl Step for $name {
@@ -101,7 +101,7 @@ fn is_explicit_request(builder: &Builder<'_>, path: &str) -> bool {
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct UnstableBook {
-    target: Interned<String>,
+    target: TargetSelection,
 }
 
 impl Step for UnstableBook {
@@ -129,7 +129,7 @@ impl Step for UnstableBook {
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 struct RustbookSrc {
-    target: Interned<String>,
+    target: TargetSelection,
     name: Interned<String>,
     src: Interned<PathBuf>,
 }
@@ -169,7 +169,7 @@ impl Step for RustbookSrc {
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct TheBook {
     compiler: Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
 }
 
 impl Step for TheBook {
@@ -241,7 +241,7 @@ impl Step for TheBook {
 fn invoke_rustdoc(
     builder: &Builder<'_>,
     compiler: Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
     markdown: &str,
 ) {
     let out = builder.doc_out(target);
@@ -277,7 +277,7 @@ fn invoke_rustdoc(
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Standalone {
     compiler: Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
 }
 
 impl Step for Standalone {
@@ -386,7 +386,7 @@ impl Step for Standalone {
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Std {
     pub stage: u32,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Std {
@@ -415,7 +415,7 @@ impl Step for Std {
         let compiler = builder.compiler(stage, builder.config.build);
 
         builder.ensure(compile::Std { compiler, target });
-        let out_dir = builder.stage_out(compiler, Mode::Std).join(target).join("doc");
+        let out_dir = builder.stage_out(compiler, Mode::Std).join(target.triple).join("doc");
 
         t!(fs::copy(builder.src.join("src/doc/rust.css"), out.join("rust.css")));
 
@@ -475,7 +475,7 @@ impl Step for Std {
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Rustc {
     stage: u32,
-    target: Interned<String>,
+    target: TargetSelection,
 }
 
 impl Step for Rustc {
@@ -522,7 +522,7 @@ impl Step for Rustc {
         // needed because rustdoc is built in a different directory from
         // rustc. rustdoc needs to be able to see everything, for example when
         // merging the search index, or generating local (relative) links.
-        let out_dir = builder.stage_out(compiler, Mode::Rustc).join(target).join("doc");
+        let out_dir = builder.stage_out(compiler, Mode::Rustc).join(target.triple).join("doc");
         t!(symlink_dir_force(&builder.config, &out, &out_dir));
 
         // Build cargo command.
@@ -559,7 +559,7 @@ impl Step for Rustc {
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Rustdoc {
     stage: u32,
-    target: Interned<String>,
+    target: TargetSelection,
 }
 
 impl Step for Rustdoc {
@@ -604,7 +604,7 @@ impl Step for Rustdoc {
         builder.ensure(tool::Rustdoc { compiler });
 
         // Symlink compiler docs to the output directory of rustdoc documentation.
-        let out_dir = builder.stage_out(compiler, Mode::ToolRustc).join(target).join("doc");
+        let out_dir = builder.stage_out(compiler, Mode::ToolRustc).join(target.triple).join("doc");
         t!(fs::create_dir_all(&out_dir));
         t!(symlink_dir_force(&builder.config, &out, &out_dir));
 
@@ -632,7 +632,7 @@ impl Step for Rustdoc {
 #[derive(Ord, PartialOrd, Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct ErrorIndex {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for ErrorIndex {
@@ -672,7 +672,7 @@ impl Step for ErrorIndex {
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct UnstableBookGen {
-    target: Interned<String>,
+    target: TargetSelection,
 }
 
 impl Step for UnstableBookGen {
diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs
index f477c752933..1055689c81e 100644
--- a/src/bootstrap/flags.rs
+++ b/src/bootstrap/flags.rs
@@ -10,8 +10,7 @@ use std::process;
 use getopts::Options;
 
 use crate::builder::Builder;
-use crate::cache::{Interned, INTERNER};
-use crate::config::Config;
+use crate::config::{Config, TargetSelection};
 use crate::{Build, DocTests};
 
 /// Deserialized version of all flags for this compile.
@@ -21,8 +20,8 @@ pub struct Flags {
     pub stage: Option<u32>,
     pub keep_stage: Vec<u32>,
 
-    pub host: Vec<Interned<String>>,
-    pub target: Vec<Interned<String>>,
+    pub host: Vec<TargetSelection>,
+    pub target: Vec<TargetSelection>,
     pub config: Option<PathBuf>,
     pub jobs: Option<u32>,
     pub cmd: Subcommand,
@@ -532,11 +531,11 @@ Arguments:
                 .collect(),
             host: split(&matches.opt_strs("host"))
                 .into_iter()
-                .map(|x| INTERNER.intern_string(x))
+                .map(|x| TargetSelection::from_user(&x))
                 .collect::<Vec<_>>(),
             target: split(&matches.opt_strs("target"))
                 .into_iter()
-                .map(|x| INTERNER.intern_string(x))
+                .map(|x| TargetSelection::from_user(&x))
                 .collect::<Vec<_>>(),
             config: cfg_file,
             jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
diff --git a/src/bootstrap/install.rs b/src/bootstrap/install.rs
index 7026b25d1b9..7266625ff39 100644
--- a/src/bootstrap/install.rs
+++ b/src/bootstrap/install.rs
@@ -14,46 +14,47 @@ use crate::dist::{self, pkgname, sanitize_sh, tmpdir};
 use crate::Compiler;
 
 use crate::builder::{Builder, RunConfig, ShouldRun, Step};
-use crate::cache::Interned;
-use crate::config::Config;
+use crate::config::{Config, TargetSelection};
 
-pub fn install_docs(builder: &Builder<'_>, stage: u32, host: Interned<String>) {
+pub fn install_docs(builder: &Builder<'_>, stage: u32, host: TargetSelection) {
     install_sh(builder, "docs", "rust-docs", stage, Some(host));
 }
 
-pub fn install_std(builder: &Builder<'_>, stage: u32, target: Interned<String>) {
+pub fn install_std(builder: &Builder<'_>, stage: u32, target: TargetSelection) {
     install_sh(builder, "std", "rust-std", stage, Some(target));
 }
 
-pub fn install_cargo(builder: &Builder<'_>, stage: u32, host: Interned<String>) {
+pub fn install_cargo(builder: &Builder<'_>, stage: u32, host: TargetSelection) {
     install_sh(builder, "cargo", "cargo", stage, Some(host));
 }
 
-pub fn install_rls(builder: &Builder<'_>, stage: u32, host: Interned<String>) {
+pub fn install_rls(builder: &Builder<'_>, stage: u32, host: TargetSelection) {
     install_sh(builder, "rls", "rls", stage, Some(host));
 }
-pub fn install_rust_analyzer(builder: &Builder<'_>, stage: u32, host: Interned<String>) {
+
+pub fn install_rust_analyzer(builder: &Builder<'_>, stage: u32, host: TargetSelection) {
     install_sh(builder, "rust-analyzer", "rust-analyzer", stage, Some(host));
 }
-pub fn install_clippy(builder: &Builder<'_>, stage: u32, host: Interned<String>) {
+
+pub fn install_clippy(builder: &Builder<'_>, stage: u32, host: TargetSelection) {
     install_sh(builder, "clippy", "clippy", stage, Some(host));
 }
-pub fn install_miri(builder: &Builder<'_>, stage: u32, host: Interned<String>) {
+pub fn install_miri(builder: &Builder<'_>, stage: u32, host: TargetSelection) {
     install_sh(builder, "miri", "miri", stage, Some(host));
 }
 
-pub fn install_rustfmt(builder: &Builder<'_>, stage: u32, host: Interned<String>) {
+pub fn install_rustfmt(builder: &Builder<'_>, stage: u32, host: TargetSelection) {
     install_sh(builder, "rustfmt", "rustfmt", stage, Some(host));
 }
 
-pub fn install_analysis(builder: &Builder<'_>, stage: u32, host: Interned<String>) {
+pub fn install_analysis(builder: &Builder<'_>, stage: u32, host: TargetSelection) {
     install_sh(builder, "analysis", "rust-analysis", stage, Some(host));
 }
 
 pub fn install_src(builder: &Builder<'_>, stage: u32) {
     install_sh(builder, "src", "rust-src", stage, None);
 }
-pub fn install_rustc(builder: &Builder<'_>, stage: u32, host: Interned<String>) {
+pub fn install_rustc(builder: &Builder<'_>, stage: u32, host: TargetSelection) {
     install_sh(builder, "rustc", "rustc", stage, Some(host));
 }
 
@@ -62,7 +63,7 @@ fn install_sh(
     package: &str,
     name: &str,
     stage: u32,
-    host: Option<Interned<String>>,
+    host: Option<TargetSelection>,
 ) {
     builder.info(&format!("Install {} stage{} ({:?})", package, stage, host));
 
@@ -150,7 +151,7 @@ macro_rules! install {
             #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
         pub struct $name {
             pub compiler: Compiler,
-            pub target: Interned<String>,
+            pub target: TargetSelection,
         }
 
         impl $name {
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index 9ca9e338d55..77820ef87e3 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -123,6 +123,7 @@ use std::os::windows::fs::symlink_file;
 use build_helper::{mtime, output, run, run_suppressed, t, try_run, try_run_suppressed};
 use filetime::FileTime;
 
+use crate::config::TargetSelection;
 use crate::util::{exe, libdir, CiEnv};
 
 mod builder;
@@ -187,7 +188,7 @@ const LLVM_TOOLS: &[&str] = &[
 #[derive(Eq, PartialOrd, Ord, PartialEq, Clone, Copy, Hash, Debug)]
 pub struct Compiler {
     stage: u32,
-    host: Interned<String>,
+    host: TargetSelection,
 }
 
 #[derive(PartialEq, Eq, Copy, Clone, Debug)]
@@ -236,9 +237,9 @@ pub struct Build {
     verbosity: usize,
 
     // Targets for which to build
-    build: Interned<String>,
-    hosts: Vec<Interned<String>>,
-    targets: Vec<Interned<String>>,
+    build: TargetSelection,
+    hosts: Vec<TargetSelection>,
+    targets: Vec<TargetSelection>,
 
     // Stage 0 (downloaded) compiler, lld and cargo or their local rust equivalents
     initial_rustc: PathBuf,
@@ -248,10 +249,10 @@ pub struct Build {
 
     // Runtime state filled in later on
     // C/C++ compilers and archiver for all targets
-    cc: HashMap<Interned<String>, cc::Tool>,
-    cxx: HashMap<Interned<String>, cc::Tool>,
-    ar: HashMap<Interned<String>, PathBuf>,
-    ranlib: HashMap<Interned<String>, PathBuf>,
+    cc: HashMap<TargetSelection, cc::Tool>,
+    cxx: HashMap<TargetSelection, cc::Tool>,
+    ar: HashMap<TargetSelection, PathBuf>,
+    ranlib: HashMap<TargetSelection, PathBuf>,
     // Miscellaneous
     crates: HashMap<Interned<String>, Crate>,
     is_sudo: bool,
@@ -259,7 +260,7 @@ pub struct Build {
     delayed_failures: RefCell<Vec<String>>,
     prerelease_version: Cell<Option<u32>>,
     tool_artifacts:
-        RefCell<HashMap<Interned<String>, HashMap<String, (&'static str, PathBuf, Vec<String>)>>>,
+        RefCell<HashMap<TargetSelection, HashMap<String, (&'static str, PathBuf, Vec<String>)>>>,
 }
 
 #[derive(Debug)]
@@ -365,7 +366,7 @@ impl Build {
             output(
                 Command::new(&config.initial_rustc)
                     .arg("--target")
-                    .arg(config.build)
+                    .arg(config.build.rustc_target_arg())
                     .arg("--print")
                     .arg("target-libdir"),
             )
@@ -453,7 +454,7 @@ impl Build {
     }
 
     pub fn build_triple(&self) -> &[Interned<String>] {
-        unsafe { slice::from_raw_parts(&self.build, 1) }
+        slice::from_ref(&self.build.triple)
     }
 
     /// Executes the entire build, as configured by the flags and configuration.
@@ -558,7 +559,10 @@ impl Build {
     }
 
     fn tools_dir(&self, compiler: Compiler) -> PathBuf {
-        let out = self.out.join(&*compiler.host).join(format!("stage{}-tools-bin", compiler.stage));
+        let out = self
+            .out
+            .join(&*compiler.host.triple)
+            .join(format!("stage{}-tools-bin", compiler.stage));
         t!(fs::create_dir_all(&out));
         out
     }
@@ -575,47 +579,47 @@ impl Build {
             Mode::ToolBootstrap => "-bootstrap-tools",
             Mode::ToolStd | Mode::ToolRustc => "-tools",
         };
-        self.out.join(&*compiler.host).join(format!("stage{}{}", compiler.stage, suffix))
+        self.out.join(&*compiler.host.triple).join(format!("stage{}{}", compiler.stage, suffix))
     }
 
     /// Returns the root output directory for all Cargo output in a given stage,
     /// running a particular compiler, whether or not we're building the
     /// standard library, and targeting the specified architecture.
-    fn cargo_out(&self, compiler: Compiler, mode: Mode, target: Interned<String>) -> PathBuf {
-        self.stage_out(compiler, mode).join(&*target).join(self.cargo_dir())
+    fn cargo_out(&self, compiler: Compiler, mode: Mode, target: TargetSelection) -> PathBuf {
+        self.stage_out(compiler, mode).join(&*target.triple).join(self.cargo_dir())
     }
 
     /// Root output directory for LLVM compiled for `target`
     ///
     /// Note that if LLVM is configured externally then the directory returned
     /// will likely be empty.
-    fn llvm_out(&self, target: Interned<String>) -> PathBuf {
-        self.out.join(&*target).join("llvm")
+    fn llvm_out(&self, target: TargetSelection) -> PathBuf {
+        self.out.join(&*target.triple).join("llvm")
     }
 
-    fn lld_out(&self, target: Interned<String>) -> PathBuf {
-        self.out.join(&*target).join("lld")
+    fn lld_out(&self, target: TargetSelection) -> PathBuf {
+        self.out.join(&*target.triple).join("lld")
     }
 
     /// Output directory for all documentation for a target
-    fn doc_out(&self, target: Interned<String>) -> PathBuf {
-        self.out.join(&*target).join("doc")
+    fn doc_out(&self, target: TargetSelection) -> PathBuf {
+        self.out.join(&*target.triple).join("doc")
     }
 
     /// Output directory for all documentation for a target
-    fn compiler_doc_out(&self, target: Interned<String>) -> PathBuf {
-        self.out.join(&*target).join("compiler-doc")
+    fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf {
+        self.out.join(&*target.triple).join("compiler-doc")
     }
 
     /// Output directory for some generated md crate documentation for a target (temporary)
-    fn md_doc_out(&self, target: Interned<String>) -> Interned<PathBuf> {
-        INTERNER.intern_path(self.out.join(&*target).join("md-doc"))
+    fn md_doc_out(&self, target: TargetSelection) -> Interned<PathBuf> {
+        INTERNER.intern_path(self.out.join(&*target.triple).join("md-doc"))
     }
 
     /// Returns `true` if no custom `llvm-config` is set for the specified target.
     ///
     /// If no custom `llvm-config` was specified then Rust's llvm will be used.
-    fn is_rust_llvm(&self, target: Interned<String>) -> bool {
+    fn is_rust_llvm(&self, target: TargetSelection) -> bool {
         match self.config.target_config.get(&target) {
             Some(ref c) => c.llvm_config.is_none(),
             None => true,
@@ -623,13 +627,13 @@ impl Build {
     }
 
     /// Returns the path to `FileCheck` binary for the specified target
-    fn llvm_filecheck(&self, target: Interned<String>) -> PathBuf {
+    fn llvm_filecheck(&self, target: TargetSelection) -> PathBuf {
         let target_config = self.config.target_config.get(&target);
         if let Some(s) = target_config.and_then(|c| c.llvm_filecheck.as_ref()) {
             s.to_path_buf()
         } else if let Some(s) = target_config.and_then(|c| c.llvm_config.as_ref()) {
             let llvm_bindir = output(Command::new(s).arg("--bindir"));
-            let filecheck = Path::new(llvm_bindir.trim()).join(exe("FileCheck", &*target));
+            let filecheck = Path::new(llvm_bindir.trim()).join(exe("FileCheck", target));
             if filecheck.exists() {
                 filecheck
             } else {
@@ -637,7 +641,7 @@ impl Build {
                 // llvm subdirectory of the libdir.
                 let llvm_libdir = output(Command::new(s).arg("--libdir"));
                 let lib_filecheck =
-                    Path::new(llvm_libdir.trim()).join("llvm").join(exe("FileCheck", &*target));
+                    Path::new(llvm_libdir.trim()).join("llvm").join(exe("FileCheck", target));
                 if lib_filecheck.exists() {
                     lib_filecheck
                 } else {
@@ -662,18 +666,18 @@ impl Build {
             } else {
                 base
             };
-            base.join("bin").join(exe("FileCheck", &*target))
+            base.join("bin").join(exe("FileCheck", target))
         }
     }
 
     /// Directory for libraries built from C/C++ code and shared between stages.
-    fn native_dir(&self, target: Interned<String>) -> PathBuf {
-        self.out.join(&*target).join("native")
+    fn native_dir(&self, target: TargetSelection) -> PathBuf {
+        self.out.join(&*target.triple).join("native")
     }
 
     /// Root output directory for rust_test_helpers library compiled for
     /// `target`
-    fn test_helpers_out(&self, target: Interned<String>) -> PathBuf {
+    fn test_helpers_out(&self, target: TargetSelection) -> PathBuf {
         self.native_dir(target).join("rust-test-helpers")
     }
 
@@ -686,7 +690,7 @@ impl Build {
 
     /// Returns the libdir of the snapshot compiler.
     fn rustc_snapshot_libdir(&self) -> PathBuf {
-        self.rustc_snapshot_sysroot().join(libdir(&self.config.build))
+        self.rustc_snapshot_sysroot().join(libdir(self.config.build))
     }
 
     /// Returns the sysroot of the snapshot compiler.
@@ -784,13 +788,13 @@ impl Build {
     }
 
     /// Returns the path to the C compiler for the target specified.
-    fn cc(&self, target: Interned<String>) -> &Path {
+    fn cc(&self, target: TargetSelection) -> &Path {
         self.cc[&target].path()
     }
 
     /// Returns a list of flags to pass to the C compiler for the target
     /// specified.
-    fn cflags(&self, target: Interned<String>, which: GitRepo) -> Vec<String> {
+    fn cflags(&self, target: TargetSelection, which: GitRepo) -> Vec<String> {
         // Filter out -O and /O (the optimization flags) that we picked up from
         // cc-rs because the build scripts will determine that for themselves.
         let mut base = self.cc[&target]
@@ -811,7 +815,7 @@ impl Build {
         // Work around an apparently bad MinGW / GCC optimization,
         // See: http://lists.llvm.org/pipermail/cfe-dev/2016-December/051980.html
         // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78936
-        if &*target == "i686-pc-windows-gnu" {
+        if &*target.triple == "i686-pc-windows-gnu" {
             base.push("-fno-omit-frame-pointer".into());
         }
 
@@ -829,17 +833,17 @@ impl Build {
     }
 
     /// Returns the path to the `ar` archive utility for the target specified.
-    fn ar(&self, target: Interned<String>) -> Option<&Path> {
+    fn ar(&self, target: TargetSelection) -> Option<&Path> {
         self.ar.get(&target).map(|p| &**p)
     }
 
     /// Returns the path to the `ranlib` utility for the target specified.
-    fn ranlib(&self, target: Interned<String>) -> Option<&Path> {
+    fn ranlib(&self, target: TargetSelection) -> Option<&Path> {
         self.ranlib.get(&target).map(|p| &**p)
     }
 
     /// Returns the path to the C++ compiler for the target specified.
-    fn cxx(&self, target: Interned<String>) -> Result<&Path, String> {
+    fn cxx(&self, target: TargetSelection) -> Result<&Path, String> {
         match self.cxx.get(&target) {
             Some(p) => Ok(p.path()),
             None => {
@@ -849,12 +853,12 @@ impl Build {
     }
 
     /// Returns the path to the linker for the given target if it needs to be overridden.
-    fn linker(&self, target: Interned<String>, can_use_lld: bool) -> Option<&Path> {
+    fn linker(&self, target: TargetSelection, can_use_lld: bool) -> Option<&Path> {
         if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.as_ref())
         {
             Some(linker)
         } else if target != self.config.build
-            && util::use_host_linker(&target)
+            && util::use_host_linker(target)
             && !target.contains("msvc")
         {
             Some(self.cc(target))
@@ -866,7 +870,7 @@ impl Build {
     }
 
     /// Returns if this target should statically link the C runtime, if specified
-    fn crt_static(&self, target: Interned<String>) -> Option<bool> {
+    fn crt_static(&self, target: TargetSelection) -> Option<bool> {
         if target.contains("pc-windows-msvc") {
             Some(true)
         } else {
@@ -875,7 +879,7 @@ impl Build {
     }
 
     /// Returns the "musl root" for this `target`, if defined
-    fn musl_root(&self, target: Interned<String>) -> Option<&Path> {
+    fn musl_root(&self, target: TargetSelection) -> Option<&Path> {
         self.config
             .target_config
             .get(&target)
@@ -885,7 +889,7 @@ impl Build {
     }
 
     /// Returns the "musl libdir" for this `target`.
-    fn musl_libdir(&self, target: Interned<String>) -> Option<PathBuf> {
+    fn musl_libdir(&self, target: TargetSelection) -> Option<PathBuf> {
         let t = self.config.target_config.get(&target)?;
         if let libdir @ Some(_) = &t.musl_libdir {
             return libdir.clone();
@@ -894,18 +898,18 @@ impl Build {
     }
 
     /// Returns the sysroot for the wasi target, if defined
-    fn wasi_root(&self, target: Interned<String>) -> Option<&Path> {
+    fn wasi_root(&self, target: TargetSelection) -> Option<&Path> {
         self.config.target_config.get(&target).and_then(|t| t.wasi_root.as_ref()).map(|p| &**p)
     }
 
     /// Returns `true` if this is a no-std `target`, if defined
-    fn no_std(&self, target: Interned<String>) -> Option<bool> {
+    fn no_std(&self, target: TargetSelection) -> Option<bool> {
         self.config.target_config.get(&target).map(|t| t.no_std)
     }
 
     /// Returns `true` if the target will be tested using the `remote-test-client`
     /// and `remote-test-server` binaries.
-    fn remote_tested(&self, target: Interned<String>) -> bool {
+    fn remote_tested(&self, target: TargetSelection) -> bool {
         self.qemu_rootfs(target).is_some()
             || target.contains("android")
             || env::var_os("TEST_DEVICE_ADDR").is_some()
@@ -916,7 +920,7 @@ impl Build {
     ///
     /// If `Some` is returned then that means that tests for this target are
     /// emulated with QEMU and binaries will need to be shipped to the emulator.
-    fn qemu_rootfs(&self, target: Interned<String>) -> Option<&Path> {
+    fn qemu_rootfs(&self, target: TargetSelection) -> Option<&Path> {
         self.config.target_config.get(&target).and_then(|t| t.qemu_rootfs.as_ref()).map(|p| &**p)
     }
 
@@ -948,7 +952,7 @@ impl Build {
     ///
     /// When all of these conditions are met the build will lift artifacts from
     /// the previous stage forward.
-    fn force_use_stage1(&self, compiler: Compiler, target: Interned<String>) -> bool {
+    fn force_use_stage1(&self, compiler: Compiler, target: TargetSelection) -> bool {
         !self.config.full_bootstrap
             && compiler.stage >= 2
             && (self.hosts.iter().any(|h| *h == target) || target == self.build)
@@ -1058,7 +1062,7 @@ impl Build {
         self.rust_version()
     }
 
-    fn llvm_link_tools_dynamically(&self, target: Interned<String>) -> bool {
+    fn llvm_link_tools_dynamically(&self, target: TargetSelection) -> bool {
         target.contains("linux-gnu") || target.contains("apple-darwin")
     }
 
diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs
index b7c527f6712..48b2cc24d4c 100644
--- a/src/bootstrap/native.rs
+++ b/src/bootstrap/native.rs
@@ -19,8 +19,8 @@ use std::process::Command;
 use build_helper::{output, t};
 
 use crate::builder::{Builder, RunConfig, ShouldRun, Step};
-use crate::cache::Interned;
 use crate::channel;
+use crate::config::TargetSelection;
 use crate::util::{self, exe};
 use crate::GitRepo;
 use build_helper::up_to_date;
@@ -41,7 +41,7 @@ pub struct Meta {
 // if not).
 pub fn prebuilt_llvm_config(
     builder: &Builder<'_>,
-    target: Interned<String>,
+    target: TargetSelection,
 ) -> Result<PathBuf, Meta> {
     // If we're using a custom LLVM bail out here, but we can only use a
     // custom LLVM for the build triple.
@@ -54,13 +54,14 @@ pub fn prebuilt_llvm_config(
 
     let root = "src/llvm-project/llvm";
     let out_dir = builder.llvm_out(target);
+
     let mut llvm_config_ret_dir = builder.llvm_out(builder.config.build);
     if !builder.config.build.contains("msvc") || builder.config.ninja {
         llvm_config_ret_dir.push("build");
     }
     llvm_config_ret_dir.push("bin");
 
-    let build_llvm_config = llvm_config_ret_dir.join(exe("llvm-config", &*builder.config.build));
+    let build_llvm_config = llvm_config_ret_dir.join(exe("llvm-config", builder.config.build));
 
     let stamp = out_dir.join("llvm-finished-building");
     let stamp = HashStamp::new(stamp, builder.in_tree_llvm_info.sha());
@@ -93,7 +94,7 @@ pub fn prebuilt_llvm_config(
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Llvm {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Llvm {
@@ -115,9 +116,9 @@ impl Step for Llvm {
         let target_native = if self.target.starts_with("riscv") {
             // RISC-V target triples in Rust is not named the same as C compiler target triples.
             // This converts Rust RISC-V target triples to C compiler triples.
-            let idx = target.find('-').unwrap();
+            let idx = target.triple.find('-').unwrap();
 
-            format!("riscv{}{}", &target[5..7], &target[idx..])
+            format!("riscv{}{}", &target.triple[5..7], &target.triple[idx..])
         } else {
             target.to_string()
         };
@@ -359,7 +360,7 @@ fn check_llvm_version(builder: &Builder<'_>, llvm_config: &Path) {
 
 fn configure_cmake(
     builder: &Builder<'_>,
-    target: Interned<String>,
+    target: TargetSelection,
     cfg: &mut cmake::Config,
     use_compiler_launcher: bool,
 ) {
@@ -375,7 +376,7 @@ fn configure_cmake(
     if builder.config.ninja {
         cfg.generator("Ninja");
     }
-    cfg.target(&target).host(&builder.config.build);
+    cfg.target(&target.triple).host(&builder.config.build.triple);
 
     let sanitize_cc = |cc: &Path| {
         if target.contains("msvc") {
@@ -405,7 +406,7 @@ fn configure_cmake(
         cfg.define("CMAKE_C_COMPILER", sanitize_cc(&wrap_cc))
             .define("CMAKE_CXX_COMPILER", sanitize_cc(&wrap_cc));
         cfg.env("SCCACHE_PATH", builder.config.ccache.as_ref().unwrap())
-            .env("SCCACHE_TARGET", target)
+            .env("SCCACHE_TARGET", target.triple)
             .env("SCCACHE_CC", &cc)
             .env("SCCACHE_CXX", &cxx);
 
@@ -505,7 +506,7 @@ fn configure_cmake(
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Lld {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Lld {
@@ -578,8 +579,8 @@ impl Step for Lld {
         // brittle and will break over time. If anyone knows better how to
         // cross-compile LLD it would be much appreciated to fix this!
         if target != builder.config.build {
-            cfg.env("LLVM_CONFIG_SHIM_REPLACE", &builder.config.build)
-                .env("LLVM_CONFIG_SHIM_REPLACE_WITH", &target)
+            cfg.env("LLVM_CONFIG_SHIM_REPLACE", &builder.config.build.triple)
+                .env("LLVM_CONFIG_SHIM_REPLACE_WITH", &target.triple)
                 .define(
                     "LLVM_TABLEGEN_EXE",
                     llvm_config.with_file_name("llvm-tblgen").with_extension(EXE_EXTENSION),
@@ -599,7 +600,7 @@ impl Step for Lld {
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct TestHelpers {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for TestHelpers {
@@ -646,8 +647,8 @@ impl Step for TestHelpers {
 
         cfg.cargo_metadata(false)
             .out_dir(&dst)
-            .target(&target)
-            .host(&builder.config.build)
+            .target(&target.triple)
+            .host(&builder.config.build.triple)
             .opt_level(0)
             .warnings(false)
             .debug(false)
@@ -658,7 +659,7 @@ impl Step for TestHelpers {
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Sanitizers {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Sanitizers {
@@ -709,7 +710,7 @@ impl Step for Sanitizers {
 
         let mut cfg = cmake::Config::new(&compiler_rt_dir);
         cfg.profile("Release");
-        cfg.define("CMAKE_C_COMPILER_TARGET", self.target);
+        cfg.define("CMAKE_C_COMPILER_TARGET", self.target.triple);
         cfg.define("COMPILER_RT_BUILD_BUILTINS", "OFF");
         cfg.define("COMPILER_RT_BUILD_CRT", "OFF");
         cfg.define("COMPILER_RT_BUILD_LIBFUZZER", "OFF");
@@ -752,7 +753,7 @@ pub struct SanitizerRuntime {
 /// Returns sanitizers available on a given target.
 fn supported_sanitizers(
     out_dir: &Path,
-    target: Interned<String>,
+    target: TargetSelection,
     channel: &str,
 ) -> Vec<SanitizerRuntime> {
     let darwin_libs = |os: &str, components: &[&str]| -> Vec<SanitizerRuntime> {
@@ -778,7 +779,7 @@ fn supported_sanitizers(
             .collect()
     };
 
-    match &*target {
+    match &*target.triple {
         "aarch64-fuchsia" => common_libs("fuchsia", "aarch64", &["asan"]),
         "aarch64-unknown-linux-gnu" => {
             common_libs("linux", "aarch64", &["asan", "lsan", "msan", "tsan"])
diff --git a/src/bootstrap/sanity.rs b/src/bootstrap/sanity.rs
index 3301d41cfee..f89bef50de9 100644
--- a/src/bootstrap/sanity.rs
+++ b/src/bootstrap/sanity.rs
@@ -183,7 +183,11 @@ pub fn check(build: &mut Build) {
             panic!("the iOS target is only supported on macOS");
         }
 
-        build.config.target_config.entry(target.clone()).or_insert(Target::from_triple(target));
+        build
+            .config
+            .target_config
+            .entry(target.clone())
+            .or_insert(Target::from_triple(&target.triple));
 
         if target.contains("-none-") || target.contains("nvptx") {
             if build.no_std(*target) == Some(false) {
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 1916d96bed7..b6641180c92 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -16,6 +16,7 @@ use build_helper::{self, output, t};
 use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::{Interned, INTERNER};
 use crate::compile;
+use crate::config::TargetSelection;
 use crate::dist;
 use crate::flags::Subcommand;
 use crate::native;
@@ -93,7 +94,7 @@ fn try_run_quiet(builder: &Builder<'_>, cmd: &mut Command) -> bool {
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Linkcheck {
-    host: Interned<String>,
+    host: TargetSelection,
 }
 
 impl Step for Linkcheck {
@@ -115,7 +116,7 @@ impl Step for Linkcheck {
         let _time = util::timeit(&builder);
         try_run(
             builder,
-            builder.tool_cmd(Tool::Linkchecker).arg(builder.out.join(host).join("doc")),
+            builder.tool_cmd(Tool::Linkchecker).arg(builder.out.join(host.triple).join("doc")),
         );
     }
 
@@ -132,7 +133,7 @@ impl Step for Linkcheck {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Cargotest {
     stage: u32,
-    host: Interned<String>,
+    host: TargetSelection,
 }
 
 impl Step for Cargotest {
@@ -177,7 +178,7 @@ impl Step for Cargotest {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Cargo {
     stage: u32,
-    host: Interned<String>,
+    host: TargetSelection,
 }
 
 impl Step for Cargo {
@@ -230,7 +231,7 @@ impl Step for Cargo {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Rls {
     stage: u32,
-    host: Interned<String>,
+    host: TargetSelection,
 }
 
 impl Step for Rls {
@@ -281,7 +282,7 @@ impl Step for Rls {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Rustfmt {
     stage: u32,
-    host: Interned<String>,
+    host: TargetSelection,
 }
 
 impl Step for Rustfmt {
@@ -338,7 +339,7 @@ impl Step for Rustfmt {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Miri {
     stage: u32,
-    host: Interned<String>,
+    host: TargetSelection,
 }
 
 impl Step for Miri {
@@ -464,7 +465,7 @@ impl Step for Miri {
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct CompiletestTest {
-    host: Interned<String>,
+    host: TargetSelection,
 }
 
 impl Step for CompiletestTest {
@@ -501,7 +502,7 @@ impl Step for CompiletestTest {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Clippy {
     stage: u32,
-    host: Interned<String>,
+    host: TargetSelection,
 }
 
 impl Step for Clippy {
@@ -542,8 +543,10 @@ impl Step for Clippy {
         cargo.env("RUSTC_TEST_SUITE", builder.rustc(compiler));
         cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler));
         let host_libs = builder.stage_out(compiler, Mode::ToolRustc).join(builder.cargo_dir());
-        let target_libs =
-            builder.stage_out(compiler, Mode::ToolRustc).join(&self.host).join(builder.cargo_dir());
+        let target_libs = builder
+            .stage_out(compiler, Mode::ToolRustc)
+            .join(&self.host.triple)
+            .join(builder.cargo_dir());
         cargo.env("HOST_LIBS", host_libs);
         cargo.env("TARGET_LIBS", target_libs);
         // clippy tests need to find the driver
@@ -607,7 +610,7 @@ impl Step for RustdocTheme {
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct RustdocJSStd {
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for RustdocJSStd {
@@ -646,8 +649,8 @@ impl Step for RustdocJSStd {
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct RustdocJSNotStd {
-    pub host: Interned<String>,
-    pub target: Interned<String>,
+    pub host: TargetSelection,
+    pub target: TargetSelection,
     pub compiler: Compiler,
 }
 
@@ -683,8 +686,8 @@ impl Step for RustdocJSNotStd {
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct RustdocUi {
-    pub host: Interned<String>,
-    pub target: Interned<String>,
+    pub host: TargetSelection,
+    pub target: TargetSelection,
     pub compiler: Compiler,
 }
 
@@ -785,8 +788,8 @@ impl Step for ExpandYamlAnchors {
     }
 }
 
-fn testdir(builder: &Builder<'_>, host: Interned<String>) -> PathBuf {
-    builder.out.join(host).join("test")
+fn testdir(builder: &Builder<'_>, host: TargetSelection) -> PathBuf {
+    builder.out.join(host.triple).join("test")
 }
 
 macro_rules! default_test {
@@ -855,7 +858,7 @@ macro_rules! test_definitions {
         #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
         pub struct $name {
             pub compiler: Compiler,
-            pub target: Interned<String>,
+            pub target: TargetSelection,
         }
 
         impl Step for $name {
@@ -943,7 +946,7 @@ default_test!(Assembly { path: "src/test/assembly", mode: "assembly", suite: "as
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 struct Compiletest {
     compiler: Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
     mode: &'static str,
     suite: &'static str,
     path: &'static str,
@@ -1023,8 +1026,8 @@ impl Step for Compiletest {
         cmd.arg("--build-base").arg(testdir(builder, compiler.host).join(suite));
         cmd.arg("--stage-id").arg(format!("stage{}-{}", compiler.stage, target));
         cmd.arg("--mode").arg(mode);
-        cmd.arg("--target").arg(target);
-        cmd.arg("--host").arg(&*compiler.host);
+        cmd.arg("--target").arg(target.rustc_target_arg());
+        cmd.arg("--host").arg(&*compiler.host.triple);
         cmd.arg("--llvm-filecheck").arg(builder.llvm_filecheck(builder.config.build));
 
         if builder.config.cmd.bless() {
@@ -1543,7 +1546,7 @@ impl Step for RustcGuide {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct CrateLibrustc {
     compiler: Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
     test_kind: TestKind,
     krate: Interned<String>,
 }
@@ -1589,7 +1592,7 @@ impl Step for CrateLibrustc {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct CrateNotDefault {
     compiler: Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
     test_kind: TestKind,
     krate: &'static str,
 }
@@ -1638,7 +1641,7 @@ impl Step for CrateNotDefault {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Crate {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
     pub mode: Mode,
     pub test_kind: TestKind,
     pub krate: Interned<String>,
@@ -1750,17 +1753,17 @@ impl Step for Crate {
 
         if target.contains("emscripten") {
             cargo.env(
-                format!("CARGO_TARGET_{}_RUNNER", envify(&target)),
+                format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)),
                 builder.config.nodejs.as_ref().expect("nodejs not configured"),
             );
         } else if target.starts_with("wasm32") {
             let node = builder.config.nodejs.as_ref().expect("nodejs not configured");
             let runner =
                 format!("{} {}/src/etc/wasm32-shim.js", node.display(), builder.src.display());
-            cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target)), &runner);
+            cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), &runner);
         } else if builder.remote_tested(target) {
             cargo.env(
-                format!("CARGO_TARGET_{}_RUNNER", envify(&target)),
+                format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)),
                 format!("{} run 0", builder.tool_exe(Tool::RemoteTestClient).display()),
             );
         }
@@ -1776,7 +1779,7 @@ impl Step for Crate {
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct CrateRustdoc {
-    host: Interned<String>,
+    host: TargetSelection,
     test_kind: TestKind,
 }
 
@@ -1883,7 +1886,7 @@ impl Step for CrateRustdoc {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct RemoteCopyLibs {
     compiler: Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
 }
 
 impl Step for RemoteCopyLibs {
@@ -1911,7 +1914,7 @@ impl Step for RemoteCopyLibs {
         // Spawn the emulator and wait for it to come online
         let tool = builder.tool_exe(Tool::RemoteTestClient);
         let mut cmd = Command::new(&tool);
-        cmd.arg("spawn-emulator").arg(target).arg(&server).arg(builder.out.join("tmp"));
+        cmd.arg("spawn-emulator").arg(target.triple).arg(&server).arg(builder.out.join("tmp"));
         if let Some(rootfs) = builder.qemu_rootfs(target) {
             cmd.arg(rootfs);
         }
@@ -1966,7 +1969,9 @@ impl Step for Distcheck {
                 .current_dir(&dir),
         );
         builder.run(
-            Command::new(build_helper::make(&builder.config.build)).arg("check").current_dir(&dir),
+            Command::new(build_helper::make(&builder.config.build.triple))
+                .arg("check")
+                .current_dir(&dir),
         );
 
         // Now make sure that rust-src has all of libstd's dependencies
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index 450b534d5df..f66061975d6 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -7,10 +7,10 @@ use std::process::{exit, Command};
 use build_helper::t;
 
 use crate::builder::{Builder, Cargo as CargoCommand, RunConfig, ShouldRun, Step};
-use crate::cache::Interned;
 use crate::channel;
 use crate::channel::GitInfo;
 use crate::compile;
+use crate::config::TargetSelection;
 use crate::toolstate::ToolState;
 use crate::util::{add_dylib_path, exe};
 use crate::Compiler;
@@ -25,7 +25,7 @@ pub enum SourceType {
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 struct ToolBuild {
     compiler: Compiler,
-    target: Interned<String>,
+    target: TargetSelection,
     tool: &'static str,
     path: &'static str,
     mode: Mode,
@@ -111,7 +111,7 @@ impl Step for ToolBuild {
                             .and_then(|p| p.file_name())
                             .and_then(|p| p.to_str())
                             .unwrap();
-                        if maybe_target != &*target {
+                        if maybe_target != &*target.triple {
                             continue;
                         }
                     }
@@ -208,8 +208,8 @@ impl Step for ToolBuild {
             }
         } else {
             let cargo_out =
-                builder.cargo_out(compiler, self.mode, target).join(exe(tool, &compiler.host));
-            let bin = builder.tools_dir(compiler).join(exe(tool, &compiler.host));
+                builder.cargo_out(compiler, self.mode, target).join(exe(tool, compiler.host));
+            let bin = builder.tools_dir(compiler).join(exe(tool, compiler.host));
             builder.copy(&cargo_out, &bin);
             Some(bin)
         }
@@ -220,7 +220,7 @@ pub fn prepare_tool_cargo(
     builder: &Builder<'_>,
     compiler: Compiler,
     mode: Mode,
-    target: Interned<String>,
+    target: TargetSelection,
     command: &'static str,
     path: &'static str,
     source_type: SourceType,
@@ -303,7 +303,7 @@ macro_rules! bootstrap_tool {
             #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
         pub struct $name {
             pub compiler: Compiler,
-            pub target: Interned<String>,
+            pub target: TargetSelection,
         }
 
         impl Step for $name {
@@ -416,7 +416,7 @@ impl Step for ErrorIndex {
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct RemoteTestServer {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for RemoteTestServer {
@@ -476,7 +476,7 @@ impl Step for Rustdoc {
             if !target_compiler.is_snapshot(builder) {
                 panic!("rustdoc in stage 0 must be snapshot rustdoc");
             }
-            return builder.initial_rustc.with_file_name(exe("rustdoc", &target_compiler.host));
+            return builder.initial_rustc.with_file_name(exe("rustdoc", target_compiler.host));
         }
         let target = target_compiler.host;
         // Similar to `compile::Assemble`, build with the previous stage's compiler. Otherwise
@@ -514,14 +514,14 @@ impl Step for Rustdoc {
         // rustdoc a different name.
         let tool_rustdoc = builder
             .cargo_out(build_compiler, Mode::ToolRustc, target)
-            .join(exe("rustdoc_tool_binary", &target_compiler.host));
+            .join(exe("rustdoc_tool_binary", target_compiler.host));
 
         // don't create a stage0-sysroot/bin directory.
         if target_compiler.stage > 0 {
             let sysroot = builder.sysroot(target_compiler);
             let bindir = sysroot.join("bin");
             t!(fs::create_dir_all(&bindir));
-            let bin_rustdoc = bindir.join(exe("rustdoc", &*target_compiler.host));
+            let bin_rustdoc = bindir.join(exe("rustdoc", target_compiler.host));
             let _ = fs::remove_file(&bin_rustdoc);
             builder.copy(&tool_rustdoc, &bin_rustdoc);
             bin_rustdoc
@@ -534,7 +534,7 @@ impl Step for Rustdoc {
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Cargo {
     pub compiler: Compiler,
-    pub target: Interned<String>,
+    pub target: TargetSelection,
 }
 
 impl Step for Cargo {
@@ -583,7 +583,7 @@ macro_rules! tool_extended {
             #[derive(Debug, Clone, Hash, PartialEq, Eq)]
         pub struct $name {
             pub compiler: Compiler,
-            pub target: Interned<String>,
+            pub target: TargetSelection,
             pub extra_features: Vec<String>,
         }
 
diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs
index 2bc6f1939d9..a307ef39d03 100644
--- a/src/bootstrap/util.rs
+++ b/src/bootstrap/util.rs
@@ -14,17 +14,16 @@ use std::time::Instant;
 use build_helper::t;
 
 use crate::builder::Builder;
-use crate::cache::Interned;
-use crate::config::Config;
+use crate::config::{Config, TargetSelection};
 
 /// Returns the `name` as the filename of a static library for `target`.
-pub fn staticlib(name: &str, target: &str) -> String {
+pub fn staticlib(name: &str, target: TargetSelection) -> String {
     if target.contains("windows") { format!("{}.lib", name) } else { format!("lib{}.a", name) }
 }
 
 /// Given an executable called `name`, return the filename for the
 /// executable for a particular target.
-pub fn exe(name: &str, target: &str) -> String {
+pub fn exe(name: &str, target: TargetSelection) -> String {
     if target.contains("windows") { format!("{}.exe", name) } else { name.to_string() }
 }
 
@@ -35,7 +34,7 @@ pub fn is_dylib(name: &str) -> bool {
 
 /// Returns the corresponding relative library directory that the compiler's
 /// dylibs will be found in.
-pub fn libdir(target: &str) -> &'static str {
+pub fn libdir(target: TargetSelection) -> &'static str {
     if target.contains("windows") { "bin" } else { "lib" }
 }
 
@@ -294,7 +293,7 @@ pub fn forcing_clang_based_tests() -> bool {
     }
 }
 
-pub fn use_host_linker(target: &Interned<String>) -> bool {
+pub fn use_host_linker(target: TargetSelection) -> bool {
     // FIXME: this information should be gotten by checking the linker flavor
     // of the rustc target
     !(target.contains("emscripten")