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.

Testing is done via CMake integration tests, since CMake
is a 3rd party buildsystem which supports depfiles.
  • Loading branch information
jschwe committed May 29, 2023
1 parent c613aff commit 89c4378
Show file tree
Hide file tree
Showing 37 changed files with 674 additions and 9 deletions.
41 changes: 41 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,47 @@ fn generate_tests() {
dst.flush().unwrap();
}

fn generate_depfile_tests() {
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};

let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let mut dst = File::create(Path::new(&out_dir).join("depfile_tests.rs")).unwrap();

let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let tests_dir = manifest_dir.join("tests").join("depfile");
let tests = fs::read_dir(&tests_dir).unwrap();

let entries = tests.map(|t| t.expect("Couldn't read test file"));

println!("cargo:rerun-if-changed={}", tests_dir.display());

for entry in entries {
if entry.file_type().unwrap().is_file() {
continue;
};
let path_segment = entry.file_name().to_str().unwrap().to_owned();

let identifier = path_segment
.replace(|c| !char::is_alphanumeric(c), "_")
.replace("__", "_");

writeln!(
dst,
"test_file!(test_depfile_{}, {:?}, {:?});",
identifier,
path_segment,
entry.path(),
)
.unwrap();
}

dst.flush().unwrap();
}

fn main() {
generate_tests();
generate_depfile_tests();
}
44 changes: 44 additions & 0 deletions src/bindgen/bindings.rs
Original file line number Diff line number Diff line change
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,47 @@ impl Bindings {
fields
}

pub fn generate_depfile<P: AsRef<path::Path>>(&self, header_path: P, depfile_path: P) {
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()
.chain(self.config.config_path.as_ref().into_iter())
.map(|p| p.canonicalize().unwrap())
.collect();
// Sorting makes testing easier by ensuring the output is ordered.
canon_source_files.sort_unstable();

// 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
Original file line number Diff line number Diff line change
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
13 changes: 8 additions & 5 deletions src/bindgen/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use std::collections::{BTreeMap, HashMap};
use std::default::Default;
use std::str::FromStr;
use std::{fmt, fs, path::Path as StdPath};
use std::{fmt, fs, path::Path as StdPath, path::PathBuf as StdPathBuf};

use serde::de::value::{MapAccessDeserializer, SeqAccessDeserializer};
use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
Expand Down Expand Up @@ -1003,6 +1003,8 @@ pub struct Config {
pub only_target_dependencies: bool,
/// Configuration options specific to Cython.
pub cython: CythonConfig,
#[serde(skip)]
pub(crate) config_path: Option<StdPathBuf>,
}

impl Default for Config {
Expand Down Expand Up @@ -1045,6 +1047,7 @@ impl Default for Config {
pointer: PtrConfig::default(),
only_target_dependencies: false,
cython: CythonConfig::default(),
config_path: None,
}
}
}
Expand Down Expand Up @@ -1086,10 +1089,10 @@ impl Config {
)
})?;

match toml::from_str::<Config>(&config_text) {
Ok(x) => Ok(x),
Err(e) => Err(format!("Couldn't parse config file: {}.", e)),
}
let mut config = toml::from_str::<Config>(&config_text)
.map_err(|e| format!("Couldn't parse config file: {}.", e))?;
config.config_path = Some(StdPathBuf::from(file_name.as_ref()));
Ok(config)
}

pub fn from_root_or_default<P: AsRef<StdPath>>(root: P) -> Config {
Expand Down
5 changes: 5 additions & 0 deletions src/bindgen/library.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub fn parse_src(src_file: &FilePath, config: &Config) -> ParseResult {
};

context.parse_mod(&pkg_ref, src_file, 0)?;
context.out.source_files = context.cache_src.keys().map(|k| k.to_owned()).collect();
Ok(context.out)
}

Expand All @@ -79,6 +80,7 @@ pub(crate) fn parse_lib(lib: Cargo, config: &Config) -> ParseResult {

let binding_crate = context.lib.as_ref().unwrap().binding_crate_ref();
context.parse_crate(&binding_crate)?;
context.out.source_files = context.cache_src.keys().map(|k| k.to_owned()).collect();
Ok(context.out)
}

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
Original file line number Diff line number Diff line change
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)
}
}
_ => {
bindings.write(io::stdout());
Expand Down
Loading

0 comments on commit 89c4378

Please sign in to comment.