Skip to content

Commit 767ac02

Browse files
author
bors-servo
authored
Auto merge of #812 - fitzgen:dump-preprocessed, r=emilio
Dump preprocessed input headers See each commit message. Fixes #811. r? @emilio
2 parents 5366a05 + 618dafe commit 767ac02

File tree

5 files changed

+165
-30
lines changed

5 files changed

+165
-30
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ tests/expectations/Cargo.lock
88
# Test script output
99
ir.dot
1010
ir.png
11+
12+
# Output of the --dump-preprocessed-input flag.
13+
__bindgen.*

CONTRIBUTING.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -272,14 +272,21 @@ With those two things in hand, running `creduce` looks like this:
272272

273273
### Isolating Your Test Case
274274

275-
Use the `-save-temps` flag to make Clang spit out its intermediate
276-
representations when compiling the test case into an object file.
275+
If you're using `bindgen` as a command line tool, pass
276+
`--dump-preprocessed-input` flag.
277277

278-
$ clang[++ -x c++ --std=c++14] -save-temps -c my_test_case.h
278+
If you're using `bindgen` as a Rust library, invoke the
279+
`bindgen::Builder::dump_preprocessed_input` method where you call
280+
`bindgen::Builder::generate`.
279281

280-
There should now be a `my_test_case.ii` file, which is the results after the C
281-
pre-processor has processed all the `#include`s, `#define`s, and `#ifdef`s. This
282-
is generally what we're looking for.
282+
Afterwards, there should be a `__bindgen.i` or `__bindgen.ii` file containing
283+
the combined and preprocessed input headers, which is usable as an isolated,
284+
standalone test case.
285+
286+
Note that the preprocessor likely removed all comments, so if the bug you're
287+
trying to pin down involves source annotations (for example, `/** <div
288+
rustbindgen opaque> */`), then you will have to manually reapply them to the
289+
preprocessed file.
283290

284291
### Writing a Predicate Script
285292

src/lib.rs

Lines changed: 111 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +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};
91+
use std::iter;
9192
use std::io::{self, Write};
92-
use std::path::Path;
93+
use std::path::{Path, PathBuf};
94+
use std::process::Command;
9395
use std::sync::Arc;
9496

9597
use syntax::ast;
@@ -165,9 +167,12 @@ impl Default for CodegenConfig {
165167
/// // Write the generated bindings to an output file.
166168
/// try!(bindings.write_to_file("path/to/output.rs"));
167169
/// ```
168-
#[derive(Debug,Default)]
170+
#[derive(Debug, Default)]
169171
pub struct Builder {
170172
options: BindgenOptions,
173+
input_headers: Vec<String>,
174+
// Tuples of unsaved file contents of the form (name, contents).
175+
input_header_contents: Vec<(String, String)>,
171176
}
172177

173178
/// Construct a new [`Builder`](./struct.Builder.html).
@@ -180,9 +185,9 @@ impl Builder {
180185
pub fn command_line_flags(&self) -> Vec<String> {
181186
let mut output_vector: Vec<String> = Vec::new();
182187

183-
if let Some(ref header) = self.options.input_header {
184-
//Positional argument 'header'
185-
output_vector.push(header.clone().into());
188+
if let Some(header) = self.input_headers.last().cloned() {
189+
// Positional argument 'header'
190+
output_vector.push(header);
186191
}
187192

188193
self.options
@@ -412,16 +417,19 @@ impl Builder {
412417
})
413418
.count();
414419

420+
output_vector.push("--".into());
421+
415422
if !self.options.clang_args.is_empty() {
416-
output_vector.push("--".into());
417-
self.options
418-
.clang_args
419-
.iter()
420-
.cloned()
421-
.map(|item| {
422-
output_vector.push(item);
423-
})
424-
.count();
423+
output_vector.extend(
424+
self.options
425+
.clang_args
426+
.iter()
427+
.cloned()
428+
);
429+
}
430+
431+
if self.input_headers.len() > 1 {
432+
output_vector.extend(self.input_headers[..self.input_headers.len() - 1].iter().cloned());
425433
}
426434

427435
output_vector
@@ -450,21 +458,15 @@ impl Builder {
450458
/// .unwrap();
451459
/// ```
452460
pub fn header<T: Into<String>>(mut self, header: T) -> Builder {
453-
if let Some(prev_header) = self.options.input_header.take() {
454-
self.options.clang_args.push("-include".into());
455-
self.options.clang_args.push(prev_header);
456-
}
457-
458-
let header = header.into();
459-
self.options.input_header = Some(header);
461+
self.input_headers.push(header.into());
460462
self
461463
}
462464

463465
/// Add `contents` as an input C/C++ header named `name`.
464466
///
465467
/// The file `name` will be added to the clang arguments.
466468
pub fn header_contents(mut self, name: &str, contents: &str) -> Builder {
467-
self.options.input_unsaved_files.push(clang::UnsavedFile::new(name, contents));
469+
self.input_header_contents.push((name.into(), contents.into()));
468470
self
469471
}
470472

@@ -794,9 +796,94 @@ impl Builder {
794796
}
795797

796798
/// Generate the Rust bindings using the options built up thus far.
797-
pub fn generate<'ctx>(self) -> Result<Bindings<'ctx>, ()> {
799+
pub fn generate<'ctx>(mut self) -> Result<Bindings<'ctx>, ()> {
800+
self.options.input_header = self.input_headers.pop();
801+
self.options.clang_args.extend(
802+
self.input_headers
803+
.drain(..)
804+
.flat_map(|header| {
805+
iter::once("-include".into())
806+
.chain(iter::once(header))
807+
})
808+
);
809+
810+
self.options.input_unsaved_files.extend(
811+
self.input_header_contents
812+
.drain(..)
813+
.map(|(name, contents)| clang::UnsavedFile::new(&name, &contents))
814+
);
815+
798816
Bindings::generate(self.options, None)
799817
}
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+
}
800887
}
801888

802889
/// 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)