diff --git a/README.md b/README.md
index b24f4f0219..e6a5c5f71e 100644
--- a/README.md
+++ b/README.md
@@ -404,9 +404,21 @@ source repository. This is most often the case for nightly-only
 software that pins to a revision from the release archives.
 
 In these cases the toolchain can be named in the project's directory
-in a file called `rust-toolchain`, the content of which is the name of
-a single `rustup` toolchain, and which is suitable to check in to
-source control. This file has to be encoded in US-ASCII (if you are on
+in a file called `rust-toolchain`, the content of which is either the name of
+a single `rustup` toolchain, or a TOML file with the following layout:
+
+``` toml
+[toolchain]
+channel = "nightly-2020-07-10"
+components = [ "rustfmt", "rustc-dev" ]
+targets = [ "wasm32-unknown-unknown", "thumbv2-none-eabi" ]
+```
+
+If the TOML format is used, the `[toolchain]` section is mandatory,
+and at least one property must be specified.
+
+The `rust-toolchain` file is suitable to check in to source control. 
+This file has to be encoded in US-ASCII (if you are on
 Windows, check the encoding and that it does not starts with a BOM).
 
 The toolchains named in this file have a more restricted form than
diff --git a/src/cli/common.rs b/src/cli/common.rs
index 6ecbaf4e65..619b5da07a 100644
--- a/src/cli/common.rs
+++ b/src/cli/common.rs
@@ -480,9 +480,8 @@ pub fn list_toolchains(cfg: &Cfg, verbose: bool) -> Result<utils::ExitCode> {
             String::new()
         };
         let cwd = utils::current_dir()?;
-        let ovr_toolchain_name = if let Ok(Some((ovr_toolchain, _reason))) = cfg.find_override(&cwd)
-        {
-            ovr_toolchain.name().to_string()
+        let ovr_toolchain_name = if let Ok(Some((toolchain, _reason))) = cfg.find_override(&cwd) {
+            toolchain.name().to_string()
         } else {
             String::new()
         };
diff --git a/src/config.rs b/src/config.rs
index 4e82c5bbb6..de0e6e00a4 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -7,6 +7,7 @@ use std::str::FromStr;
 use std::sync::Arc;
 
 use pgp::{Deserializable, SignedPublicKey};
+use serde::Deserialize;
 
 use crate::dist::download::DownloadCfg;
 use crate::dist::{dist, temp};
@@ -18,6 +19,41 @@ use crate::settings::{Settings, SettingsFile, DEFAULT_METADATA_VERSION};
 use crate::toolchain::{DistributableToolchain, Toolchain, UpdateStatus};
 use crate::utils::utils;
 
+#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
+struct OverrideFile {
+    toolchain: ToolchainSection,
+}
+
+impl OverrideFile {
+    fn is_empty(&self) -> bool {
+        self.toolchain.is_empty()
+    }
+}
+
+#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
+struct ToolchainSection {
+    channel: Option<String>,
+    components: Option<Vec<String>>,
+    targets: Option<Vec<String>>,
+}
+
+impl ToolchainSection {
+    fn is_empty(&self) -> bool {
+        self.channel.is_none() && self.components.is_none() && self.targets.is_none()
+    }
+}
+
+impl<T: Into<String>> From<T> for OverrideFile {
+    fn from(channel: T) -> Self {
+        Self {
+            toolchain: ToolchainSection {
+                channel: Some(channel.into()),
+                ..Default::default()
+            },
+        }
+    }
+}
+
 #[derive(Debug)]
 pub enum OverrideReason {
     Environment,
@@ -37,6 +73,26 @@ impl Display for OverrideReason {
     }
 }
 
+#[derive(Default)]
+struct OverrideCfg<'a> {
+    toolchain: Option<Toolchain<'a>>,
+    components: Vec<String>,
+    targets: Vec<String>,
+}
+
+impl<'a> OverrideCfg<'a> {
+    fn from_file(cfg: &'a Cfg, file: OverrideFile) -> Result<Self> {
+        Ok(Self {
+            toolchain: match file.toolchain.channel {
+                Some(name) => Some(Toolchain::from(cfg, &name)?),
+                None => None,
+            },
+            components: file.toolchain.components.unwrap_or_default(),
+            targets: file.toolchain.targets.unwrap_or_default(),
+        })
+    }
+}
+
 lazy_static::lazy_static! {
     static ref BUILTIN_PGP_KEY: SignedPublicKey = pgp::SignedPublicKey::from_armor_single(
         io::Cursor::new(&include_bytes!("rust-key.pgp.ascii")[..])
@@ -402,16 +458,27 @@ impl Cfg {
     }
 
     pub fn find_override(&self, path: &Path) -> Result<Option<(Toolchain<'_>, OverrideReason)>> {
+        self.find_override_config(path).map(|opt| {
+            opt.and_then(|(override_cfg, reason)| {
+                override_cfg.toolchain.map(|toolchain| (toolchain, reason))
+            })
+        })
+    }
+
+    fn find_override_config(
+        &self,
+        path: &Path,
+    ) -> Result<Option<(OverrideCfg<'_>, OverrideReason)>> {
         let mut override_ = None;
 
         // First check toolchain override from command
         if let Some(ref name) = self.toolchain_override {
-            override_ = Some((name.to_string(), OverrideReason::CommandLine));
+            override_ = Some((name.into(), OverrideReason::CommandLine));
         }
 
         // Check RUSTUP_TOOLCHAIN
         if let Some(ref name) = self.env_override {
-            override_ = Some((name.to_string(), OverrideReason::Environment));
+            override_ = Some((name.into(), OverrideReason::Environment));
         }
 
         // Then walk up the directory tree from 'path' looking for either the
@@ -424,7 +491,7 @@ impl Cfg {
             })?;
         }
 
-        if let Some((name, reason)) = override_ {
+        if let Some((file, reason)) = override_ {
             // This is hackishly using the error chain to provide a bit of
             // extra context about what went wrong. The CLI will display it
             // on a line after the proximate error.
@@ -448,19 +515,22 @@ impl Cfg {
                 ),
             };
 
-            let toolchain = Toolchain::from(self, &name)?;
-            // Overridden toolchains can be literally any string, but only
-            // distributable toolchains will be auto-installed by the wrapping
-            // code; provide a nice error for this common case. (default could
-            // be set badly too, but that is much less common).
-            if !toolchain.exists() && toolchain.is_custom() {
-                // Strip the confusing NotADirectory error and only mention that the
-                // override toolchain is not installed.
-                Err(Error::from(reason_err))
-                    .chain_err(|| ErrorKind::OverrideToolchainNotInstalled(name.to_string()))
-            } else {
-                Ok(Some((toolchain, reason)))
+            let override_cfg = OverrideCfg::from_file(self, file)?;
+            if let Some(toolchain) = &override_cfg.toolchain {
+                // Overridden toolchains can be literally any string, but only
+                // distributable toolchains will be auto-installed by the wrapping
+                // code; provide a nice error for this common case. (default could
+                // be set badly too, but that is much less common).
+                if !toolchain.exists() && toolchain.is_custom() {
+                    // Strip the confusing NotADirectory error and only mention that the
+                    // override toolchain is not installed.
+                    return Err(Error::from(reason_err)).chain_err(|| {
+                        ErrorKind::OverrideToolchainNotInstalled(toolchain.name().into())
+                    });
+                }
             }
+
+            Ok(Some((override_cfg, reason)))
         } else {
             Ok(None)
         }
@@ -470,7 +540,7 @@ impl Cfg {
         &self,
         dir: &Path,
         settings: &Settings,
-    ) -> Result<Option<(String, OverrideReason)>> {
+    ) -> Result<Option<(OverrideFile, OverrideReason)>> {
         let notify = self.notify_handler.as_ref();
         let dir = utils::canonicalize_path(dir, notify);
         let mut dir = Some(&*dir);
@@ -479,14 +549,14 @@ impl Cfg {
             // First check the override database
             if let Some(name) = settings.dir_override(d, notify) {
                 let reason = OverrideReason::OverrideDB(d.to_owned());
-                return Ok(Some((name, reason)));
+                return Ok(Some((name.into(), reason)));
             }
 
             // Then look for 'rust-toolchain'
             let toolchain_file = d.join("rust-toolchain");
-            if let Ok(s) = utils::read_file("toolchain file", &toolchain_file) {
-                if let Some(s) = s.lines().next() {
-                    let toolchain_name = s.trim();
+            if let Ok(contents) = utils::read_file("toolchain file", &toolchain_file) {
+                let override_file = Cfg::parse_override_file(contents)?;
+                if let Some(toolchain_name) = &override_file.toolchain.channel {
                     let all_toolchains = self.list_toolchains()?;
                     if !all_toolchains.iter().any(|s| s == toolchain_name) {
                         // The given name is not resolvable as a toolchain, so
@@ -499,9 +569,10 @@ impl Cfg {
                             )
                         })?;
                     }
-                    let reason = OverrideReason::ToolchainFile(toolchain_file);
-                    return Ok(Some((toolchain_name.to_string(), reason)));
                 }
+
+                let reason = OverrideReason::ToolchainFile(toolchain_file);
+                return Ok(Some((override_file, reason)));
             }
 
             dir = d.parent();
@@ -510,26 +581,100 @@ impl Cfg {
         Ok(None)
     }
 
+    fn parse_override_file<S: AsRef<str>>(contents: S) -> Result<OverrideFile> {
+        let contents = contents.as_ref();
+
+        match contents.lines().count() {
+            0 => return Err(ErrorKind::EmptyOverrideFile.into()),
+            1 => {
+                let channel = contents.trim();
+
+                if channel.is_empty() {
+                    Err(ErrorKind::EmptyOverrideFile.into())
+                } else {
+                    Ok(channel.into())
+                }
+            }
+            _ => {
+                let override_file = toml::from_str::<OverrideFile>(contents)
+                    .map_err(ErrorKind::ParsingOverrideFile)?;
+
+                if override_file.is_empty() {
+                    Err(ErrorKind::InvalidOverrideFile.into())
+                } else {
+                    Ok(override_file)
+                }
+            }
+        }
+    }
+
     pub fn find_or_install_override_toolchain_or_default(
         &self,
         path: &Path,
     ) -> Result<(Toolchain<'_>, Option<OverrideReason>)> {
-        if let Some((toolchain, reason)) =
-            if let Some((toolchain, reason)) = self.find_override(path)? {
-                Some((toolchain, Some(reason)))
-            } else {
-                self.find_default()?.map(|toolchain| (toolchain, None))
+        fn components_exist(
+            distributable: &DistributableToolchain<'_>,
+            components: &[&str],
+            targets: &[&str],
+        ) -> Result<bool> {
+            let components_requested = !components.is_empty() || !targets.is_empty();
+
+            match (distributable.list_components(), components_requested) {
+                // If the toolchain does not support components but there were components requested, bubble up the error
+                (Err(e), true) => Err(e),
+                (Ok(installed_components), _) => {
+                    Ok(components.iter().chain(targets.iter()).all(|name| {
+                        installed_components.iter().any(|status| {
+                            status.component.short_name_in_manifest() == name && status.installed
+                        })
+                    }))
+                }
+                _ => Ok(true),
+            }
+        }
+
+        if let Some((toolchain, components, targets, reason)) =
+            match self.find_override_config(path)? {
+                Some((
+                    OverrideCfg {
+                        toolchain,
+                        components,
+                        targets,
+                    },
+                    reason,
+                )) => {
+                    let default = if toolchain.is_none() {
+                        self.find_default()?
+                    } else {
+                        None
+                    };
+
+                    toolchain
+                        .or(default)
+                        .map(|toolchain| (toolchain, components, targets, Some(reason)))
+                }
+                None => self
+                    .find_default()?
+                    .map(|toolchain| (toolchain, vec![], vec![], None)),
             }
         {
-            if !toolchain.exists() {
-                if toolchain.is_custom() {
+            if toolchain.is_custom() {
+                if !toolchain.exists() {
                     return Err(
                         ErrorKind::ToolchainNotInstalled(toolchain.name().to_string()).into(),
                     );
                 }
+            } else {
+                let components: Vec<_> = components.iter().map(AsRef::as_ref).collect();
+                let targets: Vec<_> = targets.iter().map(AsRef::as_ref).collect();
+
                 let distributable = DistributableToolchain::new(&toolchain)?;
-                distributable.install_from_dist(true, false, &[], &[])?;
+                if !toolchain.exists() || !components_exist(&distributable, &components, &targets)?
+                {
+                    distributable.install_from_dist(true, false, &components, &targets)?;
+                }
             }
+
             Ok((toolchain, reason))
         } else {
             // No override and no default set
@@ -722,3 +867,175 @@ impl Cfg {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn parse_legacy_toolchain_file() {
+        let contents = "nightly-2020-07-10";
+
+        let result = Cfg::parse_override_file(contents);
+        assert_eq!(
+            result.unwrap(),
+            OverrideFile {
+                toolchain: ToolchainSection {
+                    channel: Some(contents.into()),
+                    components: None,
+                    targets: None,
+                }
+            }
+        );
+    }
+
+    #[test]
+    fn parse_toml_toolchain_file() {
+        let contents = r#"[toolchain]
+channel = "nightly-2020-07-10"
+components = [ "rustfmt", "rustc-dev" ]
+targets = [ "wasm32-unknown-unknown", "thumbv2-none-eabi" ]
+"#;
+
+        let result = Cfg::parse_override_file(contents);
+        assert_eq!(
+            result.unwrap(),
+            OverrideFile {
+                toolchain: ToolchainSection {
+                    channel: Some("nightly-2020-07-10".into()),
+                    components: Some(vec!["rustfmt".into(), "rustc-dev".into()]),
+                    targets: Some(vec![
+                        "wasm32-unknown-unknown".into(),
+                        "thumbv2-none-eabi".into()
+                    ]),
+                }
+            }
+        );
+    }
+
+    #[test]
+    fn parse_toml_toolchain_file_only_channel() {
+        let contents = r#"[toolchain]
+channel = "nightly-2020-07-10"
+"#;
+
+        let result = Cfg::parse_override_file(contents);
+        assert_eq!(
+            result.unwrap(),
+            OverrideFile {
+                toolchain: ToolchainSection {
+                    channel: Some("nightly-2020-07-10".into()),
+                    components: None,
+                    targets: None,
+                }
+            }
+        );
+    }
+
+    #[test]
+    fn parse_toml_toolchain_file_empty_components() {
+        let contents = r#"[toolchain]
+channel = "nightly-2020-07-10"
+components = []
+"#;
+
+        let result = Cfg::parse_override_file(contents);
+        assert_eq!(
+            result.unwrap(),
+            OverrideFile {
+                toolchain: ToolchainSection {
+                    channel: Some("nightly-2020-07-10".into()),
+                    components: Some(vec![]),
+                    targets: None,
+                }
+            }
+        );
+    }
+
+    #[test]
+    fn parse_toml_toolchain_file_empty_targets() {
+        let contents = r#"[toolchain]
+channel = "nightly-2020-07-10"
+targets = []
+"#;
+
+        let result = Cfg::parse_override_file(contents);
+        assert_eq!(
+            result.unwrap(),
+            OverrideFile {
+                toolchain: ToolchainSection {
+                    channel: Some("nightly-2020-07-10".into()),
+                    components: None,
+                    targets: Some(vec![]),
+                }
+            }
+        );
+    }
+
+    #[test]
+    fn parse_toml_toolchain_file_no_channel() {
+        let contents = r#"[toolchain]
+components = [ "rustfmt" ]
+"#;
+
+        let result = Cfg::parse_override_file(contents);
+        assert_eq!(
+            result.unwrap(),
+            OverrideFile {
+                toolchain: ToolchainSection {
+                    channel: None,
+                    components: Some(vec!["rustfmt".into()]),
+                    targets: None,
+                }
+            }
+        );
+    }
+
+    #[test]
+    fn parse_empty_toml_toolchain_file() {
+        let contents = r#"
+[toolchain]
+"#;
+
+        let result = Cfg::parse_override_file(contents);
+        assert!(matches!(
+            result.unwrap_err().kind(),
+            ErrorKind::InvalidOverrideFile
+        ));
+    }
+
+    #[test]
+    fn parse_empty_toolchain_file() {
+        let contents = "";
+
+        let result = Cfg::parse_override_file(contents);
+        assert!(matches!(
+            result.unwrap_err().kind(),
+            ErrorKind::EmptyOverrideFile
+        ));
+    }
+
+    #[test]
+    fn parse_whitespace_toolchain_file() {
+        let contents = "   ";
+
+        let result = Cfg::parse_override_file(contents);
+        assert!(matches!(
+            result.unwrap_err().kind(),
+            ErrorKind::EmptyOverrideFile
+        ));
+    }
+
+    #[test]
+    fn parse_toml_syntax_error() {
+        let contents = r#"[toolchain]
+channel = nightly
+"#;
+
+        let result = Cfg::parse_override_file(contents);
+        assert!(matches!(
+            result.unwrap_err().kind(),
+            ErrorKind::ParsingOverrideFile(..)
+        ));
+    }
+}
diff --git a/src/errors.rs b/src/errors.rs
index 71d24a3442..eb411de371 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -374,6 +374,15 @@ error_chain! {
         BrokenPartialFile {
             description("partially downloaded file may have been damaged and was removed, please try again")
         }
+        EmptyOverrideFile {
+            description("empty toolchain override file detected. Please remove it, or else specify the desired toolchain properties in the file")
+        }
+        InvalidOverrideFile {
+            description("missing toolchain properties in toolchain override file")
+        }
+        ParsingOverrideFile(e: toml::de::Error) {
+            description("error parsing override file")
+        }
     }
 }
 
diff --git a/tests/cli-rustup.rs b/tests/cli-rustup.rs
index 2e393a9c6c..28def93ff8 100644
--- a/tests/cli-rustup.rs
+++ b/tests/cli-rustup.rs
@@ -11,8 +11,8 @@ use rustup::test::this_host_triple;
 use rustup::utils::raw;
 
 use crate::mock::clitools::{
-    self, expect_err, expect_ok, expect_ok_ex, expect_stderr_ok, expect_stdout_ok, run,
-    set_current_dist_date, Config, Scenario,
+    self, expect_err, expect_not_stdout_ok, expect_ok, expect_ok_ex, expect_stderr_ok,
+    expect_stdout_ok, run, set_current_dist_date, Config, Scenario,
 };
 
 macro_rules! for_host_and_home {
@@ -1402,6 +1402,152 @@ fn file_override_with_archive() {
     });
 }
 
+#[test]
+fn file_override_toml_format_select_installed_toolchain() {
+    setup(&|config| {
+        expect_ok(config, &["rustup", "default", "stable"]);
+        expect_ok(
+            config,
+            &[
+                "rustup",
+                "toolchain",
+                "install",
+                "nightly-2015-01-01",
+                "--no-self-update",
+            ],
+        );
+
+        expect_stdout_ok(config, &["rustc", "--version"], "hash-stable-1.1.0");
+
+        let cwd = config.current_dir();
+        let toolchain_file = cwd.join("rust-toolchain");
+        raw::write_file(
+            &toolchain_file,
+            r#"
+[toolchain]
+channel = "nightly-2015-01-01"
+"#,
+        )
+        .unwrap();
+
+        expect_stdout_ok(config, &["rustc", "--version"], "hash-nightly-1");
+    });
+}
+
+#[test]
+fn file_override_toml_format_install_both_toolchain_and_components() {
+    setup(&|config| {
+        expect_ok(config, &["rustup", "default", "stable"]);
+        expect_stdout_ok(config, &["rustc", "--version"], "hash-stable-1.1.0");
+        expect_not_stdout_ok(
+            config,
+            &["rustup", "component", "list"],
+            "rust-src (installed)",
+        );
+
+        let cwd = config.current_dir();
+        let toolchain_file = cwd.join("rust-toolchain");
+        raw::write_file(
+            &toolchain_file,
+            r#"
+[toolchain]
+channel = "nightly-2015-01-01"
+components = [ "rust-src" ]
+"#,
+        )
+        .unwrap();
+
+        expect_stdout_ok(config, &["rustc", "--version"], "hash-nightly-1");
+        expect_stdout_ok(
+            config,
+            &["rustup", "component", "list"],
+            "rust-src (installed)",
+        );
+    });
+}
+
+#[test]
+fn file_override_toml_format_add_missing_components() {
+    setup(&|config| {
+        expect_ok(config, &["rustup", "default", "stable"]);
+        expect_not_stdout_ok(
+            config,
+            &["rustup", "component", "list"],
+            "rust-src (installed)",
+        );
+
+        let cwd = config.current_dir();
+        let toolchain_file = cwd.join("rust-toolchain");
+        raw::write_file(
+            &toolchain_file,
+            r#"
+[toolchain]
+components = [ "rust-src" ]
+"#,
+        )
+        .unwrap();
+
+        expect_stdout_ok(
+            config,
+            &["rustup", "component", "list"],
+            "rust-src (installed)",
+        );
+    });
+}
+
+#[test]
+fn file_override_toml_format_add_missing_targets() {
+    setup(&|config| {
+        expect_ok(config, &["rustup", "default", "stable"]);
+        expect_not_stdout_ok(
+            config,
+            &["rustup", "component", "list"],
+            "arm-linux-androideabi (installed)",
+        );
+
+        let cwd = config.current_dir();
+        let toolchain_file = cwd.join("rust-toolchain");
+        raw::write_file(
+            &toolchain_file,
+            r#"
+[toolchain]
+targets = [ "arm-linux-androideabi" ]
+"#,
+        )
+        .unwrap();
+
+        expect_stdout_ok(
+            config,
+            &["rustup", "component", "list"],
+            "arm-linux-androideabi (installed)",
+        );
+    });
+}
+
+#[test]
+fn file_override_toml_format_skip_invalid_component() {
+    setup(&|config| {
+        expect_ok(config, &["rustup", "default", "stable"]);
+
+        let cwd = config.current_dir();
+        let toolchain_file = cwd.join("rust-toolchain");
+        raw::write_file(
+            &toolchain_file,
+            r#"
+[toolchain]
+components = [ "rust-bongo" ]
+"#,
+        )
+        .unwrap();
+
+        expect_stderr_ok(
+            config,
+            &["rustc", "--version"],
+            "warning: Force-skipping unavailable component 'rust-bongo",
+        );
+    });
+}
+
 #[test]
 fn directory_override_beats_file_override() {
     setup(&|config| {