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/codegen/mod.rs b/src/codegen/mod.rs index e498d2b26b..1a702b00f7 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -4207,6 +4207,16 @@ pub(crate) fn codegen( } } + if let Some(spec) = context.options().depfile.as_ref() { + match spec.write(context.deps()) { + 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..479c396cbb --- /dev/null +++ b/src/deps.rs @@ -0,0 +1,20 @@ +/// Generating build depfiles from parsed bindings. +use std::{collections::BTreeSet, path::PathBuf}; + +#[derive(Debug)] +pub(crate) struct DepfileSpec { + pub output_module: String, + pub depfile_path: PathBuf, +} + +impl DepfileSpec { + pub fn write(&self, deps: &BTreeSet) -> std::io::Result<()> { + let mut buf = format!("{}:", self.output_module); + + for file in deps { + buf = format!("{} {}", buf, file); + } + + std::fs::write(&self.depfile_path, &buf) + } +} diff --git a/src/ir/context.rs b/src/ir/context.rs index ccb05e7565..2b8acc36f0 100644 --- a/src/ir/context.rs +++ b/src/ir/context.rs @@ -29,7 +29,7 @@ use clang_sys; use proc_macro2::{Ident, Span}; use std::borrow::Cow; use std::cell::{Cell, RefCell}; -use std::collections::HashMap as StdHashMap; +use std::collections::{BTreeSet, HashMap as StdHashMap}; use std::iter::IntoIterator; use std::mem; @@ -354,6 +354,9 @@ pub struct BindgenContext { /// This needs to be an std::HashMap because the cexpr API requires it. parsed_macros: StdHashMap, cexpr::expr::EvalResult>, + /// A set of all the included filenames. + deps: BTreeSet, + /// The active replacements collected from replaces="xxx" annotations. replacements: HashMap, ItemId>, @@ -545,8 +548,16 @@ If you encounter an error missing from this list, please file an issue or a PR!" let root_module = Self::build_root_module(ItemId(0)); let root_module_id = root_module.id().as_module_id_unchecked(); + // depfiles need to include the explicitly listed headers too + let mut deps = BTreeSet::default(); + if let Some(filename) = &options.input_header { + deps.insert(filename.clone()); + } + deps.extend(options.extra_input_headers.iter().cloned()); + BindgenContext { items: vec![Some(root_module)], + deps, types: Default::default(), type_params: Default::default(), modules: Default::default(), @@ -632,6 +643,19 @@ If you encounter an error missing from this list, please file an issue or a PR!" self.options().parse_callbacks.as_ref().map(|t| &**t) } + /// Add another path to the set of included files. + pub fn include_file(&mut self, filename: String) { + if let Some(cbs) = self.parse_callbacks() { + cbs.include_file(&filename); + } + self.deps.insert(filename); + } + + /// Get any included files. + pub fn deps(&self) -> &BTreeSet { + &self.deps + } + /// Define a new item. /// /// This inserts it into the internal items set, and its type into the diff --git a/src/ir/item.rs b/src/ir/item.rs index 45415045fe..d7c92ab444 100644 --- a/src/ir/item.rs +++ b/src/ir/item.rs @@ -1415,9 +1415,7 @@ impl ClangItemParser for Item { ); } Some(filename) => { - if let Some(cb) = ctx.parse_callbacks() { - cb.include_file(&filename) - } + ctx.include_file(filename); } } } diff --git a/src/lib.rs b/src/lib.rs index 418811e3da..58c99c8dec 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. @@ -1417,11 +1431,13 @@ impl Builder { // Transform input headers to arguments on the clang command line. self.options.input_header = self.input_headers.pop(); - self.options - .clang_args - .extend(self.input_headers.drain(..).flat_map(|header| { - iter::once("-include".into()).chain(iter::once(header)) - })); + self.options.extra_input_headers = self.input_headers; + self.options.clang_args.extend( + self.options.extra_input_headers.iter().flat_map(|header| { + iter::once("-include".into()) + .chain(iter::once(header.to_string())) + }), + ); self.options.input_unsaved_files.extend( self.input_header_contents @@ -1624,6 +1640,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. /// @@ -1785,6 +1804,9 @@ struct BindgenOptions { /// The input header file. input_header: Option, + /// Any additional input header files. + extra_input_headers: Vec, + /// Unsaved files for input. input_unsaved_files: Vec, @@ -1963,6 +1985,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(), @@ -2008,6 +2031,7 @@ impl Default for BindgenOptions { module_lines: HashMap::default(), clang_args: vec![], input_header: None, + extra_input_headers: vec![], input_unsaved_files: vec![], parse_callbacks: None, codegen_config: CodegenConfig::all(), 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..09b151145c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -578,6 +578,34 @@ fn no_system_header_includes() { .success()); } +// TODO write a test that makes sure multiple include dirs with relative prefixes handled correctly + +#[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 =