ext-php-rs/build.rs
David afdac8e4b3
Add support for field and method properties (#69)
* Rough implementation of struct properties

* Store properties hashtable once

* Tidy up handler functions with exceptions

* Add stub `get_properties` function for codegen structs

* Remove nightly features

* Revert storing properties

Technically, the zend object _could_ move in memory, leaving dangling
references in the properties hashtable. We will just build the hashtable
when required.

* Added `#[prop]` attribute

* Add pointer type to zval, tidy up zend string

* Add support for method properties

* Add `#[getter]` and `#[setter]` attributes

* Update documentation with method properties

* Tidy up macros code

* Remove string gc checks (done on PHP side)

* Move `RegisteredClass` implementation to module, update docs

* Fix read property `rv` segfault

* Fixed doctests
2021-09-28 00:54:23 +13:00

327 lines
9.5 KiB
Rust

use std::{
env,
path::{Path, PathBuf},
process::Command,
str,
};
use regex::Regex;
const MIN_PHP_API_VER: u32 = 20200930;
const MAX_PHP_API_VER: u32 = 20200930;
fn main() {
// rerun if wrapper header is changed
println!("cargo:rerun-if-changed=src/wrapper/wrapper.h");
println!("cargo:rerun-if-changed=src/wrapper/wrapper.c");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
// check for docs.rs and use stub bindings if required
if env::var("DOCS_RS").is_ok() {
println!("cargo:warning=docs.rs detected - using stub bindings");
println!("cargo:rustc-cfg=php_debug");
std::fs::copy("docsrs_bindings.rs", out_path)
.expect("Unable to copy docs.rs stub bindings to output directory.");
return;
}
// use php-config to fetch includes
let includes_cmd = Command::new("php-config")
.arg("--includes")
.output()
.expect("Unable to run `php-config`. Please ensure it is visible in your PATH.");
if !includes_cmd.status.success() {
let stderr = String::from_utf8(includes_cmd.stderr)
.unwrap_or_else(|_| String::from("Unable to read stderr"));
panic!("Error running `php-config`: {}", stderr);
}
// Ensure the PHP API version is supported.
// We could easily use grep and sed here but eventually we want to support Windows,
// so it's easier to just use regex.
let php_i_cmd = Command::new("php")
.arg("-i")
.output()
.expect("Unable to run `php -i`. Please ensure it is visible in your PATH.");
if !php_i_cmd.status.success() {
let stderr = str::from_utf8(&includes_cmd.stderr).unwrap_or("Unable to read stderr");
panic!("Error running `php -i`: {}", stderr);
}
let api_ver = Regex::new(r"PHP API => ([0-9]+)")
.unwrap()
.captures_iter(
str::from_utf8(&php_i_cmd.stdout).expect("Unable to parse `php -i` stdout as UTF-8"),
)
.next()
.and_then(|ver| ver.get(1))
.and_then(|ver| ver.as_str().parse::<u32>().ok())
.expect("Unable to retrieve PHP API version from `php -i`.");
if api_ver < MIN_PHP_API_VER || api_ver > MAX_PHP_API_VER {
panic!("The current version of PHP is not supported. Current PHP API version: {}, requires a version between {} and {}", api_ver, MIN_PHP_API_VER, MAX_PHP_API_VER);
}
let includes =
String::from_utf8(includes_cmd.stdout).expect("unable to parse `php-config` stdout");
// Build `wrapper.c` and link to Rust.
cc::Build::new()
.file("src/wrapper/wrapper.c")
.includes(
str::replace(includes.as_ref(), "-I", "")
.split(' ')
.map(|path| Path::new(path)),
)
.compile("wrapper");
let mut bindgen = bindgen::Builder::default()
.header("src/wrapper/wrapper.h")
.clang_args(includes.split(' '))
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.rustfmt_bindings(true)
.no_copy("_zend_value")
.no_copy("_zend_string")
.layout_tests(env::var("EXT_PHP_RS_TEST").is_ok());
for binding in ALLOWED_BINDINGS.iter() {
bindgen = bindgen
.allowlist_function(binding)
.allowlist_type(binding)
.allowlist_var(binding);
}
bindgen
.generate()
.expect("Unable to generate bindings for PHP")
.write_to_file(out_path)
.expect("Unable to write bindings file.");
let configure = Configure::get();
if configure.has_zts() {
println!("cargo:rustc-cfg=php_zts");
}
if configure.debug() {
println!("cargo:rustc-cfg=php_debug");
}
}
struct Configure(String);
impl Configure {
pub fn get() -> Self {
let cmd = Command::new("php-config")
.arg("--configure-options")
.output()
.expect("Unable to run `php-config --configure-options`. Please ensure it is visible in your PATH.");
if !cmd.status.success() {
let stderr = String::from_utf8(cmd.stderr)
.unwrap_or_else(|_| String::from("Unable to read stderr"));
panic!("Error running `php -i`: {}", stderr);
}
// check for the ZTS feature flag in configure
let stdout =
String::from_utf8(cmd.stdout).expect("Unable to read stdout from `php-config`.");
Self(stdout)
}
pub fn has_zts(&self) -> bool {
self.0.contains("--enable-zts")
}
pub fn debug(&self) -> bool {
self.0.contains("--enable-debug")
}
}
/// Array of functions/types used in `ext-php-rs` - used to allowlist when generating
/// bindings, as we don't want to generate bindings for everything (i.e. stdlib headers).
const ALLOWED_BINDINGS: &[&str] = &[
"HashTable",
"_Bucket",
"_call_user_function_impl",
"_efree",
"_emalloc",
"_zend_executor_globals",
"_zend_expected_type",
"_zend_expected_type_Z_EXPECTED_ARRAY",
"_zend_expected_type_Z_EXPECTED_BOOL",
"_zend_expected_type_Z_EXPECTED_DOUBLE",
"_zend_expected_type_Z_EXPECTED_LONG",
"_zend_expected_type_Z_EXPECTED_OBJECT",
"_zend_expected_type_Z_EXPECTED_RESOURCE",
"_zend_expected_type_Z_EXPECTED_STRING",
"_zend_new_array",
"_zval_struct__bindgen_ty_1",
"_zval_struct__bindgen_ty_2",
"ext_php_rs_executor_globals",
"ext_php_rs_php_build_id",
"ext_php_rs_zend_object_alloc",
"ext_php_rs_zend_object_release",
"ext_php_rs_zend_string_init",
"ext_php_rs_zend_string_release",
"object_properties_init",
"php_info_print_table_end",
"php_info_print_table_header",
"php_info_print_table_row",
"php_info_print_table_start",
"std_object_handlers",
"zend_array_destroy",
"zend_array_dup",
"zend_ce_argument_count_error",
"zend_ce_arithmetic_error",
"zend_ce_compile_error",
"zend_ce_division_by_zero_error",
"zend_ce_error_exception",
"zend_ce_exception",
"zend_ce_parse_error",
"zend_ce_throwable",
"zend_ce_type_error",
"zend_ce_unhandled_match_error",
"zend_ce_value_error",
"zend_class_entry",
"zend_declare_class_constant",
"zend_declare_property",
"zend_do_implement_interface",
"zend_execute_data",
"zend_function_entry",
"zend_hash_clean",
"zend_hash_index_del",
"zend_hash_index_find",
"zend_hash_index_update",
"zend_hash_next_index_insert",
"zend_hash_str_del",
"zend_hash_str_find",
"zend_hash_str_update",
"zend_internal_arg_info",
"zend_is_callable",
"zend_long",
"zend_lookup_class_ex",
"zend_module_entry",
"zend_object",
"zend_object_handlers",
"zend_object_std_init",
"zend_objects_clone_members",
"zend_register_bool_constant",
"zend_register_double_constant",
"zend_register_internal_class_ex",
"zend_register_long_constant",
"zend_register_string_constant",
"zend_resource",
"zend_string",
"zend_string_init_interned",
"zend_throw_exception_ex",
"zend_type",
"zend_value",
"zend_wrong_parameters_count_error",
"zval",
"CONST_CS",
"CONST_DEPRECATED",
"CONST_NO_FILE_CACHE",
"CONST_PERSISTENT",
"HT_MIN_SIZE",
"IS_ARRAY",
"IS_ARRAY_EX",
"IS_CALLABLE",
"IS_CONSTANT_AST",
"IS_CONSTANT_AST_EX",
"IS_DOUBLE",
"IS_FALSE",
"IS_INTERNED_STRING_EX",
"IS_LONG",
"IS_MIXED",
"IS_NULL",
"IS_OBJECT",
"IS_OBJECT_EX",
"IS_REFERENCE",
"IS_REFERENCE_EX",
"IS_RESOURCE",
"IS_RESOURCE_EX",
"IS_STRING",
"IS_STRING_EX",
"IS_TRUE",
"IS_TYPE_COLLECTABLE",
"IS_TYPE_REFCOUNTED",
"IS_UNDEF",
"IS_VOID",
"IS_PTR",
"MAY_BE_ANY",
"MAY_BE_BOOL",
"USING_ZTS",
"ZEND_ACC_ABSTRACT",
"ZEND_ACC_ANON_CLASS",
"ZEND_ACC_CALL_VIA_TRAMPOLINE",
"ZEND_ACC_CHANGED",
"ZEND_ACC_CLOSURE",
"ZEND_ACC_CONSTANTS_UPDATED",
"ZEND_ACC_CTOR",
"ZEND_ACC_DEPRECATED",
"ZEND_ACC_DONE_PASS_TWO",
"ZEND_ACC_EARLY_BINDING",
"ZEND_ACC_FAKE_CLOSURE",
"ZEND_ACC_FINAL",
"ZEND_ACC_GENERATOR",
"ZEND_ACC_HAS_FINALLY_BLOCK",
"ZEND_ACC_HAS_RETURN_TYPE",
"ZEND_ACC_HAS_TYPE_HINTS",
"ZEND_ACC_HAS_UNLINKED_USES",
"ZEND_ACC_HEAP_RT_CACHE",
"ZEND_ACC_IMMUTABLE",
"ZEND_ACC_IMPLICIT_ABSTRACT_CLASS",
"ZEND_ACC_INTERFACE",
"ZEND_ACC_LINKED",
"ZEND_ACC_NEARLY_LINKED",
"ZEND_ACC_NEVER_CACHE",
"ZEND_ACC_NO_DYNAMIC_PROPERTIES",
"ZEND_ACC_PRELOADED",
"ZEND_ACC_PRIVATE",
"ZEND_ACC_PROMOTED",
"ZEND_ACC_PROPERTY_TYPES_RESOLVED",
"ZEND_ACC_PROTECTED",
"ZEND_ACC_PUBLIC",
"ZEND_ACC_RESOLVED_INTERFACES",
"ZEND_ACC_RESOLVED_PARENT",
"ZEND_ACC_RETURN_REFERENCE",
"ZEND_ACC_REUSE_GET_ITERATOR",
"ZEND_ACC_STATIC",
"ZEND_ACC_STRICT_TYPES",
"ZEND_ACC_TOP_LEVEL",
"ZEND_ACC_TRAIT",
"ZEND_ACC_TRAIT_CLONE",
"ZEND_ACC_UNRESOLVED_VARIANCE",
"ZEND_ACC_USES_THIS",
"ZEND_ACC_USE_GUARDS",
"ZEND_ACC_VARIADIC",
"ZEND_DEBUG",
"ZEND_HAS_STATIC_IN_METHODS",
"ZEND_ISEMPTY",
"ZEND_MM_ALIGNMENT",
"ZEND_MM_ALIGNMENT_MASK",
"ZEND_MODULE_API_NO",
"ZEND_PROPERTY_EXISTS",
"ZEND_PROPERTY_ISSET",
"Z_TYPE_FLAGS_SHIFT",
"_IS_BOOL",
"_ZEND_IS_VARIADIC_BIT",
"_ZEND_SEND_MODE_SHIFT",
"_ZEND_TYPE_NULLABLE_BIT",
"ts_rsrc_id",
"_ZEND_TYPE_NAME_BIT",
"zval_ptr_dtor",
"zend_refcounted_h",
"zend_is_true",
"zend_object_std_dtor",
"zend_std_read_property",
"zend_std_write_property",
"zend_std_get_properties",
"zend_std_has_property",
];