Cargo, Windows, pkg-config and build-dependencies



Regarding the build-dependencies that some crates are having on pkg-config, what is the approach for those of us trying to use the MSVC build?

Is it possible to make entries in the build-dependencies optional as in the dependencies section?

Basically what are the best practices to build such crates not only on WIndows, but in general for OSes which don’t have pkg-config available?



The pkg-config dependency is harmless because that small crate just calls pkg-config and parses its output. There’s an issue though that its error type is String so one can’t expect to discriminate failure modes. I’ve just been considering making a PR to add a proper error type to pkg-config.

As to pkg-config alternatives it seems to me that the only “idiomatic” alternative is building the dependency from source if pkg-config fails for whatever reason.

The actual libgit2 build script does something slightly different and unexpected however:

    if env::var("LIBGIT2_SYS_USE_PKG_CONFIG").is_ok() {
        if pkg_config::find_library("libgit2").is_ok() {

openssl-sys can’t get away with building from source so it allows setting the search paths in env variables and calls pkg-config if none of them are set:

    let lib_dir = env::var("OPENSSL_LIB_DIR").ok();
    let include_dir = env::var("OPENSSL_INCLUDE_DIR").ok();

    if lib_dir.is_none() && include_dir.is_none() {
        // rustc doesn't seem to work with pkg-config's output in mingw64
        if !target.contains("windows") {
            if let Ok(info) = pkg_config::find_library("openssl") {
                // avoid empty include paths as they are not supported by GCC
                if info.include_paths.len() > 0 {
                    let paths = env::join_paths(info.include_paths).unwrap();
                    println!("cargo:include={}", paths.to_str().unwrap());
        if let Some(mingw_paths) = get_mingw_in_path() {
            for path in mingw_paths {
                println!("cargo:rustc-link-search=native={}", path);


I’ve personally found pkg-config to be pretty unreliable on Windows, so I would recommend just avoiding it altogether. In my opinion the “most robust” build scripts do something like:

  1. Try to use the system version of the package, if available. This may involve probing, using pkg-config, etc. This may be disabled by default (e.g. in libgit2’s case) or come with an option to always disable (as with the pkg-config crate itself).
  2. Failing that, either provide an error with information about how to install the crate, or compile a locally bundled version of the source code. For example this is how libcurl-sys and libgit2-sys work.

For MSVC you’ll almost always need a branch in logic to compile for either Unix or MSVC, although if you’re using CMake then there’s a cmake crate to in theory handle that all for you.

Hope that helps! I will agree that writing robust build scripts is kinda hard but it’s unfortunately just a hard problem!


@alexcrichton Thanks for the description on the current state of affairs.


A year later this still looks like it’s the current state of affairs. There’s a room for improvement.

  • I’m inadvertently screwing Windows users by relying on pkg-config. It seems pkg-config doesn’t have any fallback at all for msvc targets, and I don’t know how to provide one. It’d be nice if pkg-config (or another crate like it) had a cross-platform fallback built-in.

  • Existing alternatives are ad-hoc. Some support env vars, some bundle the code. There’s a lot of duplication, little unification. Sadly, some of it is inherent to the problem due to platforms’ quirks, and messiness and fragility of include paths and linker flags.

  • There’s no standard for sharing information between build scripts OK, there is, thanks @sfackler


If your crate depends on libz-sys, the include directory is provided to your crate’s build script via the DEP_Z_INCLUDE environment variable: