Finding Libraries with pkg-config
Many C libraries install their headers somewhere that clang does not search
by default. Some keep them in a versioned subdirectory such as
/usr/include/libpng16, and package managers like Homebrew install them under
their own prefix. In that case bindgen fails with an error like
fatal error: 'png.h' file not found.
Such libraries usually ship a pkg-config file describing where
their headers and binaries live. The pkg-config crate can
query it from build.rs, and we can pass the include paths it finds to
bindgen.
First, add pkg-config to the [build-dependencies] section next to bindgen:
[build-dependencies]
bindgen = "0.73.2"
pkg-config = "0.3"
Our wrapper.h includes the library's header as usual:
#include <png.h>
Then, in build.rs, we probe for the library and turn each include path into
a -I argument for clang:
use std::env;
use std::path::PathBuf;
fn main() {
// Ask pkg-config for `libpng`. On success, this also prints the
// `cargo:rustc-link-search` and `cargo:rustc-link-lib` lines needed to
// link against the library.
let library = pkg_config::probe_library("libpng")
.expect("Unable to find libpng with pkg-config");
let bindings = bindgen::Builder::default()
// The input header we would like to generate
// bindings for.
.header("wrapper.h")
// Tell clang where to find the headers included by `wrapper.h`.
.clang_args(
library
.include_paths
.iter()
.map(|path| format!("-I{}", path.display())),
)
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
A few things to note:
bindgenonly generates the Rust declarations, it does not link anything. Linking is handled bypkg_config::probe_library, which prints the neededcargo:rustc-link-*lines, so there is no need to write them by hand as in the tutorial.bindgen::CargoCallbacksprints acargo:rerun-if-changedline forwrapper.hand for every header it includes, so the bindings are regenerated when the library's headers change. Thepkg-configcrate printscargo:rerun-if-env-changedlines for the environment variables it reads, such asPKG_CONFIG_PATH.- Some libraries also require preprocessor definitions. These are available in
library.definesand can be passed toclangas-Darguments in the same way. - Use
pkg_config::Configinstead ofprobe_libraryto require a minimum version, prefer static linking, and so on.