blob: ffea17c9371865690d93177ffaa8a498dda160bf [file] [view] [edit]
# 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`][pkg-config] file describing where
their headers and binaries live. The [`pkg-config` crate][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`:
```toml
[build-dependencies]
bindgen = "0.73.2"
pkg-config = "0.3"
```
Our `wrapper.h` includes the library's header as usual:
```c
#include <png.h>
```
Then, in `build.rs`, we probe for the library and turn each include path into
a `-I` argument for `clang`:
```rust,ignore
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:
- `bindgen` only generates the Rust declarations, it does not link anything.
Linking is handled by `pkg_config::probe_library`, which prints the needed
`cargo:rustc-link-*` lines, so there is no need to write them by hand as in
the [tutorial](./tutorial-3.md).
- `bindgen::CargoCallbacks` prints a `cargo:rerun-if-changed` line for
`wrapper.h` and for every header it includes, so the bindings are regenerated
when the library's headers change. The `pkg-config` crate prints
`cargo:rerun-if-env-changed` lines for the environment variables it reads,
such as `PKG_CONFIG_PATH`.
- Some libraries also require preprocessor definitions. These are available in
`library.defines` and can be passed to `clang` as `-D` arguments in the same
way.
- Use [`pkg_config::Config`][pkg-config-config] instead of `probe_library` to
require a minimum version, prefer static linking, and so on.
[pkg-config]: https://www.freedesktop.org/wiki/Software/pkg-config/
[pkg-config-crate]: https://docs.rs/pkg-config
[pkg-config-config]: https://docs.rs/pkg-config/latest/pkg_config/struct.Config.html