From 59a81cac9ada22b5ea6ca2841f5bd1229f1dd659 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Tue, 11 Jul 2023 00:46:38 +0100 Subject: [PATCH] build: `LIBGIT2_NO_VENDOR` to force to use system libgit2 Specify `LIBGIT2_NO_VENDOR` to force to use system libgit2. Due to the additive nature of Cargo features, if some crate in the dependency graph activates `vendored` feature, there is no way to revert it back. This env var serves as a workaround for this purpose. An alternative is having `no_vendored` feature. We still need to modify `build.rs` to make both `vendored` and `no_vendored` play nice with each other (or bail out as they are mutual-exclusive). However, there is no way to activate a Cargo feature via environment variable (see rust-lang/cargo#4829). Altering environment variables may be the only way to interact with some external build systems. It is also pretty common that people don't want to vendor anything and rather to see the build fail than vendoring. Some prior arts: * `OPENSSL_NO_VENDOR` in `openssl` crate, which serves the exact purpose of `LIBGIT2_NO_VENDOR` [^1]. * Crate `ssh2` has a similar `LIBSSH2_SYS_USE_PKG_CONFIG`, but it doesn't force to do so. It still falls back to vendored lib [^2]. * Crate `curl` has a feature `force-system-lib-on-osx` to trump all others features. It was created primarily for Rust releases [^3]. More motivations why this is something we want: * We have no control over transitive dependencies that accidentally activate the vendored feature. And there is no way to turn it off. * If the build environment has the wrong system libgit2, we want build to always fail because that is a real mistake. For example, libgit2 expects a range 1.6.4..1.7.0 and it silently goes vendoring if nothing found. We may want an explicit error like "your system needs a newer libgit2 to proceed". [^1]: https://github.com/sfackler/rust-openssl/blob/50787ed35bf9efa9dd3cbb1993a2564014b67489/openssl/src/lib.rs#L65 [^2]: https://github.com/alexcrichton/ssh2-rs/blob/d9a1dfac4b8c09c5437eb477606b82aa4f67b092/libssh2-sys/build.rs#L22-L33 [^3]: https://github.com/alexcrichton/curl-rust/blob/431babf1dffe205641793353d3d57fdd36fe8534/curl-sys/build.rs#L15-L20 --- README.md | 5 +++++ libgit2-sys/build.rs | 49 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7a756bae9a..01e0353062 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,11 @@ source for libgit2 is included in the libgit2-sys crate so there's no need to pre-install the libgit2 library, the libgit2-sys crate will figure that and/or build that for you. +You can enable the Cargo feature `vendored-libgit2` to always compile and +statically link to a copy of libgit2. Sometimes the libgit2 on the system is +required to be found and used even when `vendored-libgit2` is activated. In +this case, you shall set the environment variable `LIBGIT2_NO_VENDOR=1`. + ## Building git2-rs ```sh diff --git a/libgit2-sys/build.rs b/libgit2-sys/build.rs index 6917ed9348..364123bf4e 100644 --- a/libgit2-sys/build.rs +++ b/libgit2-sys/build.rs @@ -4,22 +4,55 @@ use std::io; use std::path::{Path, PathBuf}; use std::process::Command; +/// Tries to use system libgit2 and emits necessary build script instructions. +fn try_system_libgit2() -> Result { + let mut cfg = pkg_config::Config::new(); + match cfg.range_version("1.7.0".."1.8.0").probe("libgit2") { + Ok(lib) => { + for include in &lib.include_paths { + println!("cargo:root={}", include.display()); + } + Ok(lib) + } + Err(e) => { + println!("cargo:warning=failed to probe system libgit2: {e}"); + Err(e) + } + } +} + fn main() { let https = env::var("CARGO_FEATURE_HTTPS").is_ok(); let ssh = env::var("CARGO_FEATURE_SSH").is_ok(); let vendored = env::var("CARGO_FEATURE_VENDORED").is_ok(); let zlib_ng_compat = env::var("CARGO_FEATURE_ZLIB_NG_COMPAT").is_ok(); + // Specify `LIBGIT2_NO_VENDOR` to force to use system libgit2. + // Due to the additive nature of Cargo features, if some crate in the + // dependency graph activates `vendored` feature, there is no way to revert + // it back. This env var serves as a workaround for this purpose. + println!("cargo:rerun-if-env-changed=LIBGIT2_NO_VENDOR"); + let forced_no_vendor = env::var_os("LIBGIT2_NO_VENDOR").map_or(false, |s| s != "0"); + + if forced_no_vendor { + if try_system_libgit2().is_err() { + panic!( + "\ +The environment variable `LIBGIT2_NO_VENDOR` has been set but no compatible system libgit2 could be found. +The build is now aborting. To disable, unset the variable or use `LIBGIT2_NO_VENDOR=0`. +", + ); + } + + // We've reached here, implying we're using system libgit2. + return; + } + // To use zlib-ng in zlib-compat mode, we have to build libgit2 ourselves. let try_to_use_system_libgit2 = !vendored && !zlib_ng_compat; - if try_to_use_system_libgit2 { - let mut cfg = pkg_config::Config::new(); - if let Ok(lib) = cfg.range_version("1.7.0".."1.8.0").probe("libgit2") { - for include in &lib.include_paths { - println!("cargo:root={}", include.display()); - } - return; - } + if try_to_use_system_libgit2 && try_system_libgit2().is_ok() { + // using system libgit2 has worked + return; } println!("cargo:rustc-cfg=libgit2_vendored");