use std::env;
use std::path::{Path, PathBuf};
fn main() {
println!("cargo:rerun-if-env-changed=WHITEOUT_LIB_DIR");
println!("cargo:rerun-if-env-changed=WHITEOUT_INCLUDE_DIR");
println!("cargo:rerun-if-env-changed=WHITEOUT_STATIC");
if env::var_os("DOCS_RS").is_some() {
return;
}
if let Some(dir) = env::var_os("WHITEOUT_LIB_DIR") {
link_prebuilt(PathBuf::from(dir));
return;
}
#[cfg(feature = "vendored")]
build_vendored();
#[cfg(not(feature = "vendored"))]
{
if pkg_config_probe() {
return;
}
panic!(
"could not locate the whiteout native library.\n\
\n\
The `vendored` feature is disabled, so this crate expects a\n\
prebuilt library. Either:\n\
\n\
* re-enable the default `vendored` feature to build the\n\
bundled C++ sources (needs CMake and a C++20 compiler), or\n\
* set WHITEOUT_LIB_DIR to a directory containing\n\
whiteout_native (add WHITEOUT_STATIC=1 for the static\n\
archive), or\n\
* install the library so pkg-config can find it."
);
}
}
fn publish(lib_dir: &Path, include_dir: Option<&Path>) {
println!("cargo:lib_dir={}", lib_dir.display());
if let Some(inc) = include_dir {
println!("cargo:include={}", inc.display());
}
println!("cargo:has_casc={}", u8::from(cfg!(feature = "casc")));
println!("cargo:has_mpq={}", u8::from(cfg!(feature = "mpq")));
}
fn prebuilt_include(lib_dir: &Path) -> Option<PathBuf> {
if let Some(dir) = env::var_os("WHITEOUT_INCLUDE_DIR") {
return Some(PathBuf::from(dir));
}
[lib_dir.join("..").join("include"), lib_dir.join("include")]
.into_iter()
.find(|p| p.join("whiteout").is_dir())
.map(simplify)
}
fn want_static() -> bool {
env::var("WHITEOUT_STATIC")
.map(|v| v != "0" && !v.eq_ignore_ascii_case("false"))
.unwrap_or(false)
}
fn link_prebuilt(dir: PathBuf) {
if !dir.is_dir() {
panic!(
"WHITEOUT_LIB_DIR points at {} which is not a directory",
dir.display()
);
}
println!("cargo:rustc-link-search=native={}", dir.display());
if want_static() {
println!("cargo:rustc-link-lib=static=whiteout_native_static");
link_cxx_runtime();
} else {
println!("cargo:rustc-link-lib=dylib=whiteout_native");
}
let include = prebuilt_include(&dir);
publish(&dir, include.as_deref());
}
#[allow(dead_code)]
fn vendored_sources() -> Option<PathBuf> {
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").ok()?);
let candidates = [
manifest.join("native"),
manifest.join("..").join("..").join(".."),
];
candidates
.into_iter()
.find(|p| p.join("CMakeLists.txt").is_file() && p.join("include").join("whiteout").is_dir())
.map(simplify)
}
#[allow(dead_code)]
fn simplify(p: PathBuf) -> PathBuf {
let full = p.canonicalize().unwrap_or(p);
let s = full.to_string_lossy().into_owned();
match s.strip_prefix(r"\\?\") {
Some(rest) => PathBuf::from(rest),
None => PathBuf::from(s),
}
}
#[cfg(feature = "vendored")]
fn build_vendored() {
let src = vendored_sources().unwrap_or_else(|| {
panic!(
"the `vendored` feature is enabled but the bundled C++ sources \
are missing.\n\
Expected `native/CMakeLists.txt` beside this crate, or a \
repository checkout above it.\n\
If you are consuming a published crate this is a packaging \
bug — please report it."
)
});
println!(
"cargo:rerun-if-changed={}",
src.join("CMakeLists.txt").display()
);
let mut cfg = cmake::Config::new(&src);
cfg.profile("Release");
cfg.define("WHITEOUT_BUILD_C_BINDINGS", "ON")
.define("WHITEOUT_BUILD_C_STATIC", "ON")
.define("WHITEOUT_BUILD_TESTS", "OFF")
.define("WHITEOUT_BUILD_EXAMPLES", "OFF")
.define("WHITEOUT_USE_ZLIBNG", "OFF")
.define(
"WHITEOUT_ENABLE_CASC",
if cfg!(feature = "casc") { "ON" } else { "OFF" },
)
.define(
"WHITEOUT_ENABLE_MPQ",
if cfg!(feature = "mpq") { "ON" } else { "OFF" },
)
.build_target("whiteout_c_static");
let dst = cfg.build();
let base = dst.join("build").join("c-dist");
let dir = ["Release", "RelWithDebInfo", "Debug", ""]
.iter()
.map(|c| {
if c.is_empty() {
base.clone()
} else {
base.join(c)
}
})
.find(|d| d.is_dir())
.unwrap_or(base);
println!("cargo:rustc-link-search=native={}", dir.display());
println!("cargo:rustc-link-lib=static=whiteout_native_static");
link_static_deps(&dst);
link_cxx_runtime();
publish(&dir, Some(&src.join("include")));
}
#[allow(dead_code)]
fn link_static_deps(dst: &Path) {
let build = dst.join("build");
for sub in ["", "Release", "RelWithDebInfo", "Debug"] {
let dir = if sub.is_empty() {
build.clone()
} else {
build.join(sub)
};
if dir.is_dir() {
println!("cargo:rustc-link-search=native={}", dir.display());
}
}
let mut libs = Vec::new();
if cfg!(feature = "casc") {
libs.push("whiteout_casc");
}
if cfg!(feature = "mpq") {
libs.push("whiteout_mpq");
}
libs.push("whiteout_lib");
for lib in libs {
println!("cargo:rustc-link-lib=static={lib}");
}
}
#[allow(dead_code)]
fn pkg_config_probe() -> bool {
let out = std::process::Command::new("pkg-config")
.args(["--libs", "--silence-errors", "whiteout"])
.output();
match out {
Ok(o) if o.status.success() => {
let flags = String::from_utf8_lossy(&o.stdout).to_string();
let mut lib_dir = None;
for tok in flags.split_whitespace() {
if let Some(p) = tok.strip_prefix("-L") {
println!("cargo:rustc-link-search=native={p}");
lib_dir.get_or_insert_with(|| PathBuf::from(p));
} else if let Some(l) = tok.strip_prefix("-l") {
println!("cargo:rustc-link-lib=dylib={l}");
}
}
if let Some(dir) = lib_dir {
let include = prebuilt_include(&dir);
publish(&dir, include.as_deref());
}
true
}
_ => false,
}
}
fn link_cxx_runtime() {
let target = env::var("TARGET").unwrap_or_default();
if target.contains("msvc") {
for lib in [
"advapi32", "winhttp", "ole32", "shell32", "user32",
] {
println!("cargo:rustc-link-lib=dylib={lib}");
}
return;
}
if target.contains("apple") {
println!("cargo:rustc-link-lib=dylib=c++");
} else {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
}