diff --git a/CHANGELOG.md b/CHANGELOG.md index b092e9e57..3a53d516e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Change Log -## [0.3.13] (in development) +## [0.3.13] (2026-06-11) + +### Added + +* Opt-in **static linking** of libxml2. Set the `LIBXML2_STATIC` environment + variable and `build.rs` probes via pkg-config with `.statik(true)`, + emitting `cargo:rustc-link-lib=static=xml2` (plus the transitive `-lm`). + Point `PKG_CONFIG_PATH` at a non-system prefix holding a PIC `libxml2.a` + to get a binary with no runtime `libxml2.so` dependency — independent of + the host's libxml2 SONAME, which bumped `.so.2` → `.so.16` at libxml2 2.14. + Unset (the default) is unchanged: a normal dynamic link against the host + libxml2. +* New cargo features **`runtime`** (default) and **`static`** that select how + bindgen locates libclang to generate the FFI bindings at build time. + `cargo build --no-default-features --features static` routes bindgen to + `clang-sys/static`, enabling fully static / musl / Alpine builds where a + runtime `dlopen` of libclang is unavailable. Together with `LIBXML2_STATIC`, + this resolves [#110](https://github.com/KWARC/rust-libxml/issues/110). ## [0.3.12] (2026-05-23) diff --git a/Cargo.toml b/Cargo.toml index a58eb7ab9..55610137d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libxml" -version = "0.3.12" +version = "0.3.13" edition = "2024" authors = ["Andreas Franzén ", "Deyan Ginev ","Jan Frederik Schaefer "] description = "A Rust wrapper for libxml2 - the XML C parser and toolkit developed for the Gnome project" @@ -17,6 +17,21 @@ exclude = [ [lib] name = "libxml" +[features] +# How bindgen locates libclang to generate the FFI bindings at BUILD time. +# Orthogonal to the `LIBXML2_STATIC` env var, which controls how libxml2 itself is +# linked into your binary (see build.rs). Together they let you build a fully +# static / musl / Alpine binary — see issue #110. +default = ["runtime"] +# clang-sys loads libclang dynamically (dlopen) at build time — the usual desktop +# setup; needs a libclang shared library available while building. +runtime = ["bindgen/runtime"] +# clang-sys links libclang statically — required for fully static / musl / Alpine +# build environments where dlopen of libclang is unavailable. Select it WITHOUT +# the default feature: cargo build --no-default-features --features static +# (clang-sys then needs a static libclang, e.g. via LLVM_CONFIG_PATH). +static = ["bindgen/static"] + [dependencies] libc = "0.2" @@ -34,9 +49,6 @@ pkg-config = "0.3.2" [build-dependencies.bindgen] version = "0.72.1" -features = [ - "runtime", -] default-features = false [dev-dependencies] diff --git a/README.md b/README.md index b78d2585d..775b825c0 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,25 @@ So you have to install CLang 9.0 or greater: - Debian / Ubuntu: `$ apt install libclang-dev` - Fedora: `$ dnf install clang-devel` +### Static linking (musl / Alpine / fully static binaries) + +Two independent knobs build a binary with no runtime libxml2 / libclang +dependency (see [#110](https://github.com/KWARC/rust-libxml/issues/110)): + +* **`static` cargo feature** — selects how `bindgen` finds libclang at build + time. The default `runtime` feature `dlopen`s libclang, which is unavailable + in fully static / musl environments; `static` links it via `clang-sys/static` + instead: + + ``` + cargo build --no-default-features --features static + ``` + +* **`LIBXML2_STATIC` env var** — statically links libxml2 *itself*. Point + `PKG_CONFIG_PATH` at a prefix holding a PIC `libxml2.a` and set + `LIBXML2_STATIC=1`; `build.rs` then emits `cargo:rustc-link-lib=static=xml2`, + so the binary carries no `libxml2.so` SONAME dependency. + ### MacOS [Community contributed](https://github.com/KWARC/rust-libxml/issues/88#issuecomment-890876895): diff --git a/build.rs b/build.rs index 852047ad4..63af295ea 100644 --- a/build.rs +++ b/build.rs @@ -49,7 +49,18 @@ fn find_libxml2() -> Option { all(target_family = "windows", target_env = "gnu") ))] { - let lib = pkg_config::Config::new() + // Opt-in static link: when LIBXML2_STATIC is set and PKG_CONFIG_PATH points + // at a non-system prefix carrying a PIC `libxml2.a`, `.statik(true)` makes + // pkg-config emit `cargo:rustc-link-lib=static=xml2` plus the transitive + // `-lm` (which stays dynamic). pkg-config's static guard refuses a `static=` + // for a /usr/lib system path, so the non-system prefix is what enables this. + // Unset (the default) is unchanged: a normal dynamic link against the host + // libxml2. Used for the self-contained, SONAME-independent release binary. + let mut cfg = pkg_config::Config::new(); + if std::env::var_os("LIBXML2_STATIC").is_some() { + cfg.statik(true); + } + let lib = cfg .probe("libxml-2.0") .expect("Couldn't find libxml2 via pkg-config"); return Some(ProbedLib { @@ -92,6 +103,8 @@ fn generate_bindings(header_dirs: Vec, output_path: &Path) { } fn main() { + // Re-run if the opt-in static toggle flips (see find_libxml2's static branch). + println!("cargo:rerun-if-env-changed=LIBXML2_STATIC"); let bindings_path = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("bindings.rs"); // declare availability of config variable (without setting it) println!("cargo::rustc-check-cfg=cfg(libxml_older_than_2_12)");