diff --git a/src/lib.rs b/src/lib.rs index a978d1b5bd..312155a51d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,10 +87,11 @@ use ir::item::Item; use parse::{ClangItemParser, ParseError}; use regex_set::RegexSet; -use std::fs::OpenOptions; +use std::fs::{File, OpenOptions}; use std::iter; use std::io::{self, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; +use std::process::Command; use std::sync::Arc; use syntax::ast; @@ -814,6 +815,75 @@ impl Builder { Bindings::generate(self.options, None) } + + /// Preprocess and dump the input header files to disk. + /// + /// This is useful when debugging bindgen, using C-Reduce, or when filing + /// issues. The resulting file will be named something like `__bindgen.i` or + /// `__bindgen.ii` + pub fn dump_preprocessed_input(&self) -> io::Result<()> { + let clang = clang_sys::support::Clang::find(None, &[]) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, + "Cannot find clang executable"))?; + + // The contents of a wrapper file that includes all the input header + // files. + let mut wrapper_contents = String::new(); + + // Whether we are working with C or C++ inputs. + let mut is_cpp = false; + + // For each input header, add `#include "$header"`. + for header in &self.input_headers { + is_cpp |= header.ends_with(".hpp"); + + wrapper_contents.push_str("#include \""); + wrapper_contents.push_str(header); + wrapper_contents.push_str("\"\n"); + } + + // For each input header content, add a prefix line of `#line 0 "$name"` + // followed by the contents. + for &(ref name, ref contents) in &self.input_header_contents { + is_cpp |= name.ends_with(".hpp"); + + wrapper_contents.push_str("#line 0 \""); + wrapper_contents.push_str(name); + wrapper_contents.push_str("\"\n"); + wrapper_contents.push_str(contents); + } + + is_cpp |= self.options.clang_args.windows(2).any(|w| { + w[0] == "-x=c++" || w[1] == "-x=c++" || w == &["-x", "c++"] + }); + + let wrapper_path = PathBuf::from(if is_cpp { + "__bindgen.cpp" + } else { + "__bindgen.c" + }); + + { + let mut wrapper_file = File::create(&wrapper_path)?; + wrapper_file.write(wrapper_contents.as_bytes())?; + } + + let mut cmd = Command::new(&clang.path); + cmd.arg("-save-temps") + .arg("-c") + .arg(&wrapper_path); + + for a in &self.options.clang_args { + cmd.arg(a); + } + + if cmd.spawn()?.wait()?.success() { + Ok(()) + } else { + Err(io::Error::new(io::ErrorKind::Other, + "clang exited with non-zero status")) + } + } } /// Configuration options for generated bindings. diff --git a/src/options.rs b/src/options.rs index 6a6fde8111..bf479ca5ed 100644 --- a/src/options.rs +++ b/src/options.rs @@ -214,6 +214,12 @@ pub fn builder_from_flags Arg::with_name("verbose") .long("verbose") .help("Print verbose error messages."), + Arg::with_name("dump-preprocessed-input") + .long("dump-preprocessed-input") + .help("Preprocess and dump the input header files to disk. \ + Useful when debugging bindgen, using C-Reduce, or when \ + filing issues. The resulting file will be named \ + something like `__bindgen.i` or `__bindgen.ii`.") ]) // .args() .get_matches_from(args); @@ -424,6 +430,10 @@ pub fn builder_from_flags Box::new(io::BufWriter::new(io::stdout())) as Box }; + if matches.is_present("dump-preprocessed-input") { + builder.dump_preprocessed_input()?; + } + let verbose = matches.is_present("verbose"); Ok((builder, output, verbose)) diff --git a/tests/tests.rs b/tests/tests.rs index 24d5770e98..6950026817 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -238,3 +238,31 @@ fn no_system_header_includes() { .expect("should wait for ./ci/no-includes OK") .success()); } + +#[test] +fn dump_preprocessed_input() { + let arg_keyword = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/headers/arg_keyword.hpp"); + let empty_layout = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/headers/cpp-empty-layout.hpp"); + + builder() + .header(arg_keyword) + .header(empty_layout) + .dump_preprocessed_input() + .expect("should dump preprocessed input"); + + fn slurp(p: &str) -> String { + let mut contents = String::new(); + let mut file = fs::File::open(p).unwrap(); + file.read_to_string(&mut contents).unwrap(); + contents + } + + let bindgen_ii = slurp("__bindgen.ii"); + let arg_keyword = slurp(arg_keyword); + let empty_layout = slurp(empty_layout); + + assert!(bindgen_ii.find(&arg_keyword).is_some(), + "arg_keyword.hpp is in the preprocessed file"); + assert!(bindgen_ii.find(&empty_layout).is_some(), + "cpp-empty-layout.hpp is in the preprocessed file"); +}