diff --git a/Cargo.lock b/Cargo.lock
index 74bb3c8e6c285..152cd0488ac8b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5491,7 +5491,6 @@ dependencies = [
  "getopts",
  "panic_abort",
  "panic_unwind",
- "proc_macro",
  "std",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index 15cbb2659c9b3..bee432f9a66a4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,7 @@ members = [
   "compiler/rustc",
   "library/std",
   "library/test",
+  "library/proc_macro",
   "src/rustdoc-json-types",
   "src/tools/build_helper",
   "src/tools/cargotest",
diff --git a/library/test/Cargo.toml b/library/test/Cargo.toml
index 18cb023d274a4..91a1abde059f6 100644
--- a/library/test/Cargo.toml
+++ b/library/test/Cargo.toml
@@ -12,23 +12,3 @@ std = { path = "../std" }
 core = { path = "../core" }
 panic_unwind = { path = "../panic_unwind" }
 panic_abort = { path = "../panic_abort" }
-
-# not actually used but needed to always have proc_macro in the sysroot
-proc_macro = { path = "../proc_macro" }
-
-# Forward features to the `std` crate as necessary
-[features]
-default = ["std_detect_file_io", "std_detect_dlsym_getauxval", "panic-unwind"]
-backtrace = ["std/backtrace"]
-compiler-builtins-c = ["std/compiler-builtins-c"]
-compiler-builtins-mem = ["std/compiler-builtins-mem"]
-compiler-builtins-no-asm = ["std/compiler-builtins-no-asm"]
-compiler-builtins-mangled-names = ["std/compiler-builtins-mangled-names"]
-llvm-libunwind = ["std/llvm-libunwind"]
-system-llvm-libunwind = ["std/system-llvm-libunwind"]
-panic-unwind = ["std/panic_unwind"]
-panic_immediate_abort = ["std/panic_immediate_abort"]
-profiler = ["std/profiler"]
-std_detect_file_io = ["std/std_detect_file_io"]
-std_detect_dlsym_getauxval = ["std/std_detect_dlsym_getauxval"]
-std_detect_env_override = ["std/std_detect_env_override"]
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index b33fc02f49c24..e8e4f42d262af 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -422,8 +422,8 @@ impl<'a> ShouldRun<'a> {
         }
     }
 
-    /// Indicates it should run if the command-line selects the given crate or
-    /// any of its (local) dependencies.
+    /// Indicates it should run if the command-line selects the given crates or
+    /// any of their (local) dependencies.
     ///
     /// Compared to `krate`, this treats the dependencies as aliases for the
     /// same job. Generally it is preferred to use `krate`, and treat each
@@ -431,9 +431,9 @@ impl<'a> ShouldRun<'a> {
     /// (which uses `krate`) will test just `liballoc`. However, `./x.py check
     /// src/liballoc` (which uses `all_krates`) will check all of `libtest`.
     /// `all_krates` should probably be removed at some point.
-    pub fn all_krates(mut self, name: &str) -> Self {
+    pub fn all_krates(mut self, names: &[&str]) -> Self {
         let mut set = BTreeSet::new();
-        for krate in self.builder.in_tree_crates(name, None) {
+        for krate in self.builder.in_tree_crates(names, None) {
             let path = krate.local_path(self.builder);
             set.insert(TaskPath { path, kind: Some(self.kind) });
         }
@@ -445,8 +445,8 @@ impl<'a> ShouldRun<'a> {
     /// any of its (local) dependencies.
     ///
     /// `make_run` will be called a single time with all matching command-line paths.
-    pub fn crate_or_deps(self, name: &str) -> Self {
-        let crates = self.builder.in_tree_crates(name, None);
+    pub fn crates_or_deps(self, names: &[&str]) -> Self {
+        let crates = self.builder.in_tree_crates(names, None);
         self.crates(crates)
     }
 
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index cd19667139ab6..a5ff116b33910 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -79,7 +79,7 @@ impl Step for Std {
     const DEFAULT: bool = true;
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-        run.all_krates("test").path("library")
+        run.all_krates(&["test", "proc_macro"]).path("library")
     }
 
     fn make_run(run: RunConfig<'_>) {
@@ -99,7 +99,7 @@ impl Step for Std {
             target,
             cargo_subcommand(builder.kind),
         );
-        std_cargo(builder, target, compiler.stage, &mut cargo);
+        std_cargo(builder, target, compiler.stage, &mut cargo, true);
         if matches!(builder.config.cmd, Subcommand::Fix { .. }) {
             // By default, cargo tries to fix all targets. Tell it not to fix tests until we've added `test` to the sysroot.
             cargo.arg("--lib");
@@ -158,12 +158,12 @@ impl Step for Std {
             cargo.arg("--all-targets");
         }
 
-        std_cargo(builder, target, compiler.stage, &mut cargo);
+        std_cargo(builder, target, compiler.stage, &mut cargo, true);
 
         // Explicitly pass -p for all dependencies krates -- this will force cargo
         // to also check the tests/benches/examples for these crates, rather
         // than just the leaf crate.
-        for krate in builder.in_tree_crates("test", Some(target)) {
+        for krate in builder.in_tree_crates(&["test", "proc_macro"], Some(target)) {
             cargo.arg("-p").arg(krate.name);
         }
 
@@ -202,7 +202,7 @@ impl Step for Rustc {
     const DEFAULT: bool = true;
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-        run.all_krates("rustc-main").path("compiler")
+        run.all_krates(&["rustc-main"]).path("compiler")
     }
 
     fn make_run(run: RunConfig<'_>) {
@@ -248,7 +248,7 @@ impl Step for Rustc {
         // Explicitly pass -p for all compiler krates -- this will force cargo
         // to also check the tests/benches/examples for these crates, rather
         // than just the leaf crate.
-        for krate in builder.in_tree_crates("rustc-main", Some(target)) {
+        for krate in builder.in_tree_crates(&["rustc-main"], Some(target)) {
             cargo.arg("-p").arg(krate.name);
         }
 
diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs
index 7ebd0a8f27069..7e75ba0d64ee4 100644
--- a/src/bootstrap/clean.rs
+++ b/src/bootstrap/clean.rs
@@ -36,7 +36,7 @@ impl Step for CleanAll {
 }
 
 macro_rules! clean_crate_tree {
-    ( $( $name:ident, $mode:path, $root_crate:literal);+ $(;)? ) => { $(
+    ( $( $name:ident, $mode:path, $root_crates:expr);+ $(;)? ) => { $(
         #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
         pub struct $name {
             compiler: Compiler,
@@ -47,7 +47,7 @@ macro_rules! clean_crate_tree {
             type Output = ();
 
             fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-                let crates = run.builder.in_tree_crates($root_crate, None);
+                let crates = run.builder.in_tree_crates(&$root_crates, None);
                 run.crates(crates)
             }
 
@@ -80,8 +80,8 @@ macro_rules! clean_crate_tree {
 }
 
 clean_crate_tree! {
-    Rustc, Mode::Rustc, "rustc-main";
-    Std, Mode::Std, "test";
+    Rustc, Mode::Rustc, ["rustc-main"];
+    Std, Mode::Std, ["test", "proc_macro"];
 }
 
 fn clean_default(build: &Build, all: bool) {
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index 8b80dfc0f9b97..386ba664274b9 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -55,7 +55,7 @@ impl Step for Std {
         // When downloading stage1, the standard library has already been copied to the sysroot, so
         // there's no need to rebuild it.
         let builder = run.builder;
-        run.crate_or_deps("test")
+        run.crates_or_deps(&["test", "proc_macro"])
             .path("library")
             .lazy_default_condition(Box::new(|| !builder.download_rustc()))
     }
@@ -137,7 +137,7 @@ impl Step for Std {
         target_deps.extend(copy_self_contained_objects(builder, &compiler, target));
 
         let mut cargo = builder.cargo(compiler, Mode::Std, SourceType::InTree, target, "build");
-        std_cargo(builder, target, compiler.stage, &mut cargo);
+        std_cargo(builder, target, compiler.stage, &mut cargo, true);
         for krate in &*self.crates {
             cargo.arg("-p").arg(krate);
         }
@@ -309,7 +309,13 @@ 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: TargetSelection, stage: u32, cargo: &mut Cargo) {
+pub fn std_cargo(
+    builder: &Builder<'_>,
+    target: TargetSelection,
+    stage: u32,
+    cargo: &mut Cargo,
+    add_crates: bool,
+) {
     if let Some(target) = env::var_os("MACOSX_STD_DEPLOYMENT_TARGET") {
         cargo.env("MACOSX_DEPLOYMENT_TARGET", target);
     }
@@ -363,11 +369,15 @@ pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, stage: u32, car
         features += &builder.std_features(target);
         features.push_str(compiler_builtins_c_feature);
 
+        if add_crates {
+            cargo.args(&["-p", "proc_macro"]).args(&["-p", "std"]).args(&["-p", "test"]);
+        }
+
         cargo
             .arg("--features")
             .arg(features)
             .arg("--manifest-path")
-            .arg(builder.src.join("library/test/Cargo.toml"));
+            .arg(builder.src.join("Cargo.toml"));
 
         // Help the libc crate compile by assisting it in finding various
         // sysroot native libraries.
@@ -614,7 +624,7 @@ impl Step for Rustc {
     const DEFAULT: bool = false;
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-        let mut crates = run.builder.in_tree_crates("rustc-main", None);
+        let mut crates = run.builder.in_tree_crates(&["rustc-main"], None);
         for (i, krate) in crates.iter().enumerate() {
             if krate.name == "rustc-main" {
                 crates.swap_remove(i);
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index cc80763ef4495..a80423f7f531c 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -438,7 +438,9 @@ impl Step for Std {
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
         let builder = run.builder;
-        run.all_krates("test").path("library").default_condition(builder.config.docs)
+        run.all_krates(&["test", "proc_macro"])
+            .path("library")
+            .default_condition(builder.config.docs)
     }
 
     fn make_run(run: RunConfig<'_>) {
@@ -592,7 +594,7 @@ fn doc_std(
 
     let run_cargo_rustdoc_for = |package: &str| {
         let mut cargo = builder.cargo(compiler, Mode::Std, SourceType::InTree, target, "rustdoc");
-        compile::std_cargo(builder, target, compiler.stage, &mut cargo);
+        compile::std_cargo(builder, target, compiler.stage, &mut cargo, false);
         cargo
             .arg("--target-dir")
             .arg(&*target_dir.to_string_lossy())
@@ -635,7 +637,7 @@ impl Step for Rustc {
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
         let builder = run.builder;
-        run.crate_or_deps("rustc-main")
+        run.crates_or_deps(&["rustc-main"])
             .path("compiler")
             .default_condition(builder.config.compiler_docs)
     }
@@ -721,7 +723,7 @@ impl Step for Rustc {
         };
         // Find dependencies for top level crates.
         let compiler_crates = root_crates.iter().flat_map(|krate| {
-            builder.in_tree_crates(krate, Some(target)).into_iter().map(|krate| krate.name)
+            builder.in_tree_crates(&[krate], Some(target)).into_iter().map(|krate| krate.name)
         });
 
         let mut to_open = None;
@@ -759,7 +761,7 @@ macro_rules! tool_doc {
 
             fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
                 let builder = run.builder;
-                run.crate_or_deps($should_run).default_condition(builder.config.compiler_docs)
+                run.crates_or_deps(&[$should_run]).default_condition(builder.config.compiler_docs)
             }
 
             fn make_run(run: RunConfig<'_>) {
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index f4abdf1cc5758..7a47756a80599 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -1316,12 +1316,12 @@ impl Build {
         }
     }
 
-    /// Returns a Vec of all the dependencies of the given root crate,
-    /// including transitive dependencies and the root itself. Only includes
+    /// Returns a Vec of all the dependencies of the given root crates,
+    /// including transitive dependencies and the roots itself. Only includes
     /// "local" crates (those in the local source tree, not from a registry).
-    fn in_tree_crates(&self, root: &str, target: Option<TargetSelection>) -> Vec<&Crate> {
+    fn in_tree_crates(&self, roots: &[&str], target: Option<TargetSelection>) -> Vec<&Crate> {
         let mut ret = Vec::new();
-        let mut list = vec![INTERNER.intern_str(root)];
+        let mut list: Vec<_> = roots.iter().map(|root| INTERNER.intern_str(*root)).collect();
         let mut visited = HashSet::new();
         while let Some(krate) = list.pop() {
             let krate = self
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index b4f1506dc8f30..0074c323c1a73 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -2052,7 +2052,7 @@ impl Step for CrateLibrustc {
     const ONLY_HOSTS: bool = true;
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-        run.crate_or_deps("rustc-main")
+        run.crates_or_deps(&["rustc-main"])
     }
 
     fn make_run(run: RunConfig<'_>) {
@@ -2094,7 +2094,7 @@ impl Step for Crate {
     const DEFAULT: bool = true;
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-        run.crate_or_deps("test")
+        run.crates_or_deps(&["test", "proc_macro"])
     }
 
     fn make_run(run: RunConfig<'_>) {
@@ -2138,7 +2138,7 @@ impl Step for Crate {
             builder.cargo(compiler, mode, SourceType::InTree, target, test_kind.subcommand());
         match mode {
             Mode::Std => {
-                compile::std_cargo(builder, target, compiler.stage, &mut cargo);
+                compile::std_cargo(builder, target, compiler.stage, &mut cargo, true);
             }
             Mode::Rustc => {
                 compile::rustc_cargo(builder, &mut cargo, target);