Collect errors in cc_bindings_from_rs.
When errors happens, besides generating a comment, we also
insert the error in the error reporting.
Added a command line flag to dump the error summary to a file.
PiperOrigin-RevId: 653406651
Change-Id: Idf5f748aaeaae3dd8d9c5ff888688eb52fa4328e
diff --git a/cc_bindings_from_rs/BUILD b/cc_bindings_from_rs/BUILD
index 53fcbe2..5dc287a 100644
--- a/cc_bindings_from_rs/BUILD
+++ b/cc_bindings_from_rs/BUILD
@@ -58,6 +58,7 @@
":run_compiler",
"//common:arc_anyhow",
"//common:code_gen_utils",
+ "//common:error_report",
"//common:token_stream_printer",
"@crate_index//:clap",
"@crate_index//:itertools",
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index f859065..2a9d7fa 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -18,7 +18,7 @@
escape_non_identifier_chars, format_cc_ident, format_cc_includes, make_rs_ident, CcInclude,
NamespaceQualifier,
};
-use error_report::{anyhow, bail, ensure};
+use error_report::{anyhow, bail, ensure, ErrorReporting};
use itertools::Itertools;
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{format_ident, quote, ToTokens};
@@ -63,6 +63,10 @@
#[input]
fn crate_name_to_include_paths(&self) -> Rc<HashMap<Rc<str>, Vec<CcInclude>>>;
+ /// Error collector for generating reports of errors encountered during the generation of bindings.
+ #[input]
+ fn errors(&self) -> Rc<dyn ErrorReporting>;
+
// TODO(b/262878759): Provide a set of enabled/disabled Crubit features.
#[input]
fn _features(&self) -> ();
@@ -2328,7 +2332,7 @@
AssocItemKind::Fn { .. } => db.format_fn(def_id).map(Some),
other => Err(anyhow!("Unsupported `impl` item kind: {other:?}")),
};
- result.unwrap_or_else(|err| Some(format_unsupported_def(tcx, def_id, err)))
+ result.unwrap_or_else(|err| Some(format_unsupported_def(db, def_id, err)))
})
.collect();
@@ -2531,7 +2535,13 @@
/// Formats a C++ comment explaining why no bindings have been generated for
/// `local_def_id`.
-fn format_unsupported_def(tcx: TyCtxt, local_def_id: LocalDefId, err: Error) -> ApiSnippets {
+fn format_unsupported_def(
+ db: &dyn BindingsGenerator<'_>,
+ local_def_id: LocalDefId,
+ err: Error,
+) -> ApiSnippets {
+ let tcx = db.tcx();
+ db.errors().insert(&err);
let source_loc = format_source_location(tcx, local_def_id);
let name = tcx.def_path_str(local_def_id.to_def_id());
@@ -2629,7 +2639,7 @@
.filter_map(|item_id| {
let def_id: LocalDefId = item_id.owner_id.def_id;
db.format_item(def_id)
- .unwrap_or_else(|err| Some(format_unsupported_def(tcx, def_id, err)))
+ .unwrap_or_else(|err| Some(format_unsupported_def(db, def_id, err)))
.map(|api_snippets| (def_id, api_snippets))
})
.sorted_by_key(|(def_id, _)| tcx.def_span(*def_id));
@@ -2744,6 +2754,7 @@
use quote::quote;
+ use error_report::IgnoreErrors;
use run_compiler_test_support::{find_def_id_by_name, run_compiler_for_testing};
use token_stream_matchers::{
assert_cc_matches, assert_cc_not_matches, assert_rs_matches, assert_rs_not_matches,
@@ -8415,6 +8426,7 @@
tcx,
/* crubit_support_path_format= */ "<crubit/support/for/tests/{header}>".into(),
/* crate_name_to_include_paths= */ Default::default(),
+ /* errors = */ Rc::new(IgnoreErrors),
/* _features= */ (),
)
}
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs.rs b/cc_bindings_from_rs/cc_bindings_from_rs.rs
index 5ef8252..b95f326 100644
--- a/cc_bindings_from_rs/cc_bindings_from_rs.rs
+++ b/cc_bindings_from_rs/cc_bindings_from_rs.rs
@@ -18,6 +18,7 @@
use bindings::Database;
use cmdline::Cmdline;
use code_gen_utils::CcInclude;
+use error_report::{ErrorReport, ErrorReporting, IgnoreErrors};
use run_compiler::run_compiler;
use token_stream_printer::{
cc_tokens_to_formatted_string, rs_tokens_to_formatted_string, RustfmtConfig,
@@ -28,7 +29,11 @@
.with_context(|| format!("Error when writing to {}", path.display()))
}
-fn new_db<'tcx>(cmdline: &Cmdline, tcx: TyCtxt<'tcx>) -> Database<'tcx> {
+fn new_db<'tcx>(
+ cmdline: &Cmdline,
+ tcx: TyCtxt<'tcx>,
+ errors: Rc<dyn ErrorReporting>,
+) -> Database<'tcx> {
let crubit_support_path_format = cmdline.crubit_support_path_format.as_str().into();
let mut crate_name_to_include_paths = <HashMap<Rc<str>, Vec<CcInclude>>>::new();
@@ -41,14 +46,22 @@
tcx,
crubit_support_path_format,
crate_name_to_include_paths.into(),
+ errors,
/* _features= */ (),
)
}
fn run_with_tcx(cmdline: &Cmdline, tcx: TyCtxt) -> Result<()> {
use bindings::{generate_bindings, Output};
+
+ let errors: Rc<dyn ErrorReporting> = if cmdline.error_report_out.is_some() {
+ Rc::new(ErrorReport::new())
+ } else {
+ Rc::new(IgnoreErrors)
+ };
+
let Output { h_body, rs_body } = {
- let db = new_db(cmdline, tcx);
+ let db = new_db(cmdline, tcx, errors.clone());
generate_bindings(&db)?
};
@@ -64,6 +77,10 @@
write_file(&cmdline.rs_out, &rs_body)?;
}
+ if let Some(error_report_out) = &cmdline.error_report_out {
+ write_file(error_report_out, &errors.serialize_to_string().unwrap())?;
+ }
+
Ok(())
}
@@ -115,7 +132,9 @@
/// Test data builder (see also
/// https://testing.googleblog.com/2018/02/testing-on-toilet-cleanly-create-test.html).
struct TestArgs {
+ rs_input: Option<String>,
h_path: Option<String>,
+ error_report_out: Option<String>,
extra_crubit_args: Vec<String>,
/// Arg for the following `rustc` flag: `--codegen=panic=<arg>`.
@@ -133,12 +152,15 @@
struct TestResult {
h_path: PathBuf,
rs_path: PathBuf,
+ error_report_out_path: Option<PathBuf>,
}
impl TestArgs {
fn default_args() -> Result<Self> {
Ok(Self {
+ rs_input: None,
h_path: None,
+ error_report_out: None,
extra_crubit_args: vec![],
panic_mechanism: "abort".to_string(),
extra_rustc_args: vec![],
@@ -153,6 +175,18 @@
self
}
+ /// Specify the path to the error report output file.
+ fn with_error_report_out(mut self, error_report_out: &str) -> Self {
+ self.error_report_out = Some(error_report_out.to_string());
+ self
+ }
+
+ /// Specify the test Rust input.
+ fn with_rs_input(mut self, rs_input: &str) -> Self {
+ self.rs_input = Some(rs_input.to_string());
+ self
+ }
+
/// Replaces the default `--codegen=panic=abort` with the specified
/// `panic_mechanism`.
fn with_panic_mechanism(mut self, panic_mechanism: &str) -> Self {
@@ -185,11 +219,13 @@
None => self.tempdir.path().join("test_crate_cc_api.h"),
Some(s) => PathBuf::from(s),
};
+
let rs_path = self.tempdir.path().join("test_crate_cc_api_impl.rs");
let rs_input_path = self.tempdir.path().join("test_crate.rs");
- std::fs::write(
- &rs_input_path,
+ let rs_input = if let Some(rs_input) = &self.rs_input {
+ rs_input
+ } else {
r#" pub mod public_module {
pub fn public_function() {
private_function()
@@ -197,8 +233,9 @@
fn private_function() {}
}
- "#,
- )?;
+ "#
+ };
+ std::fs::write(&rs_input_path, rs_input)?;
let mut args = vec![
"cc_bindings_from_rs_unittest_executable".to_string(),
@@ -208,6 +245,15 @@
format!("--clang-format-exe-path={CLANG_FORMAT_EXE_PATH_FOR_TESTING}"),
format!("--rustfmt-exe-path={RUSTFMT_EXE_PATH_FOR_TESTING}"),
];
+
+ let mut error_report_out_path = None;
+ if let Some(error_report_out) = self.error_report_out.as_ref() {
+ error_report_out_path = Some(self.tempdir.path().join(error_report_out));
+ args.push(format!(
+ "--error-report-out={}",
+ error_report_out_path.as_ref().unwrap().display()
+ ));
+ }
args.extend(self.extra_crubit_args.iter().cloned());
args.extend([
"--".to_string(),
@@ -225,7 +271,7 @@
run_with_cmdline_args(&args)?;
- Ok(TestResult { h_path, rs_path })
+ Ok(TestResult { h_path, rs_path, error_report_out_path })
}
}
@@ -275,6 +321,31 @@
}
#[test]
+ fn test_error_reporting_generation() -> Result<()> {
+ let test_args =
+ TestArgs::default_args()?.with_error_report_out("error_report.json").with_rs_input(
+ r#"
+ pub use std::collections;
+ pub use std::path;
+ "#,
+ );
+
+ let test_result = test_args.run().expect("Error report generation should succeed");
+ assert!(test_result.error_report_out_path.is_some());
+ let error_report_out_path = test_result.error_report_out_path.as_ref().unwrap();
+ assert!(error_report_out_path.exists());
+ let error_report = std::fs::read_to_string(&error_report_out_path)?;
+ let expected_error_report = r#"{
+ "Unsupported rustc_hir::hir::ItemKind: {}": {
+ "count": 2,
+ "sample_message": "Unsupported rustc_hir::hir::ItemKind: `use` import"
+ }
+}"#;
+ assert_eq!(expected_error_report, error_report);
+ Ok(())
+ }
+
+ #[test]
fn test_happy_path() -> Result<()> {
let test_args = TestArgs::default_args()?;
let test_result = test_args.run().expect("Default args should succeed");
diff --git a/cc_bindings_from_rs/cmdline.rs b/cc_bindings_from_rs/cmdline.rs
index f5f5067..d0ad460 100644
--- a/cc_bindings_from_rs/cmdline.rs
+++ b/cc_bindings_from_rs/cmdline.rs
@@ -60,6 +60,10 @@
/// Command line arguments of the Rust compiler.
#[clap(last = true, value_parser)]
pub rustc_args: Vec<String>,
+
+ /// Path to the error reporting output file.
+ #[clap(long, value_parser, value_name = "FILE")]
+ pub error_report_out: Option<PathBuf>,
}
impl Cmdline {
@@ -229,6 +233,8 @@
Path to a rustfmt executable that will be used to format the Rust source files generated by the tool
--rustfmt-config-path <FILE>
Path to a rustfmt.toml file that should replace the default formatting of the .rs files generated by the tool
+ --error-report-out <FILE>
+ Path to the error reporting output file
-h, --help
Print help
"#;
diff --git a/common/error_report.rs b/common/error_report.rs
index 1081bea..198c99b 100644
--- a/common/error_report.rs
+++ b/common/error_report.rs
@@ -115,6 +115,7 @@
/// shared freely.
fn insert(&self, error: &arc_anyhow::Error);
fn serialize_to_vec(&self) -> anyhow::Result<Vec<u8>>;
+ fn serialize_to_string(&self) -> anyhow::Result<String>;
}
/// A null [`ErrorReporting`] strategy.
@@ -127,6 +128,10 @@
fn serialize_to_vec(&self) -> anyhow::Result<Vec<u8>> {
Ok(vec![])
}
+
+ fn serialize_to_string(&self) -> anyhow::Result<String> {
+ Ok(String::new())
+ }
}
/// An aggregate of zero or more errors.
@@ -165,6 +170,10 @@
fn serialize_to_vec(&self) -> anyhow::Result<Vec<u8>> {
Ok(serde_json::to_vec(&*self.map.borrow())?)
}
+
+ fn serialize_to_string(&self) -> anyhow::Result<String> {
+ Ok(serde_json::to_string_pretty(&*self.map.borrow())?)
+ }
}
#[derive(Default, Debug, Serialize)]
@@ -371,7 +380,7 @@
);
assert_eq!(
- serde_json::to_string_pretty(&*report.map.borrow()).unwrap(),
+ report.serialize_to_string().unwrap(),
r#"{
"abc{}": {
"count": 2,