Skip to content

Commit

Permalink
bindgen::ParseCallbacks: support tracking env variable usage for cargo
Browse files Browse the repository at this point in the history
bindgen currently has a `bindgen/build.rs` which attempts to have cargo
rebuild bindgen when TARGET specific env variables change, specifically
`BINDGEN_EXTRA_CLANG_ARGS_<target>`. Unfortunately, this doesn't have
the desired effect in most cases.

Specifically, when a crate `A` has `bindgen` in `build-dependencies`,
and we're cross compiling `A` for target `T` on a host `H`, `bindgen`'s
`build.rs` will observe `TARGET` set to `H` (the host target name)
instead of `T` (because `bindgen` itself is being built for `H` and not
`T`). Then, within the build script of crate `A`, one would use
`bindgen` to generate bindings, and now `TARGET` is set to `T`, so
different env variables are used.

Allow crates using `bindgen` in build scripts to correctly handle env
variable changes by adding `ParseCallbacks::read_env_var()` to track
env var reads and adding a basic implementation to `CargoCallbacks` to
emit `cargo:rerun-if-env-changed` lines.
  • Loading branch information
codyps authored and pvdrz committed Apr 10, 2023
1 parent 1e3e25f commit 461faa3
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 15 deletions.
4 changes: 4 additions & 0 deletions bindgen/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ pub trait ParseCallbacks: fmt::Debug {
/// This will be called on every file inclusion, with the full path of the included file.
fn include_file(&self, _filename: &str) {}

/// This will be called every time an environment variable is read (whether or not it has any
/// content), with the name of the env variable.
fn read_env_var(&self, _key: &str) {}

/// This will be called to determine whether a particular blocklisted type
/// implements a trait or not. This will be used to implement traits on
/// other types containing the blocklisted type.
Expand Down
59 changes: 44 additions & 15 deletions bindgen/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,13 @@ use parse::ParseError;
use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::env;
use std::ffi::OsStr;
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::str::FromStr;
use std::rc::Rc;

// Some convenient typedefs for a fast hash map and hash set.
type HashMap<K, V> = rustc_hash::FxHashMap<K, V>;
Expand Down Expand Up @@ -278,13 +280,18 @@ pub fn builder() -> Builder {
Default::default()
}

fn get_extra_clang_args() -> Vec<String> {
fn get_extra_clang_args(
parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>],
) -> Vec<String> {
// Add any extra arguments from the environment to the clang command line.
let extra_clang_args =
match get_target_dependent_env_var("BINDGEN_EXTRA_CLANG_ARGS") {
None => return vec![],
Some(s) => s,
};
let extra_clang_args = match get_target_dependent_env_var(
parse_callbacks,
"BINDGEN_EXTRA_CLANG_ARGS",
) {
None => return vec![],
Some(s) => s,
};

// Try to parse it with shell quoting. If we fail, make it one single big argument.
if let Some(strings) = shlex::split(&extra_clang_args) {
return strings;
Expand All @@ -296,7 +303,9 @@ impl Builder {
/// Generate the Rust bindings using the options built up thus far.
pub fn generate(mut self) -> Result<Bindings, BindgenError> {
// Add any extra arguments from the environment to the clang command line.
self.options.clang_args.extend(get_extra_clang_args());
self.options
.clang_args
.extend(get_extra_clang_args(&self.options.parse_callbacks));

// Transform input headers to arguments on the clang command line.
self.options.clang_args.extend(
Expand Down Expand Up @@ -379,7 +388,7 @@ impl Builder {
cmd.arg(a);
}

for a in get_extra_clang_args() {
for a in get_extra_clang_args(&self.options.parse_callbacks) {
cmd.arg(a);
}

Expand Down Expand Up @@ -1152,22 +1161,38 @@ pub fn clang_version() -> ClangVersion {
}
}

fn env_var<K: AsRef<str> + AsRef<OsStr>>(
parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>],
key: K,
) -> Result<String, std::env::VarError> {
for callback in parse_callbacks {
callback.read_env_var(key.as_ref());
}
std::env::var(key)
}

/// Looks for the env var `var_${TARGET}`, and falls back to just `var` when it is not found.
fn get_target_dependent_env_var(var: &str) -> Option<String> {
if let Ok(target) = env::var("TARGET") {
if let Ok(v) = env::var(format!("{}_{}", var, target)) {
fn get_target_dependent_env_var(
parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>],
var: &str,
) -> Option<String> {
if let Ok(target) = env_var(parse_callbacks, "TARGET") {
if let Ok(v) = env_var(parse_callbacks, format!("{}_{}", var, target)) {
return Some(v);
}
if let Ok(v) = env::var(format!("{}_{}", var, target.replace('-', "_")))
{
if let Ok(v) = env_var(
parse_callbacks,
format!("{}_{}", var, target.replace('-', "_")),
) {
return Some(v);
}
}
env::var(var).ok()

env_var(parse_callbacks, var).ok()
}

/// A ParseCallbacks implementation that will act on file includes by echoing a rerun-if-changed
/// line
/// line and on env variable usage by echoing a rerun-if-env-changed line
///
/// When running inside a `build.rs` script, this can be used to make cargo invalidate the
/// generated bindings whenever any of the files included from the header change:
Expand All @@ -1185,6 +1210,10 @@ impl callbacks::ParseCallbacks for CargoCallbacks {
fn include_file(&self, filename: &str) {
println!("cargo:rerun-if-changed={}", filename);
}

fn read_env_var(&self, key: &str) {
println!("cargo:rerun-if-env-changed={}", key);
}
}

/// Test command_line_flag function.
Expand Down

0 comments on commit 461faa3

Please sign in to comment.