extern crate bindgen;
extern crate cc;
extern crate num_cpus;
use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks};
use bindgen::Builder;
use std::collections::HashSet;
use std::env;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::sync::{Arc, RwLock};
const PHP_VERSION: &'static str = concat!("php-", env!("CARGO_PKG_VERSION"));
macro_rules! println_stderr(
($($arg:tt)*) => { {
let r = writeln!(&mut ::std::io::stderr(), $($arg)*);
r.expect("failed printing to stderr");
} }
);
fn run_command_or_fail(dir: String, cmd: &str, args: &[&str]) {
println_stderr!(
"Running command: \"{} {}\" in dir: {}",
cmd,
args.join(" "),
dir
);
let ret = Command::new(cmd).current_dir(dir).args(args).status();
match ret.map(|status| (status.success(), status.code())) {
Ok((true, _)) => return,
Ok((false, Some(c))) => panic!("Command failed with error code {}", c),
Ok((false, None)) => panic!("Command got killed"),
Err(e) => panic!("Command failed with error: {}", e),
}
}
fn target(path: &str) -> String {
let osdir = env::var("PWD").unwrap();
let pfx = match env::var("CARGO_TARGET_DIR") {
Ok(d) => d,
Err(_) => String::from("target"),
};
let profile = env::var("PROFILE").unwrap();
format!("{}/{}/{}/native/{}", osdir, pfx, profile, path)
}
fn exists(path: &str) -> bool {
Path::new(target(path).as_str()).exists()
}
#[derive(Debug)]
struct MacroCallback {
macros: Arc<RwLock<HashSet<String>>>,
}
impl ParseCallbacks for MacroCallback {
fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior {
self.macros.write().unwrap().insert(name.into());
match name {
"FP_NAN" | "FP_INFINITE" | "FP_ZERO" | "FP_SUBNORMAL" | "FP_NORMAL" => {
MacroParsingBehavior::Ignore
}
_ => MacroParsingBehavior::Default,
}
}
}
fn main() {
let cpus = format!("{}", num_cpus::get());
#[cfg(all(target_os = "linux"))]
let default_link_static = false;
#[cfg(all(target_os = "macos"))]
let default_link_static = true;
let php_version = option_env!("PHP_VERSION").unwrap_or(PHP_VERSION);
let macros = Arc::new(RwLock::new(HashSet::new()));
println!("cargo:rerun-if-env-changed=PHP_VERSION");
println!("cargo:rerun-if-env-changed=PHP_LINK_STATIC");
let link_dynamic = env::var_os("PHP_LINK_DYNAMIC")
.map(|_| true)
.unwrap_or(false);
let link_static = env::var_os("PHP_LINK_STATIC")
.map(|_| true)
.unwrap_or(default_link_static && !link_dynamic);
if !exists("php-src/LICENSE") {
println_stderr!("Setting up PHP {}", php_version);
run_command_or_fail(
target(""),
"git",
&[
"clone",
"https://github.com/php/php-src",
format!("--branch={}", php_version).as_str(),
],
);
run_command_or_fail(
target("php-src"),
"sed",
&[
"-e",
"s/void zend_signal_startup/ZEND_API void zend_signal_startup/g",
"-ibk",
"Zend/zend_signal.c",
"Zend/zend_signal.h",
],
);
run_command_or_fail(target("php-src"), "./genfiles", &[]);
run_command_or_fail(target("php-src"), "./buildconf", &["--force"]);
let embed_type = if link_static { "static" } else { "shared" };
#[cfg(all(target_os = "linux"))]
let config = &[
"--enable-debug",
&format!("--enable-embed={}", embed_type),
"--disable-cli",
"--disable-cgi",
"--enable-maintainer-zts",
"--disable-libxml",
"--disable-dom",
"--disable-xml",
"--disable-simplexml",
"--disable-xmlwriter",
"--disable-xmlreader",
];
#[cfg(all(target_os = "macos"))]
let config = &[
"--enable-debug",
&format!("--enable-embed={}", embed_type),
"--disable-cli",
"--disable-cgi",
"--enable-maintainer-zts",
"--without-iconv",
"--disable-libxml",
"--disable-dom",
"--disable-xml",
"--disable-simplexml",
"--disable-xmlwriter",
"--disable-xmlreader",
];
run_command_or_fail(target("php-src"), "./configure", config);
run_command_or_fail(target("php-src"), "make", &["-j", cpus.as_str()]);
}
let include_dir = target("php-src");
let lib_dir = target("php-src/libs");
let link_type = if link_static { "=static" } else { "" };
println!("cargo:rustc-link-lib{}=php7", link_type);
println!("cargo:rustc-link-search=native={}", lib_dir);
let includes = ["/", "/TSRM", "/Zend", "/main"]
.iter()
.map(|d| format!("-I{}{}", include_dir, d))
.collect::<Vec<String>>();
let bindings = Builder::default()
.rustfmt_bindings(true)
.clang_args(includes)
.whitelist_function("_zend_file_handle__bindgen_ty_1")
.whitelist_function("php_execute_script")
.whitelist_function("php_module_startup")
.whitelist_function("php_request_shutdown")
.whitelist_function("php_request_startup")
.whitelist_function("phprpm_fopen")
.whitelist_function("sapi_send_headers")
.whitelist_function("sapi_startup")
.whitelist_function("sg_request_info")
.whitelist_function("sg_sapi_headers")
.whitelist_function("sg_server_context")
.whitelist_function("sg_server_context")
.whitelist_function("sg_set_server_context")
.whitelist_function("sg_set_server_context")
.whitelist_function("ts_resource_ex")
.whitelist_function("tsrm_startup")
.whitelist_function("zend_error")
.whitelist_function("zend_signal_startup")
.whitelist_function("zend_tsrmls_cache_update")
.whitelist_var("SAPI_HEADER_SENT_SUCCESSFULLY")
.whitelist_type("sapi_headers_struc")
.whitelist_type("sapi_module_struc")
.whitelist_type("sapi_request_info")
.whitelist_type("ZEND_RESULT_CODE")
.whitelist_type("zval")
.whitelist_var("zend_stream_type_ZEND_HANDLE_FP")
.parse_callbacks(Box::new(MacroCallback {
macros: macros.clone(),
})).derive_default(true)
.header("wrapper.h")
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
cc::Build::new()
.file("src/shim.c")
.include(&include_dir)
.flag("-fPIC")
.flag("-m64")
.include(&format!("{}/TSRM", include_dir))
.include(&format!("{}/Zend", include_dir))
.include(&format!("{}/main", include_dir))
.compile("foo");
}