diff --git a/.gitignore b/.gitignore
index 906dcfeebe..b2bfe5aa6c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@ tests/expectations/Cargo.lock
# Test script output
ir.dot
ir.png
+
+# Output of the --dump-preprocessed-input flag.
+__bindgen.*
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index de5f709d60..56f75ab1f8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -272,14 +272,21 @@ With those two things in hand, running `creduce` looks like this:
### Isolating Your Test Case
-Use the `-save-temps` flag to make Clang spit out its intermediate
-representations when compiling the test case into an object file.
+If you're using `bindgen` as a command line tool, pass
+`--dump-preprocessed-input` flag.
- $ clang[++ -x c++ --std=c++14] -save-temps -c my_test_case.h
+If you're using `bindgen` as a Rust library, invoke the
+`bindgen::Builder::dump_preprocessed_input` method where you call
+`bindgen::Builder::generate`.
-There should now be a `my_test_case.ii` file, which is the results after the C
-pre-processor has processed all the `#include`s, `#define`s, and `#ifdef`s. This
-is generally what we're looking for.
+Afterwards, there should be a `__bindgen.i` or `__bindgen.ii` file containing
+the combined and preprocessed input headers, which is usable as an isolated,
+standalone test case.
+
+Note that the preprocessor likely removed all comments, so if the bug you're
+trying to pin down involves source annotations (for example, `/**
*/`), then you will have to manually reapply them to the
+preprocessed file.
### Writing a Predicate Script
diff --git a/src/lib.rs b/src/lib.rs
index 7f16677701..312155a51d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -87,9 +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;
@@ -165,9 +167,12 @@ impl Default for CodegenConfig {
/// // Write the generated bindings to an output file.
/// try!(bindings.write_to_file("path/to/output.rs"));
/// ```
-#[derive(Debug,Default)]
+#[derive(Debug, Default)]
pub struct Builder {
options: BindgenOptions,
+ input_headers: Vec,
+ // Tuples of unsaved file contents of the form (name, contents).
+ input_header_contents: Vec<(String, String)>,
}
/// Construct a new [`Builder`](./struct.Builder.html).
@@ -180,9 +185,9 @@ impl Builder {
pub fn command_line_flags(&self) -> Vec {
let mut output_vector: Vec = Vec::new();
- if let Some(ref header) = self.options.input_header {
- //Positional argument 'header'
- output_vector.push(header.clone().into());
+ if let Some(header) = self.input_headers.last().cloned() {
+ // Positional argument 'header'
+ output_vector.push(header);
}
self.options
@@ -412,16 +417,19 @@ impl Builder {
})
.count();
+ output_vector.push("--".into());
+
if !self.options.clang_args.is_empty() {
- output_vector.push("--".into());
- self.options
- .clang_args
- .iter()
- .cloned()
- .map(|item| {
- output_vector.push(item);
- })
- .count();
+ output_vector.extend(
+ self.options
+ .clang_args
+ .iter()
+ .cloned()
+ );
+ }
+
+ if self.input_headers.len() > 1 {
+ output_vector.extend(self.input_headers[..self.input_headers.len() - 1].iter().cloned());
}
output_vector
@@ -450,13 +458,7 @@ impl Builder {
/// .unwrap();
/// ```
pub fn header>(mut self, header: T) -> Builder {
- if let Some(prev_header) = self.options.input_header.take() {
- self.options.clang_args.push("-include".into());
- self.options.clang_args.push(prev_header);
- }
-
- let header = header.into();
- self.options.input_header = Some(header);
+ self.input_headers.push(header.into());
self
}
@@ -464,7 +466,7 @@ impl Builder {
///
/// The file `name` will be added to the clang arguments.
pub fn header_contents(mut self, name: &str, contents: &str) -> Builder {
- self.options.input_unsaved_files.push(clang::UnsavedFile::new(name, contents));
+ self.input_header_contents.push((name.into(), contents.into()));
self
}
@@ -794,9 +796,94 @@ impl Builder {
}
/// Generate the Rust bindings using the options built up thus far.
- pub fn generate<'ctx>(self) -> Result, ()> {
+ pub fn generate<'ctx>(mut self) -> Result, ()> {
+ 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.input_unsaved_files.extend(
+ self.input_header_contents
+ .drain(..)
+ .map(|(name, contents)| clang::UnsavedFile::new(&name, &contents))
+ );
+
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");
+}