Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
- name: test forced system libz
run: ci/test-force-system.bash
# Ensures --all-features builds correctly, the current logic will mean it
# uses stock zlib, not cmake nor cc
- run: |
Expand Down
85 changes: 60 additions & 25 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
//! # Build behavior
//!
//! This script selects and links one zlib implementation. Its decisions are made in the
//! following order:
//!
//! 1. `LIBZ_SYS_STATIC=0` selects the system libz. This hard override skips implementation
//! features, compiler probing, and bundled builds, while allowing pkg-config or vcpkg to emit
//! available link and include metadata. The caller must ultimately provide a linkable libz.
//! 2. The `zlib-ng` or `zlib-ng-no-cmake-experimental-community-maintained` feature builds
//! zlib-ng in compatibility mode, unless `stock-zlib` is also enabled or the target is
//! `wasm32-unknown-unknown`. The former uses CMake and the latter uses `cc`.
//! 3. Android, Haiku, and OpenHarmony targets link `z` directly.
//! 4. `LIBZ_SYS_STATIC=1` requests bundled stock zlib. Any other value falls back to the
//! `static` feature setting.
//! 5. Unless static linking was requested, the target is MSVC, or both host and target are
//! FreeBSD or DragonFly, `pkg-config` probes `zlib`. It emits Cargo link metadata and this
//! script forwards non-empty include paths, but system library directories are omitted.
//! Probe failure is only a warning.
//! 6. Windows targets try vcpkg. A successful lookup emits its link metadata and include paths
//! and completes the script.
//! 7. MSVC, MinGW, and explicit static builds compile the bundled stock zlib unless the system
//! override is active. The override links `z` directly; remaining targets first compile and
//! link `src/smoke.c` with `-lz`, then link `z` on success or bundle it on failure.
//!
//! Except for the `0` hard override, `LIBZ_SYS_STATIC` and the `static` feature are preferences
//! rather than guarantees because the earlier implementation and platform branches take
//! precedence. Changing `LIBZ_SYS_STATIC` reruns the script.
//!
//! ## Bundled stock zlib
//!
//! The bundled build disables warnings and compiles into `$OUT_DIR/lib`. It defines `STDC` on
//! every target. Non-Windows targets also define `_LARGEFILE64_SOURCE` and use hidden symbol
//! visibility. `wasm32-unknown-unknown` additionally defines `Z_SOLO` and omits the `gz*`
//! sources; other targets include them. An AArch64 target with the `crc` target feature enables
//! the compiler's Armv8 CRC support when available.
//!
//! After compilation, `zlib.h` and `zconf.h` are copied to `$OUT_DIR/include`, a `zlib.pc` file
//! is generated in `$OUT_DIR/lib/pkgconfig`, and Cargo receives the root, native library search
//! path, and include directory. Cargo also reruns this script when `build.rs`, `zng/cmake.rs`, or
//! `zng/cc.rs` changes.

use std::env;
use std::fs;
use std::path::PathBuf;
Expand All @@ -11,14 +52,23 @@ fn main() {
let host = env::var("HOST").unwrap();
let target = env::var("TARGET").unwrap();

let link_static = option_env!("LIBZ_SYS_STATIC")
.and_then(|s| s.parse::<u8>().ok())
.and_then(|b| match b {
0 => Some(false),
1 => Some(true),
_ => None,
});
let force_system = link_static == Some(false);

let host_and_target_contain = |s| host.contains(s) && target.contains(s);

let want_ng = cfg!(any(
feature = "zlib-ng",
feature = "zlib-ng-no-cmake-experimental-community-maintained"
)) && !cfg!(feature = "stock-zlib");

if want_ng && target != "wasm32-unknown-unknown" {
if want_ng && !force_system && target != "wasm32-unknown-unknown" {
return build_zlib_ng(&target, true);
}

Expand All @@ -30,7 +80,7 @@ fn main() {
return;
}

let want_static = should_link_static();
let want_static = link_static.unwrap_or(cfg!(feature = "static"));
// Don't run pkg-config if we're linking statically (we'll build below) and
// also don't run pkg-config on FreeBSD/DragonFly. That'll end up printing
// `-L /usr/lib` which wreaks havoc with linking to an OpenSSL in /usr/local/lib
Expand Down Expand Up @@ -79,17 +129,21 @@ fn main() {
// - MSVC basically never has zlib preinstalled
// - MinGW picks up a bunch of weird paths we don't like
// - Explicit opt-in via `want_static`
if target.contains("msvc") || target.contains("pc-windows-gnu") || want_static {
//
// An explicit system override takes precedence over all three.
if !force_system
&& (target.contains("msvc") || target.contains("pc-windows-gnu") || want_static)
{
return build_zlib(&mut cfg, &target);
}

// If we've gotten this far we're probably a pretty standard platform.
// Almost all platforms here ship libz by default, but some don't have
// pkg-config files that we would find above.
//
// In any case test if zlib is actually installed and if so we link to it,
// otherwise continue below to build things.
if zlib_installed(&mut cfg) {
// In any case link an explicitly requested system zlib, or test if zlib is
// installed and link to it, otherwise continue below to build things.
if force_system || zlib_installed(&mut cfg) {
println!("cargo:rustc-link-lib=z");
return;
}
Expand Down Expand Up @@ -252,22 +306,3 @@ fn zlib_installed(cfg: &mut cc::Build) -> bool {

false
}

/// The environment variable `LIBZ_SYS_STATIC` is first checked for a value of `0` (false) or `1` (true),
/// before considering the `static` feature when no explicit ENV value was detected.
/// When `libz-sys` is a transitive dependency from a crate that forces static linking via the `static` feature,
/// this enables the build environment to revert that preference via `LIBZ_SYS_STATIC=0`.
/// The default is otherwise `false`.
fn should_link_static() -> bool {
let has_static_env: Option<&'static str> = option_env!("LIBZ_SYS_STATIC");
let has_static_cfg = cfg!(feature = "static");

has_static_env
.and_then(|s: &str| s.parse::<u8>().ok())
.and_then(|b| match b {
0 => Some(false),
1 => Some(true),
_ => None,
})
.unwrap_or(has_static_cfg)
}
33 changes: 33 additions & 0 deletions ci/test-force-system.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Verify that LIBZ_SYS_STATIC=0 overrides the static feature and selects system libz
# without compiling or emitting metadata for the bundled library.
set -euo pipefail

target_dir=target/force-system-libz

CC=false LIBZ_SYS_STATIC=0 CARGO_TARGET_DIR="$target_dir" \
cargo test --features static --no-run

build_output=$(find "$target_dir/debug/build" -path '*/libz-sys-*/output' -print -quit)
if [[ -z "$build_output" ]]; then
echo 'error: libz-sys build-script output was not found' >&2
exit 1
fi

if ! grep -Fxq 'cargo:rustc-link-lib=z' "$build_output"; then
echo "error: $build_output does not request system libz" >&2
exit 1
fi

if grep -Fxq 'cargo:rustc-link-lib=static=z' "$build_output"; then
echo "error: $build_output requests static libz" >&2
exit 1
fi

archive=$(find "$target_dir" -name libz.a -print -quit)
if [[ -n "$archive" ]]; then
echo "error: bundled libz archive was produced at $archive" >&2
exit 1
fi

echo 'LIBZ_SYS_STATIC=0 linked system libz without building bundled libz.a'
Loading