#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
#[path = "build_target_flags.rs"]
mod build_target_flags;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
#[path = "build_wpd_patches.rs"]
mod build_wpd_patches;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
mod build_libwpd {
use super::build_target_flags::msvc_only_flags;
use super::build_wpd_patches::patch_wpx_table_header;
use flate2::read::GzDecoder;
use sha2::{Digest, Sha256};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
const LIBREVENGE_VERSION: &str = "0.0.6";
const LIBWPD_VERSION: &str = "0.10.3";
const LIBREVENGE_SHA256: &str = "686cc36be3196a0a808761cfd3951a46ff809cb0e028b0902c787261a1389d0f";
const LIBWPD_SHA256: &str = "ca3575282acff8c952c12160433ad7e73e803ff3f070b8442c7ffa1f3a19f9ae";
const BOOST_SUBSET_SHA256: &str = "802ee17c5e380efbcbb696468ee3c7090aa409db89c2063b4c9b8d3e3aff1e08";
const VCPKG_TRIPLET: &str = "x64-windows-static-md";
pub fn target_os() -> String {
env::var("CARGO_CFG_TARGET_OS").unwrap_or_default()
}
fn targeting_windows() -> bool {
target_os() == "windows"
}
fn target_env() -> String {
env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default()
}
fn vcpkg_root() -> PathBuf {
env::var("VCPKG_ROOT")
.or_else(|_| env::var("VCPKG_INSTALLATION_ROOT"))
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(r"C:\vcpkg"))
}
fn vcpkg_triplet_dir() -> PathBuf {
vcpkg_root().join("installed").join(VCPKG_TRIPLET)
}
fn verify_sha256(bytes: &[u8], expected: &str) {
let digest = Sha256::digest(bytes);
let actual = hex(&digest);
assert_eq!(actual, expected, "checksum mismatch: expected {expected}, got {actual}");
}
fn hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push_str(&format!("{b:02x}"));
}
s
}
fn vendor_dir() -> PathBuf {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set by cargo")).join("vendor")
}
fn extract(out_dir: &Path, archive_name: &str, expected_sha256: Option<&str>, expected_root: &str) -> PathBuf {
let archive_path = vendor_dir().join(archive_name);
let bytes = fs::read(&archive_path).unwrap_or_else(|e| panic!("reading {archive_path:?}: {e}"));
if let Some(sha256) = expected_sha256 {
verify_sha256(&bytes, sha256);
}
let root = out_dir.join(expected_root);
if root.exists() {
fs::remove_dir_all(&root).ok();
}
let mut archive = tar::Archive::new(GzDecoder::new(&bytes[..]));
archive
.unpack(out_dir)
.unwrap_or_else(|e| panic!("failed to extract {archive_name}: {e}"));
assert!(root.is_dir(), "expected {root:?} after extracting {archive_name}");
root
}
const ZLIB_RELEASE_STEMS: &[&str] = &["zlibstatic", "zlib", "zs", "z"];
const ZLIB_DEBUG_STEMS: &[&str] = &["zlibstaticd", "zlibd", "zsd", "zd"];
fn find_zlib_lib(dir: &Path, stems: &[&str]) -> Option<String> {
stems
.iter()
.find(|stem| dir.join(format!("{stem}.lib")).is_file())
.map(|stem| (*stem).to_string())
}
fn link_zlib() {
if !targeting_windows() || target_env() != "msvc" {
println!("cargo:rustc-link-lib=static=z");
return;
}
let triplet = vcpkg_triplet_dir();
let release_lib = triplet.join("lib");
let debug_lib = triplet.join("debug").join("lib");
let resolved = find_zlib_lib(&release_lib, ZLIB_RELEASE_STEMS)
.map(|stem| (release_lib.clone(), stem))
.or_else(|| find_zlib_lib(&debug_lib, ZLIB_DEBUG_STEMS).map(|stem| (debug_lib.clone(), stem)));
match resolved {
Some((dir, stem)) => {
println!("cargo:rustc-link-search=native={}", dir.display());
println!("cargo:rustc-link-lib={stem}");
}
None => {
println!("cargo:rustc-link-search=native={}", release_lib.display());
println!("cargo:rustc-link-lib=zlibstatic");
}
}
}
fn cpp_files(dir: &Path) -> Vec<PathBuf> {
let mut files: Vec<PathBuf> = fs::read_dir(dir)
.unwrap_or_else(|e| panic!("reading {dir:?}: {e}"))
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "cpp"))
.collect();
files.sort();
files
}
fn patch_wpd_msvc_narrowing(wpd: &Path) {
const FILE: &str = "src/lib/WP6GeneralTextPacket.cpp";
const ANCHOR: &str = "m_streamData.data(), m_streamData.size()";
const PATCHED: &str = "m_streamData.data(), (unsigned)m_streamData.size()";
let path = wpd.join(FILE);
let source = fs::read_to_string(&path).unwrap_or_else(|e| panic!("reading {path:?}: {e}"));
assert!(
source.contains(ANCHOR),
"expected narrowing anchor {ANCHOR:?} in {path:?}; the vendored libwpd source changed — \
re-check the MSVC make_shared<WP6SubDocument> narrowing patch"
);
let patched = source.replace(ANCHOR, PATCHED);
fs::write(&path, patched).unwrap_or_else(|e| panic!("writing {path:?}: {e}"));
}
fn patch_wpd_table_header(wpd: &Path) {
let path = wpd.join("src/lib/WPXTable.h");
let source = fs::read_to_string(&path).unwrap_or_else(|error| panic!("reading {path:?}: {error}"));
let patched = patch_wpx_table_header(&source)
.unwrap_or_else(|error| panic!("patching direct cstddef include in {path:?}: {error}"));
fs::write(&path, patched).unwrap_or_else(|error| panic!("writing {path:?}: {error}"));
}
pub fn build() {
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is set by cargo"));
let rev = extract(
&out_dir,
"librevenge-0.0.6.tar.gz",
Some(LIBREVENGE_SHA256),
&format!("librevenge-{LIBREVENGE_VERSION}"),
);
let wpd = extract(
&out_dir,
"libwpd-0.10.3.tar.gz",
Some(LIBWPD_SHA256),
&format!("libwpd-{LIBWPD_VERSION}"),
);
patch_wpd_table_header(&wpd);
patch_wpd_msvc_narrowing(&wpd);
let boost = extract(&out_dir, "boost-subset.tar.gz", Some(BOOST_SUBSET_SHA256), "boost");
let mut build = cc::Build::new();
build
.cpp(true)
.std("c++17")
.warnings(false)
.flag_if_supported("-fvisibility=hidden")
.define("NDEBUG", None)
.include(rev.join("inc"))
.include(rev.join("src/lib"))
.include(wpd.join("inc"))
.include(wpd.join("src/lib"))
.include(&boost)
.include("src");
for flag in msvc_only_flags(&target_os(), &target_env()) {
build.flag(flag);
}
if let Ok(zlib_include) = env::var("DEP_Z_INCLUDE") {
build.include(zlib_include);
}
for f in cpp_files(&rev.join("src/lib")) {
build.file(f);
}
for f in cpp_files(&wpd.join("src/lib")) {
if targeting_windows() && f.file_name().is_some_and(|n| n == "libwpd_math.cpp") {
continue;
}
build.file(f);
}
build.file("src/shim.cpp");
build.compile("xberg_libwpd");
link_zlib();
println!("cargo:rerun-if-changed=src/shim.cpp");
println!("cargo:rerun-if-changed=src/msvc_compat.h");
println!("cargo:rerun-if-changed=vendor/librevenge-0.0.6.tar.gz");
println!("cargo:rerun-if-changed=vendor/libwpd-0.10.3.tar.gz");
println!("cargo:rerun-if-changed=vendor/boost-subset.tar.gz");
if targeting_windows() {
println!("cargo:rerun-if-env-changed=VCPKG_ROOT");
println!("cargo:rerun-if-env-changed=VCPKG_INSTALLATION_ROOT");
}
}
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
if matches!(build_libwpd::target_os().as_str(), "linux" | "macos" | "windows") {
build_libwpd::build();
}
}