Skip to content

Commit

Permalink
Add the ability to dump preprocessed input headers
Browse files Browse the repository at this point in the history
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 rust-lang#811
  • Loading branch information
fitzgen committed Jul 13, 2017
1 parent 1b815d6 commit 84e9521
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 2 deletions.
74 changes: 72 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ pub fn builder_from_flags<I>
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);

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

if matches.is_present("dump-preprocessed-input") {
builder.dump_preprocessed_input()?;
}

let verbose = matches.is_present("verbose");

Ok((builder, output, verbose))
Expand Down
28 changes: 28 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

0 comments on commit 84e9521

Please sign in to comment.