blob: 09b8857507c8bc10bf67aa7adc52de552c754fab [file] [log] [blame]
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -08001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5//! The `run_compiler` crate mostly wraps and simplifies a subset of APIs
Devin Jeanpierre0b1e9882023-10-02 15:29:06 -07006//! from the `rustc_driver` crate, providing an easy way to `run_compiler`.
7#![feature(rustc_private)]
8#![deny(rustc::internal)]
9
10extern crate rustc_driver;
Devin Jeanpierre0b1e9882023-10-02 15:29:06 -070011extern crate rustc_interface;
12extern crate rustc_lint_defs;
Krasimir Georgiev75da3ef2023-11-21 01:52:53 -080013extern crate rustc_log;
Devin Jeanpierre0b1e9882023-10-02 15:29:06 -070014extern crate rustc_middle;
15extern crate rustc_session;
Krasimir Georgiev35e85b62024-02-22 07:08:52 -080016extern crate rustc_span;
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080017
Devin Jeanpierre45845b92024-06-21 01:29:11 -070018use arc_anyhow::{anyhow, bail, Result};
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080019use either::Either;
20use rustc_interface::interface::Compiler;
21use rustc_interface::Queries;
22use rustc_middle::ty::TyCtxt; // See also <internal link>/ty.html#import-conventions
Googler862d8562023-07-06 23:30:36 -070023use rustc_session::config::ErrorOutputType;
Krasimir Georgiev03f5fc52023-12-21 07:39:58 -080024use rustc_session::EarlyDiagCtxt;
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080025
26/// Wrapper around `rustc_driver::RunCompiler::run` that exposes a
27/// simplified API:
28/// - Takes a `callback` that will be invoked from within Rust compiler, after
29/// parsing and analysis are done,
30/// - Compilation will stop after parsing, analysis, and the `callback` are
31/// done,
32/// - Returns the combined results from the Rust compiler *and* the `callback`.
Lukasz Anforowicz087dff72023-02-17 12:13:32 -080033/// - Is safe to run from unit tests (which may run in parallel / on multiple
34/// threads).
Devin Jeanpierre45845b92024-06-21 01:29:11 -070035pub fn run_compiler<F, R>(rustc_args: &[String], callback: F) -> Result<R>
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080036where
Devin Jeanpierre45845b92024-06-21 01:29:11 -070037 F: FnOnce(TyCtxt) -> Result<R> + Send,
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080038 R: Send,
39{
Krasimir Georgiev75da3ef2023-11-21 01:52:53 -080040 // Calling `init_logger` 1) here and 2) via `sync::Lazy` helps to ensure
Lukasz Anforowicz087dff72023-02-17 12:13:32 -080041 // that logging is intialized exactly once, even if the `run_compiler`
42 // function is invoked by mutliple unit tests running in parallel on
43 // separate threads. This is important for avoiding flaky/racy
44 // panics related to 1) multiple threads entering
45 // `!tracing::dispatcher::has_been_set()` code in `rustc_driver_impl/src/
46 // lib.rs` and 2) `rustc_log/src/lib.rs` assumming that
47 // `tracing::subscriber::set_global_default` always succeeds.
48 use once_cell::sync::Lazy;
49 static ENV_LOGGER_INIT: Lazy<()> = Lazy::new(|| {
Krasimir Georgiev03f5fc52023-12-21 07:39:58 -080050 let early_error_handler = EarlyDiagCtxt::new(ErrorOutputType::default());
Krasimir Georgiev75da3ef2023-11-21 01:52:53 -080051 rustc_driver::init_logger(
52 &early_error_handler,
53 rustc_log::LoggerConfig::from_env("CRUBIT_LOG"),
54 );
Lukasz Anforowicz087dff72023-02-17 12:13:32 -080055 });
56 Lazy::force(&ENV_LOGGER_INIT);
57
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080058 AfterAnalysisCallback::new(rustc_args, callback).run()
59}
60
61struct AfterAnalysisCallback<'a, F, R>
62where
Devin Jeanpierre45845b92024-06-21 01:29:11 -070063 F: FnOnce(TyCtxt) -> Result<R> + Send,
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080064 R: Send,
65{
66 args: &'a [String],
Devin Jeanpierre45845b92024-06-21 01:29:11 -070067 callback_or_result: Either<F, Result<R>>,
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080068}
69
70impl<'a, F, R> AfterAnalysisCallback<'a, F, R>
71where
Devin Jeanpierre45845b92024-06-21 01:29:11 -070072 F: FnOnce(TyCtxt) -> Result<R> + Send,
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080073 R: Send,
74{
75 fn new(args: &'a [String], callback: F) -> Self {
76 Self { args, callback_or_result: Either::Left(callback) }
77 }
78
79 /// Runs Rust compiler, and then invokes the stored callback (with
80 /// `TyCtxt` of the parsed+analyzed Rust crate as the callback's
81 /// argument), and then finally returns the combined results
82 /// from Rust compiler *and* the callback.
Devin Jeanpierre45845b92024-06-21 01:29:11 -070083 fn run(mut self) -> Result<R> {
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080084 // Rust compiler unwinds with a special sentinel value to abort compilation on
85 // fatal errors. We use `catch_fatal_errors` to 1) catch such panics and
86 // translate them into a Result, and 2) resume and propagate other panics.
Krasimir Georgiev35e85b62024-02-22 07:08:52 -080087 let catch_fatal_errors_result: std::result::Result<
88 std::result::Result<(), rustc_span::ErrorGuaranteed>,
89 rustc_span::fatal_error::FatalError,
90 > = rustc_driver::catch_fatal_errors(|| {
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080091 rustc_driver::RunCompiler::new(self.args, &mut self).run()
92 });
93
Devin Jeanpierre2a8e83e2024-03-04 14:06:58 -080094 match catch_fatal_errors_result {
95 Ok(Ok(())) => {}
96 // We can ignore the `Err` payloads because the error types have only one value.
97 _ => bail!("Errors reported by Rust compiler."),
Krasimir Georgiev35e85b62024-02-22 07:08:52 -080098 };
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -080099
Devin Jeanpierre2a8e83e2024-03-04 14:06:58 -0800100 self.callback_or_result.right_or_else(|_left| {
101 // When rustc cmdline arguments (i.e. `self.args`) are empty (or contain
102 // `--help`) then the `after_analysis` callback won't be invoked. Handle
103 // this case by emitting an explicit error at the Crubit level.
104 bail!("The Rust compiler had no crate to compile and analyze")
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800105 })
106 }
107}
108
109impl<'a, F, R> rustc_driver::Callbacks for AfterAnalysisCallback<'a, F, R>
110where
Devin Jeanpierre45845b92024-06-21 01:29:11 -0700111 F: FnOnce(TyCtxt) -> Result<R> + Send,
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800112 R: Send,
113{
Lukasz Anforowicz01a1d122023-03-02 06:37:01 -0800114 /// Configures how `rustc` internals work when invoked via `run_compiler`.
Devin Jeanpierre0b1e9882023-10-02 15:29:06 -0700115 /// Note that `run_compiler_test_support` uses a separate `Config`.
Lukasz Anforowicz01a1d122023-03-02 06:37:01 -0800116 fn config(&mut self, config: &mut rustc_interface::interface::Config) {
117 // Silence warnings in the target crate to avoid reporting them twice: once when
118 // compiling the crate via `rustc` and once when "compiling" the crate
119 // via `cc_bindings_from_rs` (the `config` here affects the latter one).
120 config.opts.lint_opts.push(("warnings".to_string(), rustc_lint_defs::Level::Allow));
121 }
122
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800123 fn after_analysis<'tcx>(
124 &mut self,
125 _compiler: &Compiler,
126 queries: &'tcx Queries<'tcx>,
127 ) -> rustc_driver::Compilation {
Devin Jeanpierre0b1e9882023-10-02 15:29:06 -0700128 // `after_analysis` is only called by `rustc_driver` if earlier compiler
129 // analysis was successful (which as the *last* compilation phase
130 // presumably covers *all* errors).
131 let mut query_context = queries
132 .global_ctxt()
133 .expect("Expecting no compile errors inside `after_analysis` callback.");
134 query_context.enter(|tcx| {
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800135 let callback = {
Devin Jeanpierre45845b92024-06-21 01:29:11 -0700136 let temporary_placeholder = Either::Right(Err(anyhow!("unused")));
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800137 std::mem::replace(&mut self.callback_or_result, temporary_placeholder)
138 .left_or_else(|_| panic!("`after_analysis` should only run once"))
139 };
140 self.callback_or_result = Either::Right(callback(tcx));
141 });
142
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800143 rustc_driver::Compilation::Stop
144 }
145}
146
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800147#[cfg(test)]
Devin Jeanpierre0b1e9882023-10-02 15:29:06 -0700148mod tests {
149 use super::*;
150 use run_compiler_test_support::get_sysroot_for_testing;
Krasimir Georgiev96c12452024-07-11 00:27:40 -0700151 use run_compiler_test_support::setup_rustc_target_for_testing;
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800152 use tempfile::tempdir;
153
154 const DEFAULT_RUST_SOURCE_FOR_TESTING: &'static str = r#" pub mod public_module {
155 pub fn public_function() {
156 private_function()
157 }
158
159 fn private_function() {}
160 }
161 "#;
162
163 #[test]
Devin Jeanpierre45845b92024-06-21 01:29:11 -0700164 fn test_run_compiler_rustc_error_propagation() -> Result<()> {
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800165 let rustc_args = vec![
166 "run_compiler_unittest_executable".to_string(),
167 "--unrecognized-rustc-flag".to_string(),
168 ];
169 let err = run_compiler(&rustc_args, |_tcx| Ok(()))
170 .expect_err("--unrecognized-rustc-flag should trigger an error");
171
172 let msg = format!("{err:#}");
173 assert_eq!("Errors reported by Rust compiler.", msg);
174 Ok(())
175 }
176
177 /// `test_run_compiler_empty_args` tests that we gracefully handle scenarios
178 /// where `rustc` doesn't compile anything (e.g. when there are no
179 /// cmdline args).
180 #[test]
Devin Jeanpierre45845b92024-06-21 01:29:11 -0700181 fn test_run_compiler_no_args_except_argv0() -> Result<()> {
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800182 let rustc_args = vec!["run_compiler_unittest_executable".to_string()];
183 let err = run_compiler(&rustc_args, |_tcx| Ok(()))
184 .expect_err("Empty `rustc_args` should trigger an error");
185
186 let msg = format!("{err:#}");
187 assert_eq!("The Rust compiler had no crate to compile and analyze", msg);
188 Ok(())
189 }
190
191 /// `test_run_compiler_help` tests that we gracefully handle scenarios where
192 /// `rustc` doesn't compile anything (e.g. when passing `--help`).
193 #[test]
Devin Jeanpierre45845b92024-06-21 01:29:11 -0700194 fn test_run_compiler_help() -> Result<()> {
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800195 let rustc_args = vec!["run_compiler_unittest_executable".to_string(), "--help".to_string()];
196 let err = run_compiler(&rustc_args, |_tcx| Ok(()))
197 .expect_err("--help passed to rustc should trigger an error");
198
199 let msg = format!("{err:#}");
200 assert_eq!("The Rust compiler had no crate to compile and analyze", msg);
201 Ok(())
202 }
203
204 /// `test_run_compiler_no_output_file` tests that we stop the compilation
205 /// midway (i.e. that we return `Stop` from `after_analysis`).
206 #[test]
Devin Jeanpierre45845b92024-06-21 01:29:11 -0700207 fn test_run_compiler_no_output_file() -> Result<()> {
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800208 let tmpdir = tempdir()?;
209
210 let rs_path = tmpdir.path().join("input_crate.rs");
211 std::fs::write(&rs_path, DEFAULT_RUST_SOURCE_FOR_TESTING)?;
212
213 let out_path = tmpdir.path().join("unexpected_output.o");
Krasimir Georgiev96c12452024-07-11 00:27:40 -0700214 let mut rustc_args = vec![
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800215 // Default parameters.
216 "run_compiler_unittest_executable".to_string(),
217 "--crate-type=lib".to_string(),
218 format!("--sysroot={}", get_sysroot_for_testing().display()),
219 rs_path.display().to_string(),
220 // Test-specific parameter: asking for after-analysis output
221 "-o".to_string(),
222 out_path.display().to_string(),
223 ];
Krasimir Georgiev96c12452024-07-11 00:27:40 -0700224 if let Some(target_arg) = setup_rustc_target_for_testing(tmpdir.path()) {
225 rustc_args.push(format!("--target={}", target_arg));
226 }
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800227
228 run_compiler(&rustc_args, |_tcx| Ok(()))?;
229
230 // Verify that compilation didn't continue after the initial analysis.
231 assert!(!out_path.exists());
232 Ok(())
233 }
Lukasz Anforowicz0bef2642023-01-05 09:20:31 -0800234}