Skip to content

Commit

Permalink
Add --depfile option
Browse files Browse the repository at this point in the history
Add an option to output a depfile for outside build-systems to learn
the source file dependencies of the bindings.
This can be used by 3rd party build system integrations to only rerun bindgen
when necessary.
  • Loading branch information
jschwe committed Mar 9, 2023
1 parent 36ebe9a commit 2ba92fa
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 0 deletions.
50 changes: 50 additions & 0 deletions src/bindgen/bindings.rs
Expand Up @@ -29,6 +29,7 @@ pub struct Bindings {
constants: Vec<Constant>,
items: Vec<ItemContainer>,
functions: Vec<Function>,
source_files: Vec<path::PathBuf>,
/// Bindings are generated by a recursive call to cbindgen
/// and shouldn't do anything when written anywhere.
noop: bool,
Expand All @@ -50,6 +51,7 @@ impl Bindings {
globals: Vec<Static>,
items: Vec<ItemContainer>,
functions: Vec<Function>,
source_files: Vec<path::PathBuf>,
noop: bool,
) -> Bindings {
Bindings {
Expand All @@ -61,6 +63,7 @@ impl Bindings {
constants,
items,
functions,
source_files,
noop,
}
}
Expand Down Expand Up @@ -128,6 +131,53 @@ impl Bindings {
fields
}

pub fn generate_depfile<P: AsRef<path::Path>>(
&self,
header_path: P,
depfile_path: P,
config_file: Option<&str>,
) {
if let Some(dir) = depfile_path.as_ref().parent() {
if !dir.exists() {
std::fs::create_dir_all(dir).unwrap()
}
}
let canon_header_path = header_path.as_ref().canonicalize().unwrap();
let mut canon_source_files: Vec<_> = self
.source_files
.iter()
.map(|p| p.canonicalize().unwrap())
.collect();
// Changing the config-file should also re-trigger cbindgen.
if let Some(file) = config_file {
canon_source_files.push(PathBuf::from(file).canonicalize().unwrap())
}

// When writing the depfile we must escape whitespace in paths to avoid it being interpreted
// as a seperator.
// It is not clear how to otherwise _correctly_ replace whitespace in a non-unicode
// compliant slice, without knowing the encoding, so we lossy convert such cases,
// to avoid panics.
let mut depfile = File::create(depfile_path).unwrap();
write!(
&mut depfile,
"{}:",
canon_header_path.to_string_lossy().replace(' ', "\\ ")
)
.expect("Writing header name to depfile failed");
canon_source_files.into_iter().for_each(|source_file| {
// Add line-continue and line-break and then indent with 4 spaces.
// This makes the output more human-readable.
depfile.write_all(b" \\\n ").unwrap();
let escaped_path = source_file.to_string_lossy().replace(' ', "\\ ");
depfile.write_all(escaped_path.as_bytes()).unwrap();
});

writeln!(&mut depfile).unwrap();

depfile.flush().unwrap();
}

pub fn write_to_file<P: AsRef<path::Path>>(&self, path: P) -> bool {
if self.noop {
return false;
Expand Down
4 changes: 4 additions & 0 deletions src/bindgen/builder.rs
Expand Up @@ -359,6 +359,7 @@ impl Builder {
Default::default(),
Default::default(),
Default::default(),
Default::default(),
true,
));
}
Expand Down Expand Up @@ -391,6 +392,8 @@ impl Builder {
result.extend_with(&parser::parse_lib(cargo, &self.config)?);
}

result.source_files.extend_from_slice(self.srcs.as_slice());

Library::new(
self.config,
result.constants,
Expand All @@ -401,6 +404,7 @@ impl Builder {
result.opaque_items,
result.typedefs,
result.functions,
result.source_files,
)
.generate()
}
Expand Down
5 changes: 5 additions & 0 deletions src/bindgen/library.rs
Expand Up @@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::collections::HashMap;
use std::path::PathBuf;

use crate::bindgen::bindings::Bindings;
use crate::bindgen::config::{Config, Language, SortKey};
Expand All @@ -25,6 +26,7 @@ pub struct Library {
opaque_items: ItemMap<OpaqueItem>,
typedefs: ItemMap<Typedef>,
functions: Vec<Function>,
source_files: Vec<PathBuf>,
}

impl Library {
Expand All @@ -39,6 +41,7 @@ impl Library {
opaque_items: ItemMap<OpaqueItem>,
typedefs: ItemMap<Typedef>,
functions: Vec<Function>,
source_files: Vec<PathBuf>,
) -> Library {
Library {
config,
Expand All @@ -50,6 +53,7 @@ impl Library {
opaque_items,
typedefs,
functions,
source_files,
}
}

Expand Down Expand Up @@ -135,6 +139,7 @@ impl Library {
globals,
items,
functions,
self.source_files,
false,
))
}
Expand Down
5 changes: 5 additions & 0 deletions src/bindgen/parser.rs
Expand Up @@ -181,6 +181,8 @@ impl<'a> Parser<'a> {
}
}

self.out.source_files = self.cache_src.keys().map(|k| k.to_owned()).collect();

Ok(())
}

Expand Down Expand Up @@ -406,6 +408,7 @@ pub struct Parse {
pub opaque_items: ItemMap<OpaqueItem>,
pub typedefs: ItemMap<Typedef>,
pub functions: Vec<Function>,
pub source_files: Vec<FilePathBuf>,
}

impl Parse {
Expand All @@ -419,6 +422,7 @@ impl Parse {
opaque_items: ItemMap::default(),
typedefs: ItemMap::default(),
functions: Vec::new(),
source_files: Vec::new(),
}
}

Expand Down Expand Up @@ -466,6 +470,7 @@ impl Parse {
self.opaque_items.extend_with(&other.opaque_items);
self.typedefs.extend_with(&other.typedefs);
self.functions.extend_from_slice(&other.functions);
self.source_files.extend_from_slice(&other.source_files);
}

fn load_syn_crate_mod<'a>(
Expand Down
17 changes: 17 additions & 0 deletions src/main.rs
Expand Up @@ -262,6 +262,20 @@ fn main() {
.help("Report errors only (overrides verbosity options).")
.required(false),
)
.arg(
Arg::new("depfile")
.value_name("PATH")
.long("depfile")
.takes_value(true)
.min_values(1)
.max_values(1)
.required(false)
.help("Generate a depfile at the given Path listing the source files \
cbindgen traversed when generating the bindings. Useful when \
integrating cbindgen into 3rd party build-systems. \
This option is ignored if `--out` is missing."
)
)
.get_matches();

if !matches.is_present("out") && matches.is_present("verify") {
Expand Down Expand Up @@ -306,6 +320,9 @@ fn main() {
error!("Bindings changed: {}", file);
std::process::exit(2);
}
if let Some(depfile) = matches.value_of("depfile") {
bindings.generate_depfile(file, depfile, matches.value_of("config"))
}
}
_ => {
bindings.write(io::stdout());
Expand Down

0 comments on commit 2ba92fa

Please sign in to comment.