Skip to content

Commit 84e9521

Browse files
committed
Add the ability to dump preprocessed input headers
This is useful when debugging bindgen, using C-Reduce on an input to bindgen, or for constructing portable test cases when filing issues against bindgen. Fixes #811
1 parent 1b815d6 commit 84e9521

File tree

3 files changed

+110
-2
lines changed

3 files changed

+110
-2
lines changed

src/lib.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,11 @@ use ir::item::Item;
8787
use parse::{ClangItemParser, ParseError};
8888
use regex_set::RegexSet;
8989

90-
use std::fs::OpenOptions;
90+
use std::fs::{File, OpenOptions};
9191
use std::iter;
9292
use std::io::{self, Write};
93-
use std::path::Path;
93+
use std::path::{Path, PathBuf};
94+
use std::process::Command;
9495
use std::sync::Arc;
9596

9697
use syntax::ast;
@@ -814,6 +815,75 @@ impl Builder {
814815

815816
Bindings::generate(self.options, None)
816817
}
818+
819+
/// Preprocess and dump the input header files to disk.
820+
///
821+
/// This is useful when debugging bindgen, using C-Reduce, or when filing
822+
/// issues. The resulting file will be named something like `__bindgen.i` or
823+
/// `__bindgen.ii`
824+
pub fn dump_preprocessed_input(&self) -> io::Result<()> {
825+
let clang = clang_sys::support::Clang::find(None, &[])
826+
.ok_or_else(|| io::Error::new(io::ErrorKind::Other,
827+
"Cannot find clang executable"))?;
828+
829+
// The contents of a wrapper file that includes all the input header
830+
// files.
831+
let mut wrapper_contents = String::new();
832+
833+
// Whether we are working with C or C++ inputs.
834+
let mut is_cpp = false;
835+
836+
// For each input header, add `#include "$header"`.
837+
for header in &self.input_headers {
838+
is_cpp |= header.ends_with(".hpp");
839+
840+
wrapper_contents.push_str("#include \"");
841+
wrapper_contents.push_str(header);
842+
wrapper_contents.push_str("\"\n");
843+
}
844+
845+
// For each input header content, add a prefix line of `#line 0 "$name"`
846+
// followed by the contents.
847+
for &(ref name, ref contents) in &self.input_header_contents {
848+
is_cpp |= name.ends_with(".hpp");
849+
850+
wrapper_contents.push_str("#line 0 \"");
851+
wrapper_contents.push_str(name);
852+
wrapper_contents.push_str("\"\n");
853+
wrapper_contents.push_str(contents);
854+
}
855+
856+
is_cpp |= self.options.clang_args.windows(2).any(|w| {
857+
w[0] == "-x=c++" || w[1] == "-x=c++" || w == &["-x", "c++"]
858+
});
859+
860+
let wrapper_path = PathBuf::from(if is_cpp {
861+
"__bindgen.cpp"
862+
} else {
863+
"__bindgen.c"
864+
});
865+
866+
{
867+
let mut wrapper_file = File::create(&wrapper_path)?;
868+
wrapper_file.write(wrapper_contents.as_bytes())?;
869+
}
870+
871+
let mut cmd = Command::new(&clang.path);
872+
cmd.arg("-save-temps")
873+
.arg("-c")
874+
.arg(&wrapper_path);
875+
876+
for a in &self.options.clang_args {
877+
cmd.arg(a);
878+
}
879+
880+
if cmd.spawn()?.wait()?.success() {
881+
Ok(())
882+
} else {
883+
Err(io::Error::new(io::ErrorKind::Other,
884+
"clang exited with non-zero status"))
885+
}
886+
}
817887
}
818888

819889
/// Configuration options for generated bindings.

src/options.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ pub fn builder_from_flags<I>
214214
Arg::with_name("verbose")
215215
.long("verbose")
216216
.help("Print verbose error messages."),
217+
Arg::with_name("dump-preprocessed-input")
218+
.long("dump-preprocessed-input")
219+
.help("Preprocess and dump the input header files to disk. \
220+
Useful when debugging bindgen, using C-Reduce, or when \
221+
filing issues. The resulting file will be named \
222+
something like `__bindgen.i` or `__bindgen.ii`.")
217223
]) // .args()
218224
.get_matches_from(args);
219225

@@ -424,6 +430,10 @@ pub fn builder_from_flags<I>
424430
Box::new(io::BufWriter::new(io::stdout())) as Box<io::Write>
425431
};
426432

433+
if matches.is_present("dump-preprocessed-input") {
434+
builder.dump_preprocessed_input()?;
435+
}
436+
427437
let verbose = matches.is_present("verbose");
428438

429439
Ok((builder, output, verbose))

tests/tests.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,31 @@ fn no_system_header_includes() {
238238
.expect("should wait for ./ci/no-includes OK")
239239
.success());
240240
}
241+
242+
#[test]
243+
fn dump_preprocessed_input() {
244+
let arg_keyword = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/headers/arg_keyword.hpp");
245+
let empty_layout = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/headers/cpp-empty-layout.hpp");
246+
247+
builder()
248+
.header(arg_keyword)
249+
.header(empty_layout)
250+
.dump_preprocessed_input()
251+
.expect("should dump preprocessed input");
252+
253+
fn slurp(p: &str) -> String {
254+
let mut contents = String::new();
255+
let mut file = fs::File::open(p).unwrap();
256+
file.read_to_string(&mut contents).unwrap();
257+
contents
258+
}
259+
260+
let bindgen_ii = slurp("__bindgen.ii");
261+
let arg_keyword = slurp(arg_keyword);
262+
let empty_layout = slurp(empty_layout);
263+
264+
assert!(bindgen_ii.find(&arg_keyword).is_some(),
265+
"arg_keyword.hpp is in the preprocessed file");
266+
assert!(bindgen_ii.find(&empty_layout).is_some(),
267+
"cpp-empty-layout.hpp is in the preprocessed file");
268+
}

0 commit comments

Comments
 (0)