use std::env;
use std::process::Command;
fn main() {
let php_config = env::var("BEXT_PHP_CONFIG").unwrap_or_else(|_| "php-config".into());
let includes = php_config_flag(&php_config, "--includes");
let ldflags = php_config_flag(&php_config, "--ldflags");
let _libs = php_config_flag(&php_config, "--libs");
let prefix = php_config_flag(&php_config, "--prefix");
let extension_dir = php_config_flag(&php_config, "--extension-dir");
let configure_options = php_config_flag(&php_config, "--configure-options");
let has_zts = configure_options.contains("enable-zts")
|| configure_options.contains("zts")
|| php_has_zts_define(&includes);
println!("cargo:rustc-check-cfg=cfg(php_zts)");
if has_zts {
println!("cargo:warning=PHP ZTS (thread safety) detected — multi-threaded pool enabled");
println!("cargo:rustc-cfg=php_zts");
} else {
println!(
"cargo:warning=PHP NTS detected. bext-php will run with a single worker. \
For multi-threaded PHP, rebuild PHP with --enable-zts."
);
}
let mut build = cc::Build::new();
build.file("sapi/bext_php_sapi.c").warnings(true);
for flag in includes.split_whitespace() {
if let Some(path) = flag.strip_prefix("-I") {
build.include(path);
}
}
if has_zts {
build.define("ZTS", None);
}
build.flag("-pthread");
build.compile("bext_php_sapi");
let lib_dir = format!("{}/lib", prefix);
println!("cargo:rustc-link-search=native={}", lib_dir);
if let Some(parent) = std::path::Path::new(&extension_dir).parent() {
println!("cargo:rustc-link-search=native={}", parent.display());
}
println!("cargo:rustc-link-search=native=/usr/lib");
for flag in ldflags.split_whitespace() {
if let Some(path) = flag.strip_prefix("-L") {
println!("cargo:rustc-link-search=native={}", path);
}
}
let static_lib = std::path::Path::new(&lib_dir).join("libphp.a");
if static_lib.exists() {
println!("cargo:warning=Linking libphp statically for portability");
println!("cargo:rustc-link-lib=static=php");
for flag in _libs.split_whitespace() {
if let Some(lib) = flag.strip_prefix("-l") {
println!("cargo:rustc-link-lib=dylib={}", lib);
}
}
} else {
println!("cargo:rustc-link-lib=dylib=php");
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir);
println!("cargo:php_lib_dir={}", lib_dir);
}
println!("cargo:rustc-link-lib=dylib=pthread");
#[cfg(target_os = "linux")]
{
println!("cargo:rustc-link-lib=dylib=dl");
println!("cargo:rustc-link-lib=dylib=resolv");
}
println!("cargo:rerun-if-changed=sapi/bext_php_sapi.c");
println!("cargo:rerun-if-changed=sapi/bext_php_sapi.h");
println!("cargo:rerun-if-env-changed=BEXT_PHP_CONFIG");
}
fn php_has_zts_define(includes: &str) -> bool {
for flag in includes.split_whitespace() {
if let Some(path) = flag.strip_prefix("-I") {
let config_h = std::path::Path::new(path).join("main/php_config.h");
if let Ok(content) = std::fs::read_to_string(&config_h) {
if content.contains("#define ZTS 1") || content.contains("#define ZTS") {
return true;
}
}
}
}
false
}
fn php_config_flag(php_config: &str, flag: &str) -> String {
let forbidden = [' ', '\t', ';', '|', '&', '`', '$', '(', ')', '{', '}', '<', '>', '\n', '\r', '\'', '"'];
if php_config.contains(forbidden.as_slice()) {
panic!(
"BEXT_PHP_CONFIG contains shell metacharacters: {:?}. \
It must be a path to the php-config binary (e.g. /usr/bin/php-config).",
php_config
);
}
let path = std::path::Path::new(php_config);
if path.is_absolute() && !path.is_file() {
panic!(
"BEXT_PHP_CONFIG points to {:?} which is not a file. \
Set it to the path of the php-config binary.",
php_config
);
}
match Command::new(php_config).arg(flag).output() {
Ok(output) if output.status.success() => {
String::from_utf8_lossy(&output.stdout).trim().to_string()
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
panic!(
"php-config {} failed (exit {}): {}",
flag, output.status, stderr
);
}
Err(e) => {
panic!(
"Failed to run `{} {}`: {}. \
Is PHP (embed SAPI) installed? \
Set BEXT_PHP_CONFIG to override the php-config path.",
php_config, flag, e
);
}
}
}