Move run_compiler and its testonly helper functions into separate crates.

This was a bit hairy, but I learned a bit about how things work, so mission
accomplished.

PiperOrigin-RevId: 570198121
Change-Id: Ie3f4ca8ad0809551a4342af1b0f778ec115faf10
diff --git a/cc_bindings_from_rs/BUILD b/cc_bindings_from_rs/BUILD
index 3df7a5c..9227410 100644
--- a/cc_bindings_from_rs/BUILD
+++ b/cc_bindings_from_rs/BUILD
@@ -19,7 +19,6 @@
         "bindings.rs",
         "cc_bindings_from_rs.rs",
         "cmdline.rs",
-        "run_compiler.rs",
     ],
     crate_root = "cc_bindings_from_rs.rs",
     # TODO(b/242703401): Remove once cc_common.link works for rustc libraries.
@@ -31,6 +30,7 @@
         "//visibility:public",
     ],
     deps = [
+        ":run_compiler",
         ":toposort",
         "//common:code_gen_utils",
         "//common:token_stream_printer",
@@ -49,15 +49,12 @@
 crubit_rust_test(
     name = "cc_bindings_from_rs_test",
     crate = ":cc_bindings_from_rs",
-    data = [
-        "@rust_linux_x86_64__x86_64-unknown-linux-gnu__nightly_tools//:rust_std-x86_64-unknown-linux-gnu",
-    ],
-    rustc_flags = ["--cfg=oss"],
     tags = [
         "not_build:arm",
         "noubsan",  # rustc-as-a-library isn't supported for UBSan.
     ],
     deps = [
+        ":run_compiler_test_support",
         "//common:token_stream_matchers",
         "@crate_index//:regex",
         "@crate_index//:tempfile",
@@ -77,6 +74,59 @@
 )
 
 rust_library(
+    name = "run_compiler",
+    srcs = [
+        "run_compiler.rs",
+    ],
+    deps = [
+        "@crate_index//:anyhow",
+        "@crate_index//:either",
+        "@crate_index//:once_cell",
+    ],
+)
+
+crubit_rust_test(
+    name = "run_compiler_test",
+    crate = ":run_compiler",
+    tags = [
+        "not_build:arm",
+        "noubsan",  # rustc-as-a-library isn't supported for UBSan.
+    ],
+    deps = [
+        ":run_compiler_test_support",
+        "@crate_index//:tempfile",
+    ],
+)
+
+rust_library(
+    name = "run_compiler_test_support",
+    testonly = True,
+    srcs = [
+        "run_compiler_test_support.rs",
+    ],
+    data = [
+        "@rust_linux_x86_64__x86_64-unknown-linux-gnu__nightly_tools//:rust_std-x86_64-unknown-linux-gnu",
+    ],
+    rustc_flags = ["--cfg=oss"],
+    deps = [
+        "@crate_index//:anyhow",
+        "@crate_index//:either",
+        "@crate_index//:once_cell",
+        "@rules_rust//tools/runfiles",
+    ],
+)
+
+crubit_rust_test(
+    name = "run_compiler_test_support_test",
+    crate = ":run_compiler_test_support",
+    rustc_flags = ["--cfg=oss"],
+    tags = [
+        "not_build:arm",
+        "noubsan",  # rustc-as-a-library isn't supported for UBSan.
+    ],
+)
+
+rust_library(
     name = "toposort",
     srcs = ["toposort.rs"],
 )
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index 9ea3f85..4ac214c 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -2410,8 +2410,8 @@
     use rustc_middle::ty::{Ty, TyCtxt};
     use rustc_span::def_id::LocalDefId;
 
-    use crate::run_compiler::tests::run_compiler_for_testing;
     use code_gen_utils::format_cc_includes;
+    use run_compiler_test_support::run_compiler_for_testing;
     use token_stream_matchers::{
         assert_cc_matches, assert_cc_not_matches, assert_rs_matches, assert_rs_not_matches,
     };
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs.rs b/cc_bindings_from_rs/cc_bindings_from_rs.rs
index fb8fe83..573457e 100644
--- a/cc_bindings_from_rs/cc_bindings_from_rs.rs
+++ b/cc_bindings_from_rs/cc_bindings_from_rs.rs
@@ -22,11 +22,9 @@
 extern crate rustc_trait_selection;
 extern crate rustc_type_ir;
 
-// TODO(b/254679226): `bindings`, `cmdline`, and `run_compiler` should be
-// separate crates.
+// TODO(b/254679226): these should be separate crates.
 mod bindings;
 mod cmdline;
-mod run_compiler;
 
 use anyhow::Context;
 use itertools::Itertools;
@@ -148,9 +146,9 @@
 mod tests {
     use super::run_with_cmdline_args;
 
-    use crate::run_compiler::tests::get_sysroot_for_testing;
     use itertools::Itertools;
     use regex::{Regex, RegexBuilder};
+    use run_compiler_test_support::get_sysroot_for_testing;
     use std::path::PathBuf;
     use tempfile::{tempdir, TempDir};
     use token_stream_printer::{CLANG_FORMAT_EXE_PATH_FOR_TESTING, RUSTFMT_EXE_PATH_FOR_TESTING};
@@ -409,9 +407,8 @@
     /// results in an error.
     ///
     /// This is tested at the `cc_bindings_from_rs.rs` level instead of at the
-    /// `bindings.rs` level,
-    /// because `run_compiler::tests::run_compiler_for_testing` doesn't support
-    /// specifying a custom panic mechanism.
+    /// `bindings.rs` level, because `run_compiler_test_support` doesn't
+    /// support specifying a custom panic mechanism.
     #[test]
     fn test_rustc_unsupported_panic_mechanism() -> anyhow::Result<()> {
         let err = TestArgs::default_args()?
diff --git a/cc_bindings_from_rs/run_compiler.rs b/cc_bindings_from_rs/run_compiler.rs
index 27d60c0..e2395c3 100644
--- a/cc_bindings_from_rs/run_compiler.rs
+++ b/cc_bindings_from_rs/run_compiler.rs
@@ -3,7 +3,20 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 //! The `run_compiler` crate mostly wraps and simplifies a subset of APIs
-//! from the `rustc_driver` module, providing an easy way to `run_compiler`.
+//! from the `rustc_driver` crate, providing an easy way to `run_compiler`.
+#![feature(rustc_private)]
+#![deny(rustc::internal)]
+
+extern crate rustc_driver;
+extern crate rustc_error_codes;
+extern crate rustc_errors;
+extern crate rustc_feature;
+extern crate rustc_interface;
+extern crate rustc_lint_defs;
+extern crate rustc_middle;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
 
 use anyhow::anyhow;
 use either::Either;
@@ -111,7 +124,7 @@
     R: Send,
 {
     /// Configures how `rustc` internals work when invoked via `run_compiler`.
-    /// Note that `run_compiler_for_testing` uses a separate `Config`.
+    /// Note that `run_compiler_test_support` uses a separate `Config`.
     fn config(&mut self, config: &mut rustc_interface::interface::Config) {
         // Silence warnings in the target crate to avoid reporting them twice: once when
         // compiling the crate via `rustc` and once when "compiling" the crate
@@ -129,7 +142,13 @@
         _compiler: &Compiler,
         queries: &'tcx Queries<'tcx>,
     ) -> rustc_driver::Compilation {
-        let rustc_result = enter_tcx(queries, |tcx| {
+        // `after_analysis` is only called by `rustc_driver` if earlier compiler
+        // analysis was successful (which as the *last* compilation phase
+        // presumably covers *all* errors).
+        let mut query_context = queries
+            .global_ctxt()
+            .expect("Expecting no compile errors inside `after_analysis` callback.");
+        query_context.enter(|tcx| {
             let callback = {
                 let temporary_placeholder = Either::Right(Err(anyhow::anyhow!("unused")));
                 std::mem::replace(&mut self.callback_or_result, temporary_placeholder)
@@ -138,36 +157,14 @@
             self.callback_or_result = Either::Right(callback(tcx));
         });
 
-        // `expect`ing no errors in `rustc_result`, because `after_analysis` is only
-        // called by `rustc_driver` if earlier compiler analysis was successful
-        // (which as the *last* compilation phase presumably covers *all*
-        // errors).
-        rustc_result.expect("Expecting no compile errors inside `after_analysis` callback.");
-
         rustc_driver::Compilation::Stop
     }
 }
 
-/// Helper (used by `run_compiler` and `run_compiler_for_testing`) for invoking
-/// functions operating on `TyCtxt`.
-fn enter_tcx<'tcx, F, T>(
-    queries: &'tcx Queries<'tcx>,
-    f: F,
-) -> rustc_interface::interface::Result<T>
-where
-    F: FnOnce(TyCtxt<'tcx>) -> T + Send,
-    T: Send,
-{
-    let mut query_context = queries.global_ctxt()?;
-    Ok(query_context.enter(f))
-}
-
 #[cfg(test)]
-pub mod tests {
-    use super::run_compiler;
-
-    use rustc_middle::ty::TyCtxt; // See also <internal link>/ty.html#import-conventions
-    use std::path::PathBuf;
+mod tests {
+    use super::*;
+    use run_compiler_test_support::get_sysroot_for_testing;
     use tempfile::tempdir;
 
     const DEFAULT_RUST_SOURCE_FOR_TESTING: &'static str = r#" pub mod public_module {
@@ -248,164 +245,4 @@
         assert!(!out_path.exists());
         Ok(())
     }
-
-    #[cfg(oss)]
-    const TOOLCHAIN_ROOT: &str = "rust_linux_x86_64__x86_64-unknown-linux-gnu__nightly_tools/rust_toolchain/lib/rustlib/x86_64-unknown-linux-gnu";
-    #[cfg(not(oss))]
-    const TOOLCHAIN_ROOT: &str =
-        "google3/nowhere/llvm/rust/main_sysroot";
-
-    /// Returns the `rustc` sysroot that is suitable for the environment where
-    /// unit tests run.
-    ///
-    /// The sysroot is used internally by `run_compiler_for_testing`, but it may
-    /// also be passed as `--sysroot=...` in `rustc_args` argument of
-    /// `run_compiler`
-    pub fn get_sysroot_for_testing() -> PathBuf {
-        let runfiles = runfiles::Runfiles::create().unwrap();
-        let loc = runfiles.rlocation(std::path::Path::new(TOOLCHAIN_ROOT));
-        assert!(loc.exists(), "Sysroot directory '{}' doesn't exist", loc.display());
-        assert!(loc.is_dir(), "Provided sysroot '{}' is not a directory", loc.display());
-        loc
-    }
-
-    #[test]
-    #[should_panic(expected = "Test inputs shouldn't cause compilation errors")]
-    fn test_run_compiler_for_testing_panic_when_test_input_contains_syntax_errors() {
-        run_compiler_for_testing("syntax error here", |_tcx| panic!("This part shouldn't execute"))
-    }
-
-    #[test]
-    #[should_panic(expected = "Test inputs shouldn't cause compilation errors")]
-    fn test_run_compiler_for_testing_panic_when_test_input_triggers_analysis_errors() {
-        run_compiler_for_testing("#![feature(no_such_feature)]", |_tcx| {
-            panic!("This part shouldn't execute")
-        })
-    }
-
-    #[test]
-    #[should_panic(expected = "Test inputs shouldn't cause compilation errors")]
-    fn test_run_compiler_for_testing_panic_when_test_input_triggers_warnings() {
-        run_compiler_for_testing("pub fn foo(unused_parameter: i32) {}", |_tcx| {
-            panic!("This part shouldn't execute")
-        })
-    }
-
-    #[test]
-    fn test_run_compiler_for_testing_nightly_features_ok_in_test_input() {
-        // This test arbitrarily picks `yeet_expr` as an example of a feature that
-        // hasn't yet been stabilized.
-        let test_src = r#"
-                // This test is supposed to test that *nightly* features are ok
-                // in the test input.  The `forbid` directive below helps to
-                // ensure that we'll realize in the future when the `yeet_expr`
-                // feature gets stabilized, making it not quite fitting for use
-                // in this test.
-                #![forbid(stable_features)]
-
-                #![feature(yeet_expr)]
-            "#;
-        run_compiler_for_testing(test_src, |_tcx| ())
-    }
-
-    #[test]
-    fn test_run_compiler_for_testing_stabilized_features_ok_in_test_input() {
-        // This test arbitrarily picks `const_ptr_offset_from` as an example of a
-        // feature that has been already stabilized.
-        run_compiler_for_testing("#![feature(const_ptr_offset_from)]", |_tcx| ())
-    }
-
-    /// `run_compiler_for_testing` is similar to `run_compiler`: it invokes the
-    /// `callback` after parsing and analysis are done, but instead of taking
-    /// `rustc_args` it:
-    ///
-    /// * Invokes the Rust compiler on the given Rust `source`
-    /// * Hardcodes other compiler flags (e.g. picks Rust 2021 edition, and opts
-    ///   into treating all warnings as errors).
-    pub fn run_compiler_for_testing<F, T>(source: impl Into<String>, callback: F) -> T
-    where
-        F: for<'tcx> FnOnce(TyCtxt<'tcx>) -> T + Send,
-        T: Send,
-    {
-        use rustc_session::config::{
-            CodegenOptions, CrateType, Input, Options, OutputType, OutputTypes,
-        };
-
-        const TEST_FILENAME: &str = "crubit_unittests.rs";
-
-        // Setting `output_types` that will trigger code gen - otherwise some parts of
-        // the analysis will be missing (e.g. `tcx.exported_symbols()`).
-        // The choice of `Bitcode` is somewhat arbitrary (e.g. `Assembly`,
-        // `Mir`, etc. would also trigger code gen).
-        let output_types = OutputTypes::new(&[(OutputType::Bitcode, None /* PathBuf */)]);
-
-        let opts = Options {
-            crate_types: vec![CrateType::Rlib], // Test inputs simulate library crates.
-            maybe_sysroot: Some(get_sysroot_for_testing()),
-            output_types,
-            edition: rustc_span::edition::Edition::Edition2021,
-            unstable_features: rustc_feature::UnstableFeatures::Allow,
-            lint_opts: vec![
-                ("warnings".to_string(), rustc_lint_defs::Level::Deny),
-                ("stable_features".to_string(), rustc_lint_defs::Level::Allow),
-            ],
-            cg: CodegenOptions {
-                // As pointed out in `panics_and_exceptions.md` the tool only supports `-C
-                // panic=abort` and therefore we explicitly opt into this config for tests.
-                panic: Some(rustc_target::spec::PanicStrategy::Abort),
-                ..Default::default()
-            },
-            ..Default::default()
-        };
-
-        let config = rustc_interface::interface::Config {
-            opts,
-            crate_cfg: Default::default(),
-            crate_check_cfg: Default::default(),
-            input: Input::Str {
-                name: rustc_span::FileName::Custom(TEST_FILENAME.to_string()),
-                input: source.into(),
-            },
-            output_file: None,
-            output_dir: None,
-            file_loader: None,
-            lint_caps: Default::default(),
-            parse_sess_created: None,
-            register_lints: None,
-            override_queries: None,
-            make_codegen_backend: None,
-            registry: rustc_errors::registry::Registry::new(rustc_error_codes::DIAGNOSTICS),
-            locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES,
-            ice_file: None,
-            expanded_args: vec![],
-        };
-
-        rustc_interface::interface::run_compiler(config, |compiler| {
-            compiler.enter(|queries| {
-                use rustc_interface::interface::Result;
-                let result: Result<Result<()>> = super::enter_tcx(queries, |tcx| {
-                    // Explicitly force full `analysis` stage to detect compilation
-                    // errors that the earlier stages might miss.  This helps ensure that the
-                    // test inputs are valid Rust (even if `callback` wouldn't
-                    // have triggered full analysis).
-                    tcx.analysis(())
-                });
-
-                // Flatten the outer and inner results into a single result.  (outer result
-                // comes from `enter_tcx`; inner result comes from `analysis`).
-                //
-                // TODO(lukasza): Use `Result::flatten` API when it gets stabilized.  See also
-                // https://github.com/rust-lang/rust/issues/70142
-                let result: Result<()> = result.and_then(|result| result);
-
-                // `analysis` might succeed even if there are some lint / warning errors.
-                // Detecting these requires explicitly checking `compile_status`.
-                let result: Result<()> = result.and_then(|()| compiler.session().compile_status());
-
-                // Run the provided callback.
-                let result: Result<T> = result.and_then(|()| super::enter_tcx(queries, callback));
-                result.expect("Test inputs shouldn't cause compilation errors")
-            })
-        })
-    }
 }
diff --git a/cc_bindings_from_rs/run_compiler_test_support.rs b/cc_bindings_from_rs/run_compiler_test_support.rs
new file mode 100644
index 0000000..53a6ded
--- /dev/null
+++ b/cc_bindings_from_rs/run_compiler_test_support.rs
@@ -0,0 +1,178 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+//! A wrapper around `run_compiler` for testing only.
+
+#![feature(rustc_private)]
+extern crate rustc_driver;
+extern crate rustc_error_codes;
+extern crate rustc_errors;
+extern crate rustc_feature;
+extern crate rustc_interface;
+extern crate rustc_lint_defs;
+extern crate rustc_middle;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+
+use rustc_middle::ty::TyCtxt;
+use rustc_session::config::{CodegenOptions, CrateType, Input, Options, OutputType, OutputTypes};
+
+use std::path::PathBuf;
+
+#[cfg(oss)]
+const TOOLCHAIN_ROOT: &str = "rust_linux_x86_64__x86_64-unknown-linux-gnu__nightly_tools/rust_toolchain/lib/rustlib/x86_64-unknown-linux-gnu";
+#[cfg(not(oss))]
+const TOOLCHAIN_ROOT: &str = "google3/nowhere/llvm/rust/main_sysroot";
+
+/// Returns the `rustc` sysroot that is suitable for the environment where
+/// unit tests run.
+///
+/// The sysroot is used internally by `run_compiler_for_testing`, but it may
+/// also be passed as `--sysroot=...` in `rustc_args` argument of
+/// `run_compiler`
+pub fn get_sysroot_for_testing() -> PathBuf {
+    let runfiles = runfiles::Runfiles::create().unwrap();
+    let loc = runfiles.rlocation(std::path::Path::new(TOOLCHAIN_ROOT));
+    assert!(loc.exists(), "Sysroot directory '{}' doesn't exist", loc.display());
+    assert!(loc.is_dir(), "Provided sysroot '{}' is not a directory", loc.display());
+    loc
+}
+
+/// `run_compiler_for_testing` is similar to `run_compiler`: it invokes the
+/// `callback` after parsing and analysis are done, but instead of taking
+/// `rustc_args` it:
+///
+/// * Invokes the Rust compiler on the given Rust `source`
+/// * Hardcodes other compiler flags (e.g. picks Rust 2021 edition, and opts
+///   into treating all warnings as errors).
+pub fn run_compiler_for_testing<F, T>(source: impl Into<String>, callback: F) -> T
+where
+    F: for<'tcx> FnOnce(TyCtxt<'tcx>) -> T + Send,
+    T: Send,
+{
+    const TEST_FILENAME: &str = "crubit_unittests.rs";
+
+    // Setting `output_types` that will trigger code gen - otherwise some parts of
+    // the analysis will be missing (e.g. `tcx.exported_symbols()`).
+    // The choice of `Bitcode` is somewhat arbitrary (e.g. `Assembly`,
+    // `Mir`, etc. would also trigger code gen).
+    let output_types = OutputTypes::new(&[(OutputType::Bitcode, None /* PathBuf */)]);
+
+    let opts = Options {
+        crate_types: vec![CrateType::Rlib], // Test inputs simulate library crates.
+        maybe_sysroot: Some(get_sysroot_for_testing()),
+        output_types,
+        edition: rustc_span::edition::Edition::Edition2021,
+        unstable_features: rustc_feature::UnstableFeatures::Allow,
+        lint_opts: vec![
+            ("warnings".to_string(), rustc_lint_defs::Level::Deny),
+            ("stable_features".to_string(), rustc_lint_defs::Level::Allow),
+        ],
+        cg: CodegenOptions {
+            // As pointed out in `panics_and_exceptions.md` the tool only supports `-C
+            // panic=abort` and therefore we explicitly opt into this config for tests.
+            panic: Some(rustc_target::spec::PanicStrategy::Abort),
+            ..Default::default()
+        },
+        ..Default::default()
+    };
+
+    let config = rustc_interface::interface::Config {
+        opts,
+        crate_cfg: Default::default(),
+        crate_check_cfg: Default::default(),
+        input: Input::Str {
+            name: rustc_span::FileName::Custom(TEST_FILENAME.to_string()),
+            input: source.into(),
+        },
+        output_file: None,
+        output_dir: None,
+        file_loader: None,
+        lint_caps: Default::default(),
+        parse_sess_created: None,
+        register_lints: None,
+        override_queries: None,
+        make_codegen_backend: None,
+        registry: rustc_errors::registry::Registry::new(rustc_error_codes::DIAGNOSTICS),
+        locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES,
+        ice_file: None,
+        expanded_args: vec![],
+    };
+
+    rustc_interface::interface::run_compiler(config, |compiler| {
+        compiler.enter(|queries| {
+            use rustc_interface::interface::Result;
+            let try_func = || -> Result<T> {
+                let mut query_context = queries.global_ctxt()?;
+                query_context.enter(|tcx| {
+                    // Explicitly force full `analysis` stage to detect compilation
+                    // errors that the earlier stages might miss.  This helps ensure that the
+                    // test inputs are valid Rust (even if `callback` wouldn't
+                    // have triggered full analysis).
+                    tcx.analysis(())
+                })?;
+
+                // `analysis` might succeed even if there are some lint / warning errors.
+                // Detecting these requires explicitly checking `compile_status`.
+                compiler.session().compile_status()?;
+
+                // Run the provided callback.
+                Ok(query_context.enter(callback))
+            };
+            try_func().expect("Test inputs shouldn't cause compilation errors")
+        })
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    #[should_panic(expected = "Test inputs shouldn't cause compilation errors")]
+    fn test_run_compiler_for_testing_panic_when_test_input_contains_syntax_errors() {
+        run_compiler_for_testing("syntax error here", |_tcx| panic!("This part shouldn't execute"))
+    }
+
+    #[test]
+    #[should_panic(expected = "Test inputs shouldn't cause compilation errors")]
+    fn test_run_compiler_for_testing_panic_when_test_input_triggers_analysis_errors() {
+        run_compiler_for_testing("#![feature(no_such_feature)]", |_tcx| {
+            panic!("This part shouldn't execute")
+        })
+    }
+
+    #[test]
+    #[should_panic(expected = "Test inputs shouldn't cause compilation errors")]
+    fn test_run_compiler_for_testing_panic_when_test_input_triggers_warnings() {
+        run_compiler_for_testing("pub fn foo(unused_parameter: i32) {}", |_tcx| {
+            panic!("This part shouldn't execute")
+        })
+    }
+
+    #[test]
+    fn test_run_compiler_for_testing_nightly_features_ok_in_test_input() {
+        // This test arbitrarily picks `yeet_expr` as an example of a feature that
+        // hasn't yet been stabilized.
+        let test_src = r#"
+                // This test is supposed to test that *nightly* features are ok
+                // in the test input.  The `forbid` directive below helps to
+                // ensure that we'll realize in the future when the `yeet_expr`
+                // feature gets stabilized, making it not quite fitting for use
+                // in this test.
+                #![forbid(stable_features)]
+
+                #![feature(yeet_expr)]
+            "#;
+        run_compiler_for_testing(test_src, |_tcx| ())
+    }
+
+    #[test]
+    fn test_run_compiler_for_testing_stabilized_features_ok_in_test_input() {
+        // This test arbitrarily picks `const_ptr_offset_from` as an example of a
+        // feature that has been already stabilized.
+        run_compiler_for_testing("#![feature(const_ptr_offset_from)]", |_tcx| ())
+    }
+}