blob: 080b75da8e2fe3304903822245652d366f092ba8 [file] [log] [blame]
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -07001// 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#![feature(rustc_private)]
6#![deny(rustc::internal)]
7
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -07008extern crate rustc_driver;
9extern crate rustc_error_codes;
10extern crate rustc_errors;
11extern crate rustc_hir;
12extern crate rustc_interface;
13extern crate rustc_middle;
14extern crate rustc_session;
15extern crate rustc_span;
16
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -070017// TODO(lukasza): Make `cmdline` and `lib` a separate crate (once we move to
18// Bazel).
19mod cmdline;
20mod lib;
21
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070022use anyhow::Context;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070023use itertools::Itertools;
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070024use rustc_middle::ty::TyCtxt;
25use std::path::Path;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070026
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070027use cmdline::Cmdline;
28use lib::GeneratedBindings;
29use token_stream_printer::tokens_to_string;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070030
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070031/// This mostly wraps and simplifies a subset of APIs from the `rustc_driver`
32/// module.
Lukasz Anforowiczc3452842022-09-23 08:02:58 -070033mod bindings_driver {
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070034
Lukasz Anforowicz31ca0492022-09-28 15:55:23 -070035 use either::Either;
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -070036 use rustc_interface::interface::Compiler;
37 use rustc_interface::Queries;
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070038 use rustc_middle::ty::TyCtxt;
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070039
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -070040 use crate::lib::enter_tcx;
41
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070042 /// Wrapper around `rustc_driver::RunCompiler::run` that exposes a
43 /// simplified API:
44 /// - Takes a `callback` that will be invoked from within Rust compiler,
45 /// after parsing and analysis are done,
46 /// - Compilation will stop after parsing, analysis, and the `callback are
47 /// done,
48 /// - Returns the combined results from the Rust compiler *and* the
49 /// `callback`.
50 pub fn run_after_analysis_and_stop<F, R>(
51 rustc_args: &[String],
52 callback: F,
53 ) -> anyhow::Result<R>
54 where
55 F: FnOnce(TyCtxt) -> anyhow::Result<R> + Send,
56 R: Send,
57 {
58 AfterAnalysisCallback::new(rustc_args, callback).run()
59 }
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -070060
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070061 struct AfterAnalysisCallback<'a, F, R>
62 where
63 F: FnOnce(TyCtxt) -> anyhow::Result<R> + Send,
64 R: Send,
65 {
66 args: &'a [String],
Lukasz Anforowicz31ca0492022-09-28 15:55:23 -070067 callback_or_result: Either<F, anyhow::Result<R>>,
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070068 }
69
70 impl<'a, F, R> AfterAnalysisCallback<'a, F, R>
71 where
72 F: FnOnce(TyCtxt) -> anyhow::Result<R> + Send,
73 R: Send,
74 {
75 fn new(args: &'a [String], callback: F) -> Self {
Lukasz Anforowicz31ca0492022-09-28 15:55:23 -070076 Self { args, callback_or_result: Either::Left(callback) }
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -070077 }
78
Lukasz Anforowiczc3452842022-09-23 08:02:58 -070079 /// Runs Rust compiler and then passes the `TyCtxt` of the
80 /// parsed+analyzed Rust crate into `bindings_main::main`.
81 /// Returns the combined results from Rust compiler *and*
82 /// `bindings_main::main`.
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070083 fn run(mut self) -> anyhow::Result<R> {
Lukasz Anforowiczc3452842022-09-23 08:02:58 -070084 // 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.
87 let rustc_result = rustc_driver::catch_fatal_errors(|| {
Lukasz Anforowicz1093f782022-09-28 12:45:10 -070088 rustc_driver::RunCompiler::new(self.args, &mut self).run()
Lukasz Anforowiczc3452842022-09-23 08:02:58 -070089 });
90
91 // Flatten `Result<Result<T, ...>>` into `Result<T, ...>` (i.e. get the Result
92 // from `RunCompiler::run` rather than the Result from
93 // `catch_fatal_errors`).
94 let rustc_result = rustc_result.and_then(|result| result);
95
96 // Translate `rustc_interface::interface::Result` into `anyhow::Result`. (Can't
97 // use `?` because the trait `std::error::Error` is not implemented for
98 // `ErrorGuaranteed` which is required by the impl of
99 // `From<ErrorGuaranteed>` for `anyhow::Error`.)
100 let rustc_result = rustc_result.map_err(|_err| {
101 // We can ignore `_err` because it has no payload / because this type has only
102 // one valid/possible value.
103 anyhow::format_err!("Errors reported by Rust compiler.")
104 });
105
Lukasz Anforowicz1093f782022-09-28 12:45:10 -0700106 // Return either `rustc_result` or `self.callback_result`.
Lukasz Anforowiczc3452842022-09-23 08:02:58 -0700107 rustc_result.and_then(|()| {
Lukasz Anforowicz31ca0492022-09-28 15:55:23 -0700108 self.callback_or_result.right_or_else(|_| panic!("The callback should have been called by now"))
Lukasz Anforowiczc3452842022-09-23 08:02:58 -0700109 })
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -0700110 }
111 }
112
Lukasz Anforowicz1093f782022-09-28 12:45:10 -0700113 impl<'a, F, R> rustc_driver::Callbacks for AfterAnalysisCallback<'_, F, R>
114 where
115 F: FnOnce(TyCtxt) -> anyhow::Result<R> + Send,
116 R: Send,
117 {
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -0700118 fn after_analysis<'tcx>(
119 &mut self,
120 _compiler: &Compiler,
121 queries: &'tcx Queries<'tcx>,
122 ) -> rustc_driver::Compilation {
Lukasz Anforowiczb8279db2022-09-22 07:40:55 -0700123 let rustc_result = enter_tcx(queries, |tcx| {
Lukasz Anforowicz31ca0492022-09-28 15:55:23 -0700124 let callback = {
125 let temporary_placeholder = Either::Right(Err(anyhow::anyhow!("unused")));
126 std::mem::replace(&mut self.callback_or_result, temporary_placeholder)
127 .left_or_else(|_| panic!("`after_analysis` should only run once"))
128 };
129 self.callback_or_result = Either::Right(callback(tcx));
Lukasz Anforowiczb8279db2022-09-22 07:40:55 -0700130 });
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -0700131
132 // `expect`ing no errors in `rustc_result`, because `after_analysis` is only
133 // called by `rustc_driver` if earlier compiler analysis was successful
134 // (which as the *last* compilation phase presumably covers *all*
135 // errors).
Lukasz Anforowiczb8279db2022-09-22 07:40:55 -0700136 rustc_result.expect("Expecting no compile errors inside `after_analysis` callback.");
Lukasz Anforowiczbf5a4ee2022-09-22 07:38:33 -0700137
138 rustc_driver::Compilation::Stop
139 }
140 }
Lukasz Anforowicz581fd752022-09-21 11:30:15 -0700141}
142
Lukasz Anforowicz1093f782022-09-28 12:45:10 -0700143fn write_file(path: &Path, content: &str) -> anyhow::Result<()> {
144 std::fs::write(path, content)
145 .with_context(|| format!("Error when writing to {}", path.display()))
146}
147
148fn run_with_tcx(cmdline: &Cmdline, tcx: TyCtxt) -> anyhow::Result<()> {
149 let bindings = GeneratedBindings::generate(tcx);
150 write_file(cmdline.h_out(), tokens_to_string(bindings.h_body)?.as_str())
151}
152
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700153/// Main entrypoint that (unlike `main`) doesn't do any intitializations that
154/// should only happen once for the binary (e.g. it doesn't call
155/// `install_ice_hook`) and therefore can be used from the tests module below.
156fn run_with_cmdline_args(args: &[String]) -> anyhow::Result<()> {
157 let cmdline = Cmdline::new(args)?;
Lukasz Anforowicz1093f782022-09-28 12:45:10 -0700158 bindings_driver::run_after_analysis_and_stop(cmdline.rustc_args(), |tcx| {
159 run_with_tcx(&cmdline, tcx)
160 })
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700161}
162
163// TODO(lukasza): Add end-to-end shell tests that invoke our executable
164// and verify 1) the happy path (zero exit code) and 2) any random
165// error path (non-zero exit code).
Lukasz Anforowicz581fd752022-09-21 11:30:15 -0700166fn main() -> anyhow::Result<()> {
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -0700167 rustc_driver::init_env_logger("CRUBIT_LOG");
168
169 // TODO: Investigate if we should install a signal handler here. See also how
170 // compiler/rustc_driver/src/lib.rs calls `signal_handler::install()`.
171
172 rustc_driver::install_ice_hook();
173
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700174 // `std::env::args()` will panic if any of the cmdline arguments are not valid
175 // Unicode. This seems okay.
176 let args = std::env::args().collect_vec();
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -0700177
Lukasz Anforowiczf018e4d2022-09-28 07:35:59 -0700178 run_with_cmdline_args(&args)
179}
180
181#[cfg(test)]
182mod tests {
183 use super::run_with_cmdline_args;
184
185 use crate::lib::tests::get_sysroot_for_testing;
186 use itertools::Itertools;
187 use std::path::PathBuf;
188 use tempfile::{tempdir, TempDir};
189
190 /// Test data builder (see also
191 /// https://testing.googleblog.com/2018/02/testing-on-toilet-cleanly-create-test.html).
192 struct TestArgs {
193 h_path: Option<String>,
194 extra_crubit_args: Vec<String>,
195 extra_rustc_args: Vec<String>,
196 tempdir: TempDir,
197 }
198
199 impl TestArgs {
200 fn default_args() -> anyhow::Result<Self> {
201 Ok(Self {
202 h_path: None,
203 extra_crubit_args: vec![],
204 extra_rustc_args: vec![],
205 tempdir: tempdir()?,
206 })
207 }
208
209 /// Use the specified `h_path` rather than auto-generating one in
210 /// `self`-managed temporary directory.
211 fn with_h_path(mut self, h_path: &str) -> Self {
212 self.h_path = Some(h_path.to_string());
213 self
214 }
215
216 /// Appends `extra_rustc_args` at the end of the cmdline (i.e. as
217 /// additional rustc args, in addition to `--sysroot`,
218 /// `--crate-type=...`, etc.).
219 fn with_extra_rustc_args<T>(mut self, extra_rustc_args: T) -> Self
220 where
221 T: IntoIterator,
222 T::Item: Into<String>,
223 {
224 self.extra_rustc_args = extra_rustc_args.into_iter().map(|t| t.into()).collect_vec();
225 self
226 }
227
228 /// Appends `extra_crubit_args` before the first `--`.
229 fn with_extra_crubit_args<T>(mut self, extra_crubit_args: T) -> Self
230 where
231 T: IntoIterator,
232 T::Item: Into<String>,
233 {
234 self.extra_crubit_args = extra_crubit_args.into_iter().map(|t| t.into()).collect_vec();
235 self
236 }
237
238 /// Invokes `super::run_with_cmdline_args` with default `test_crate.rs`
239 /// input (and with other default args + args gathered by
240 /// `self`).
241 ///
242 /// Returns the path to the `h_out` file. The file's lifetime is the
243 /// same as `&self`.
244 fn run(&self) -> anyhow::Result<PathBuf> {
245 let h_path = match self.h_path.as_ref() {
246 None => self.tempdir.path().join("test_crate.rs"),
247 Some(s) => PathBuf::from(s),
248 };
249
250 let rs_input_path = self.tempdir.path().join("test_crate.rs");
251 std::fs::write(
252 &rs_input_path,
253 r#" pub fn public_function() {
254 private_function()
255 }
256
257 fn private_function() {}
258 "#,
259 )?;
260
261 let mut args = vec![
262 "cc_bindings_from_rs_unittest_executable".to_string(),
263 format!("--h_out={}", h_path.display()),
264 ];
265 args.extend(self.extra_crubit_args.iter().cloned());
266 args.extend([
267 "--".to_string(),
268 "--crate-type=lib".to_string(),
269 format!("--sysroot={}", get_sysroot_for_testing().display()),
270 rs_input_path.display().to_string(),
271 ]);
272 args.extend(self.extra_rustc_args.iter().cloned());
273
274 run_with_cmdline_args(&args)?;
275
276 Ok(h_path)
277 }
278 }
279
280 #[test]
281 fn test_happy_path() -> anyhow::Result<()> {
282 let test_args = TestArgs::default_args()?;
283 let h_path = test_args.run().expect("Default args should succeed");
284
285 assert!(h_path.exists());
286 let h_body = std::fs::read_to_string(&h_path)?;
287 assert_eq!(
288 h_body,
289 "// Automatically @generated C++ bindings for the following Rust crate:\n\
290 // test_crate\n\
291 \n\
292 // List of public functions:\n\
293 // public_function\n"
294 );
295 Ok(())
296 }
297
298 #[test]
299 fn test_cmdline_error_propagation() -> anyhow::Result<()> {
300 // Tests that errors from `Cmdline::new` get propagated. Broader coverage of
301 // various error types can be found in tests in `cmdline.rs`.
302 let err = TestArgs::default_args()?
303 .with_extra_crubit_args(["--unrecognized-crubit-flag"])
304 .run()
305 .expect_err("--unrecognized_crubit_flag should trigger an error");
306
307 let msg = err.to_string();
308 assert_eq!("Unrecognized option: 'unrecognized-crubit-flag'", msg);
309 Ok(())
310 }
311
312 #[test]
313 fn test_rustc_error_propagation() -> anyhow::Result<()> {
314 // Tests that `rustc` errors are propagated.
315 let err = TestArgs::default_args()?
316 .with_extra_rustc_args(["--unrecognized-rustc-flag"])
317 .run()
318 .expect_err("--unrecognized-rustc-flag should trigger an error");
319
320 let msg = err.to_string();
321 assert_eq!("Errors reported by Rust compiler.", msg);
322 Ok(())
323 }
324
325 #[test]
326 fn test_invalid_h_out_path() -> anyhow::Result<()> {
327 // Tests not only the specific problem of an invalid `--h_out` argument, but
328 // also tests that errors from `bindings_main::main` are propagated.
329 let err = TestArgs::default_args()?
330 .with_h_path("../..")
331 .run()
332 .expect_err("Unwriteable --h_out should trigger an error");
333
334 let msg = err.to_string();
335 assert_eq!("Error when writing to ../..", msg);
336 Ok(())
337 }
338
339 #[test]
340 fn test_no_output_file() -> anyhow::Result<()> {
341 // Tests that we stop the compilation midway.
342 let tmpdir = tempdir()?;
343 let out_path = tmpdir.path().join("unexpected_output.o");
344 TestArgs::default_args()?
345 .with_extra_rustc_args(vec!["-o", &out_path.display().to_string()])
346 .run()
347 .expect("No rustc or Crubit errors are expected in this test");
348
349 assert!(!out_path.exists());
350 Ok(())
351 }
Lukasz Anforowiczbda1cfe2022-09-20 06:25:43 -0700352}