From 9e560ad07928ce101735680744c6e085fa364b23 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Thu, 8 Apr 2021 17:26:55 +0000 Subject: [PATCH] Support emitting Makefile-syntax depfiles like gcc/clang/rustc. Needed to auto-bindgen with a ninja build without the build graph going stale. --- Cargo.lock | 96 ++++++++++ Cargo.toml | 1 + src/callbacks.rs | 8 +- src/codegen/mod.rs | 17 ++ src/deps.rs | 175 +++++++++++++++++++ src/lib.rs | 29 +++ src/options.rs | 10 ++ tests/expectations/tests/enum-default-rust.d | 1 + tests/tests.rs | 26 +++ 9 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 src/deps.rs create mode 100644 tests/expectations/tests/enum-default-rust.d diff --git a/Cargo.lock b/Cargo.lock index 7fc4535463..443201eedc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", + "tempfile", "which", ] @@ -125,6 +126,17 @@ dependencies = [ "termcolor", ] +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + [[package]] name = "glob" version = "0.3.0" @@ -205,6 +217,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -223,6 +241,55 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.4.2" @@ -241,6 +308,15 @@ version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -259,6 +335,20 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.0" @@ -310,6 +400,12 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "which" version = "3.1.1" diff --git a/Cargo.toml b/Cargo.toml index dd30f09ceb..6140407840 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ required-features = ["clap"] diff = "0.1" clap = "2" shlex = "1" +tempfile = "3" [dependencies] bitflags = "1.0.3" diff --git a/src/callbacks.rs b/src/callbacks.rs index e288af4341..d46f1edc31 100644 --- a/src/callbacks.rs +++ b/src/callbacks.rs @@ -4,6 +4,7 @@ pub use crate::ir::analysis::DeriveTrait; pub use crate::ir::derive::CanDerive as ImplementsTrait; pub use crate::ir::enum_ty::{EnumVariantCustomBehavior, EnumVariantValue}; pub use crate::ir::int::IntKind; +use std::any::Any; use std::fmt; use std::panic::UnwindSafe; @@ -25,7 +26,7 @@ impl Default for MacroParsingBehavior { /// A trait to allow configuring different kinds of types in different /// situations. -pub trait ParseCallbacks: fmt::Debug + UnwindSafe { +pub trait ParseCallbacks: Any + fmt::Debug + UnwindSafe { /// This function will be run on every macro that is identified. fn will_parse_macro(&self, _name: &str) -> MacroParsingBehavior { MacroParsingBehavior::Default @@ -95,4 +96,9 @@ pub trait ParseCallbacks: fmt::Debug + UnwindSafe { ) -> Option { None } + + /// TODO find a way to delete this from this PR + fn as_any(&self) -> Option<&dyn Any> { + None + } } diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index e62b1a8701..f751cbf8c2 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -4198,6 +4198,23 @@ pub(crate) fn codegen( } } + if let Some(collector) = + crate::deps::DepCollector::retrieve(&context.options()) + { + let spec = context + .options() + .depfile + .as_ref() + .expect("if we have a collector, we have a spec"); + match collector.write_depfile(spec) { + Ok(()) => info!( + "Your depfile was generated successfully into: {}", + spec.depfile_path.display() + ), + Err(e) => warn!("{}", e), + } + } + context.resolve_item(context.root_module()).codegen( context, &mut result, diff --git a/src/deps.rs b/src/deps.rs new file mode 100644 index 0000000000..ae85ff3e24 --- /dev/null +++ b/src/deps.rs @@ -0,0 +1,175 @@ +// Generating build depfiles from parsed bindings. + +use crate::{ + callbacks::{ + DeriveTrait, EnumVariantCustomBehavior, EnumVariantValue, + ImplementsTrait, IntKind, MacroParsingBehavior, ParseCallbacks, + }, + BindgenOptions, +}; +use std::{ + collections::BTreeSet, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +#[derive(Debug)] +pub(crate) struct DepfileSpec { + pub output_module: String, + pub depfile_path: PathBuf, +} + +#[derive(Clone, Debug)] +pub(crate) struct DepCollector { + inner: Arc>, +} + +impl DepCollector { + pub(crate) fn install(options: &mut BindgenOptions) { + let collector = DepCollector { + inner: Arc::new(Mutex::new(InnerCollector { + files: Default::default(), + prev_cb: None, + })), + }; + collector.inner.lock().unwrap().prev_cb = std::mem::replace( + &mut options.parse_callbacks, + Some(Box::new(collector.clone())), + ); + } + + pub(crate) fn retrieve(options: &BindgenOptions) -> Option<&Self> { + options + .parse_callbacks + .as_ref() + .and_then(|cb| cb.as_any()) + .and_then(|cb| cb.downcast_ref()) + } + + pub(crate) fn write_depfile( + &self, + spec: &DepfileSpec, + ) -> std::io::Result<()> { + let inner = self.inner.lock().unwrap(); + + let mut buf = format!("{}:", spec.output_module); + + for file in &inner.files { + buf = format!("{} {}", buf, file); + } + + std::fs::write(&spec.depfile_path, &buf) + } +} + +impl ParseCallbacks for DepCollector { + fn include_file(&self, filename: &str) { + let mut inner = self.inner.lock().unwrap(); + inner.files.insert(filename.to_owned()); + if let Some(prev_cb) = &inner.prev_cb { + prev_cb.include_file(filename); + } + } + + // all trait methods below here just forward to the wrapped callbacks + + fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior { + let inner = self.inner.lock().unwrap(); + if let Some(prev_cb) = &inner.prev_cb { + prev_cb.will_parse_macro(name) + } else { + Default::default() + } + } + + fn int_macro(&self, name: &str, value: i64) -> Option { + let inner = self.inner.lock().unwrap(); + if let Some(prev_cb) = &inner.prev_cb { + prev_cb.int_macro(name, value) + } else { + None + } + } + + fn str_macro(&self, name: &str, value: &[u8]) { + let inner = self.inner.lock().unwrap(); + if let Some(prev_cb) = &inner.prev_cb { + prev_cb.str_macro(name, value); + } + } + + fn func_macro(&self, name: &str, value: &[&[u8]]) { + let inner = self.inner.lock().unwrap(); + if let Some(prev_cb) = &inner.prev_cb { + prev_cb.func_macro(name, value); + } + } + + fn enum_variant_behavior( + &self, + enum_name: Option<&str>, + original_variant_name: &str, + variant_value: EnumVariantValue, + ) -> Option { + let inner = self.inner.lock().unwrap(); + if let Some(prev_cb) = &inner.prev_cb { + prev_cb.enum_variant_behavior( + enum_name, + original_variant_name, + variant_value, + ) + } else { + None + } + } + + fn enum_variant_name( + &self, + enum_name: Option<&str>, + original_variant_name: &str, + variant_value: EnumVariantValue, + ) -> Option { + let inner = self.inner.lock().unwrap(); + if let Some(prev_cb) = &inner.prev_cb { + prev_cb.enum_variant_name( + enum_name, + original_variant_name, + variant_value, + ) + } else { + None + } + } + + fn item_name(&self, original_item_name: &str) -> Option { + let inner = self.inner.lock().unwrap(); + if let Some(prev_cb) = &inner.prev_cb { + prev_cb.item_name(original_item_name) + } else { + None + } + } + + fn blocklisted_type_implements_trait( + &self, + name: &str, + derive_trait: DeriveTrait, + ) -> Option { + let inner = self.inner.lock().unwrap(); + if let Some(prev_cb) = &inner.prev_cb { + prev_cb.blocklisted_type_implements_trait(name, derive_trait) + } else { + None + } + } + + fn as_any(&self) -> Option<&dyn std::any::Any> { + Some(self) + } +} + +#[derive(Debug)] +struct InnerCollector { + files: BTreeSet, + prev_cb: Option>, +} diff --git a/src/lib.rs b/src/lib.rs index 85d555992e..6848d7b3bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ macro_rules! doc_mod { mod clang; mod codegen; +mod deps; mod features; mod ir; mod parse; @@ -604,6 +605,19 @@ impl Builder { self } + /// Add a depfile output which will be written alongside the generated bindings. + pub fn depfile, D: Into>( + mut self, + output_module: H, + depfile: D, + ) -> Builder { + self.options.depfile = Some(deps::DepfileSpec { + output_module: output_module.into(), + depfile_path: depfile.into(), + }); + self + } + /// Add `contents` as an input C/C++ header named `name`. /// /// The file `name` will be added to the clang arguments. @@ -1415,6 +1429,17 @@ impl Builder { }; } + if self.options.depfile.is_some() { + crate::deps::DepCollector::install(&mut self.options); + } + + // depfiles need to include the explicitly listed headers too + if let Some(cb) = &self.options.parse_callbacks { + for filename in &self.input_headers { + cb.include_file(filename); + } + } + // Transform input headers to arguments on the clang command line. self.options.input_header = self.input_headers.pop(); self.options @@ -1624,6 +1649,9 @@ struct BindgenOptions { /// The explicit rustfmt path. rustfmt_path: Option, + /// The path to which we should write a Makefile-syntax depfile (if any). + depfile: Option, + /// The set of types that we should have bindings for in the generated /// code. /// @@ -1963,6 +1991,7 @@ impl Default for BindgenOptions { blocklisted_items: Default::default(), opaque_types: Default::default(), rustfmt_path: Default::default(), + depfile: Default::default(), allowlisted_types: Default::default(), allowlisted_functions: Default::default(), allowlisted_vars: Default::default(), diff --git a/src/options.rs b/src/options.rs index 3626732108..02bf237782 100644 --- a/src/options.rs +++ b/src/options.rs @@ -30,6 +30,10 @@ where Arg::with_name("header") .help("C or C++ header file") .required(true), + Arg::with_name("depfile") + .long("depfile") + .takes_value(true) + .help("Path to write depfile to"), Arg::with_name("default-enum-style") .long("default-enum-style") .help("The default style of code used to generate enums.") @@ -848,8 +852,14 @@ where let output = if let Some(path) = matches.value_of("output") { let file = File::create(path)?; + if let Some(depfile) = matches.value_of("depfile") { + builder = builder.depfile(path, depfile); + } Box::new(io::BufWriter::new(file)) as Box } else { + if matches.value_of("depfile").is_some() { + panic!("--depfile can only be provided if --output is."); + } Box::new(io::BufWriter::new(io::stdout())) as Box }; diff --git a/tests/expectations/tests/enum-default-rust.d b/tests/expectations/tests/enum-default-rust.d new file mode 100644 index 0000000000..a6540005c9 --- /dev/null +++ b/tests/expectations/tests/enum-default-rust.d @@ -0,0 +1 @@ +tests/expectations/tests/enum-default-rust.rs: tests/headers/enum-default-rust.h tests/headers/enum.h diff --git a/tests/tests.rs b/tests/tests.rs index 80ccc8c9eb..ced4854027 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -578,6 +578,32 @@ fn no_system_header_includes() { .success()); } +#[test] +fn emit_depfile() { + let header = PathBuf::from("tests/headers/enum-default-rust.h"); + let expected_depfile = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("expectations") + .join("tests") + .join("enum-default-rust.d"); + let observed_depfile = tempfile::NamedTempFile::new().unwrap(); + let mut builder = create_bindgen_builder(&header).unwrap(); + builder.builder = builder.builder.depfile( + "tests/expectations/tests/enum-default-rust.rs", + observed_depfile.path(), + ); + + let check_roundtrip = + env::var_os("BINDGEN_DISABLE_ROUNDTRIP_TEST").is_none(); + let (builder, _roundtrip_builder) = + builder.into_builder(check_roundtrip).unwrap(); + let _bindings = builder.generate().unwrap(); + + let observed = std::fs::read_to_string(observed_depfile).unwrap(); + let expected = std::fs::read_to_string(expected_depfile).unwrap(); + assert_eq!(observed.trim(), expected.trim()); +} + #[test] fn dump_preprocessed_input() { let arg_keyword =