diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fe6bea9d8dc3f..e04b1bdfefdd8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -494,16 +494,11 @@ the version in `Cargo.lock`, so the build can no longer continue.
 To resolve this, we need to update `Cargo.lock`. Luckily, cargo provides a
 command to do this easily.
 
-First, go into the `src/` directory since that is where `Cargo.toml` is in
-the rust repository. Then run, `cargo update -p rustfmt-nightly` to solve
-the problem.
-
 ```
-$ cd src
 $ cargo update -p rustfmt-nightly
 ```
 
-This should change the version listed in `src/Cargo.lock` to the new version you updated
+This should change the version listed in `Cargo.lock` to the new version you updated
 the submodule to. Running `./x.py build` should work now.
 
 ## Writing Documentation
diff --git a/src/Cargo.lock b/Cargo.lock
similarity index 100%
rename from src/Cargo.lock
rename to Cargo.lock
diff --git a/src/Cargo.toml b/Cargo.toml
similarity index 53%
rename from src/Cargo.toml
rename to Cargo.toml
index e8c44ea57c2e9..1e1d7a40967b3 100644
--- a/src/Cargo.toml
+++ b/Cargo.toml
@@ -1,31 +1,31 @@
 [workspace]
 members = [
-  "bootstrap",
-  "rustc",
-  "libstd",
-  "libtest",
-  "librustc_codegen_llvm",
-  "tools/cargotest",
-  "tools/clippy",
-  "tools/compiletest",
-  "tools/error_index_generator",
-  "tools/linkchecker",
-  "tools/rustbook",
-  "tools/unstable-book-gen",
-  "tools/tidy",
-  "tools/build-manifest",
-  "tools/remote-test-client",
-  "tools/remote-test-server",
-  "tools/rust-installer",
-  "tools/cargo",
-  "tools/rustdoc",
-  "tools/rls",
-  "tools/rustfmt",
-  "tools/miri",
-  "tools/rustdoc-themes",
+  "src/bootstrap",
+  "src/rustc",
+  "src/libstd",
+  "src/libtest",
+  "src/librustc_codegen_llvm",
+  "src/tools/cargotest",
+  "src/tools/clippy",
+  "src/tools/compiletest",
+  "src/tools/error_index_generator",
+  "src/tools/linkchecker",
+  "src/tools/rustbook",
+  "src/tools/unstable-book-gen",
+  "src/tools/tidy",
+  "src/tools/build-manifest",
+  "src/tools/remote-test-client",
+  "src/tools/remote-test-server",
+  "src/tools/rust-installer",
+  "src/tools/cargo",
+  "src/tools/rustdoc",
+  "src/tools/rls",
+  "src/tools/rustfmt",
+  "src/tools/miri",
+  "src/tools/rustdoc-themes",
 ]
 exclude = [
-  "tools/rls/test_data",
+  "src/tools/rls/test_data",
 ]
 
 # Curiously, LLVM 7.0 will segfault if compiled with opt-level=3
@@ -50,18 +50,18 @@ debug-assertions = false
 # so we use a `[patch]` here to override the github repository with our local
 # vendored copy.
 [patch."https://github.com/rust-lang/cargo"]
-cargo = { path = "tools/cargo" }
+cargo = { path = "src/tools/cargo" }
 
 [patch.crates-io]
 # Similar to Cargo above we want the RLS to use a vendored version of `rustfmt`
 # that we're shipping as well (to ensure that the rustfmt in RLS and the
 # `rustfmt` executable are the same exact version).
-rustfmt-nightly = { path = "tools/rustfmt" }
+rustfmt-nightly = { path = "src/tools/rustfmt" }
 
-# See comments in `tools/rustc-workspace-hack/README.md` for what's going on
+# See comments in `src/tools/rustc-workspace-hack/README.md` for what's going on
 # here
-rustc-workspace-hack = { path = 'tools/rustc-workspace-hack' }
+rustc-workspace-hack = { path = 'src/tools/rustc-workspace-hack' }
 
 [patch."https://github.com/rust-lang-nursery/rust-clippy"]
-clippy_lints = { path = "tools/clippy/clippy_lints" }
-rustc_tools_util = { path = "tools/clippy/rustc_tools_util" }
+clippy_lints = { path = "src/tools/clippy/clippy_lints" }
+rustc_tools_util = { path = "src/tools/clippy/rustc_tools_util" }
diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py
index cd48e6aa4c4ba..d143dffb24be5 100644
--- a/src/bootstrap/bootstrap.py
+++ b/src/bootstrap/bootstrap.py
@@ -801,7 +801,7 @@ def bootstrap(help_triggered):
                 registry = 'https://example.com'
 
                 [source.vendored-sources]
-                directory = '{}/src/vendor'
+                directory = '{}/vendor'
             """.format(build.rust_root))
     else:
         if os.path.exists('.cargo'):
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index 0aab64465fd1c..cd8d5642b2570 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -851,7 +851,7 @@ impl Step for Src {
         t!(fs::create_dir_all(&dst_src));
 
         let src_files = [
-            "src/Cargo.lock",
+            "Cargo.lock",
         ];
         // This is the reduced set of paths which will become the rust-src component
         // (essentially libstd and all of its path dependencies)
@@ -949,6 +949,8 @@ impl Step for PlainSourceTarball {
             "configure",
             "x.py",
             "config.toml.example",
+            "Cargo.toml",
+            "Cargo.lock",
         ];
         let src_dirs = [
             "src",
@@ -992,7 +994,7 @@ impl Step for PlainSourceTarball {
             // Vendor all Cargo dependencies
             let mut cmd = Command::new(&builder.initial_cargo);
             cmd.arg("vendor")
-               .current_dir(&plain_dst_src.join("src"));
+               .current_dir(&plain_dst_src);
             builder.run(&mut cmd);
         }
 
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index e55773011df8e..c50e6a270339f 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -1934,6 +1934,7 @@ impl Step for Distcheck {
                 .arg("generate-lockfile")
                 .arg("--manifest-path")
                 .arg(&toml)
+                .env("__CARGO_TEST_ROOT", &dir)
                 .current_dir(&dir),
         );
     }
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index d8e86644ee054..4acc739db57bf 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -260,8 +260,13 @@ pub fn prepare_tool_cargo(
 }
 
 macro_rules! tool {
-    ($($name:ident, $path:expr, $tool_name:expr, $mode:expr
-        $(,llvm_tools = $llvm:expr)* $(,is_external_tool = $external:expr)*;)+) => {
+    ($(
+        $name:ident, $path:expr, $tool_name:expr, $mode:expr
+        $(,llvm_tools = $llvm:expr)*
+        $(,is_external_tool = $external:expr)*
+        $(,cargo_test_root = $cargo_test_root:expr)*
+        ;
+    )+) => {
         #[derive(Copy, PartialEq, Eq, Clone)]
         pub enum Tool {
             $(
@@ -283,6 +288,15 @@ macro_rules! tool {
                     $(Tool::$name => false $(|| $llvm)*,)+
                 }
             }
+
+            /// Whether this tool requires may run Cargo for test crates,
+            /// which currently needs setting the environment variable
+            /// `__CARGO_TEST_ROOT` to separate it from the workspace.
+            pub fn needs_cargo_test_root(&self) -> bool {
+                match self {
+                    $(Tool::$name => false $(|| $cargo_test_root)*,)+
+                }
+            }
         }
 
         impl<'a> Builder<'a> {
@@ -358,8 +372,9 @@ tool!(
     UnstableBookGen, "src/tools/unstable-book-gen", "unstable-book-gen", Mode::ToolBootstrap;
     Tidy, "src/tools/tidy", "tidy", Mode::ToolBootstrap;
     Linkchecker, "src/tools/linkchecker", "linkchecker", Mode::ToolBootstrap;
-    CargoTest, "src/tools/cargotest", "cargotest", Mode::ToolBootstrap;
-    Compiletest, "src/tools/compiletest", "compiletest", Mode::ToolBootstrap, llvm_tools = true;
+    CargoTest, "src/tools/cargotest", "cargotest", Mode::ToolBootstrap, cargo_test_root = true;
+    Compiletest, "src/tools/compiletest", "compiletest", Mode::ToolBootstrap,
+        llvm_tools = true, cargo_test_root = true;
     BuildManifest, "src/tools/build-manifest", "build-manifest", Mode::ToolBootstrap;
     RemoteTestClient, "src/tools/remote-test-client", "remote-test-client", Mode::ToolBootstrap;
     RustInstaller, "src/tools/rust-installer", "fabricate", Mode::ToolBootstrap,
@@ -678,6 +693,11 @@ impl<'a> Builder<'a> {
             }
         }
 
+        // Set `__CARGO_TEST_ROOT` to the build directory if needed.
+        if tool.needs_cargo_test_root() {
+            cmd.env("__CARGO_TEST_ROOT", &self.config.out);
+        }
+
         add_lib_path(lib_paths, cmd);
     }
 
diff --git a/src/test/rustdoc-ui/failed-doctest-output.stdout b/src/test/rustdoc-ui/failed-doctest-output.stdout
index 527f1355a9ef7..cab7bda6c6721 100644
--- a/src/test/rustdoc-ui/failed-doctest-output.stdout
+++ b/src/test/rustdoc-ui/failed-doctest-output.stdout
@@ -12,7 +12,7 @@ error[E0425]: cannot find value `no` in this scope
 3 | no
   | ^^ not found in this scope
 
-thread '$DIR/failed-doctest-output.rs - OtherStruct (line 27)' panicked at 'couldn't compile the test', librustdoc/test.rs:323:13
+thread '$DIR/failed-doctest-output.rs - OtherStruct (line 27)' panicked at 'couldn't compile the test', src/librustdoc/test.rs:323:13
 note: Run with `RUST_BACKTRACE=1` for a backtrace.
 
 ---- $DIR/failed-doctest-output.rs - SomeStruct (line 21) stdout ----
@@ -21,7 +21,7 @@ thread '$DIR/failed-doctest-output.rs - SomeStruct (line 21)' panicked at 'test
 thread 'main' panicked at 'oh no', $DIR/failed-doctest-output.rs:3:1
 note: Run with `RUST_BACKTRACE=1` for a backtrace.
 
-', librustdoc/test.rs:358:17
+', src/librustdoc/test.rs:358:17
 
 
 failures:
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index 2dfb9fc68ac16..a40ae8894d5e7 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -8,7 +8,7 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-//! Check license of third-party deps by inspecting src/vendor
+//! Check license of third-party deps by inspecting vendor
 
 use std::collections::{BTreeSet, HashSet, HashMap};
 use std::fs::File;
@@ -203,7 +203,7 @@ impl<'a> From<CrateVersion<'a>> for Crate<'a> {
 /// Specifically, this checks that the license is correct.
 pub fn check(path: &Path, bad: &mut bool) {
     // Check licences
-    let path = path.join("vendor");
+    let path = path.join("../vendor");
     assert!(path.exists(), "vendor directory missing");
     let mut saw_dir = false;
     for dir in t!(path.read_dir()) {
@@ -215,7 +215,7 @@ pub fn check(path: &Path, bad: &mut bool) {
             dir.path()
                 .to_str()
                 .unwrap()
-                .contains(&format!("src/vendor/{}", exception))
+                .contains(&format!("vendor/{}", exception))
         });
         if is_exception {
             continue;
@@ -304,7 +304,7 @@ fn get_deps(path: &Path, cargo: &Path) -> Resolve {
         .arg("--format-version")
         .arg("1")
         .arg("--manifest-path")
-        .arg(path.join("Cargo.toml"))
+        .arg(path.join("../Cargo.toml"))
         .output()
         .expect("Unable to run `cargo metadata`")
         .stdout;
diff --git a/src/tools/tidy/src/extdeps.rs b/src/tools/tidy/src/extdeps.rs
index 7f58b440a833e..a78d2d4ee4e15 100644
--- a/src/tools/tidy/src/extdeps.rs
+++ b/src/tools/tidy/src/extdeps.rs
@@ -21,8 +21,8 @@ const WHITELISTED_SOURCES: &[&str] = &[
 
 /// check for external package sources
 pub fn check(path: &Path, bad: &mut bool) {
-    // Cargo.lock of rust: src/Cargo.lock
-    let path = path.join("Cargo.lock");
+    // Cargo.lock of rust (tidy runs inside src/)
+    let path = path.join("../Cargo.lock");
 
     // open and read the whole file
     let mut cargo_lock = String::new();
diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs
index e235de9c5e138..700103d35d88c 100644
--- a/src/tools/tidy/src/lib.rs
+++ b/src/tools/tidy/src/lib.rs
@@ -64,7 +64,6 @@ fn filter_dirs(path: &Path) -> bool {
         "src/librustc_data_structures/owning_ref",
         "src/compiler-rt",
         "src/liblibc",
-        "src/vendor",
         "src/rt/hoedown",
         "src/tools/cargo",
         "src/tools/clang",
@@ -78,6 +77,7 @@ fn filter_dirs(path: &Path) -> bool {
         "src/target",
         "src/stdsimd",
         "target",
+        "vendor",
     ];
     skip.iter().any(|p| path.ends_with(p))
 }