Skip to main content

cargo_php_sys_build/
phpgen.rs

1use std::collections::HashSet;
2use std::path::{Path, PathBuf};
3
4#[derive(Debug)]
5struct IgnoreMacros(HashSet<String>);
6
7impl bindgen::callbacks::ParseCallbacks for IgnoreMacros {
8    fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior {
9        if self.0.contains(name) {
10            bindgen::callbacks::MacroParsingBehavior::Ignore
11        } else {
12            bindgen::callbacks::MacroParsingBehavior::Default
13        }
14    }
15}
16
17fn build_ignored_macros() -> IgnoreMacros {
18    IgnoreMacros(
19        vec![
20            "FP_INFINITE".into(),
21            "FP_NAN".into(),
22            "FP_NORMAL".into(),
23            "FP_SUBNORMAL".into(),
24            "FP_ZERO".into(),
25            "FP_INT_UPWARD".into(),
26            "FP_INT_DOWNWARD".into(),
27            "FP_INT_TOWARDZERO".into(),
28            "FP_INT_TONEARESTFROMZERO".into(),
29            "FP_INT_TONEAREST".into(),
30            "IPPORT_RESERVED".into(),
31        ]
32        .into_iter()
33        .collect(),
34    )
35}
36
37fn run_checks<T>(includes: T, check_path: &str)
38where
39    T: IntoIterator,
40    T::Item: AsRef<Path>,
41{
42    let check = cc::Build::new()
43        .file(check_path)
44        .includes(includes)
45        .expand();
46    let check = String::from_utf8(check).unwrap();
47
48    if check.contains("CHECK_DEBUG_ZEND_TRUE") {
49        println!("rustc-cfg=debug_zend");
50    }
51
52    if check.contains("CHECK_USING_ZTS_TRUE") {
53        println!("rustc-cfg=using_zts");
54    }
55}
56
57pub fn build_php(
58    bindings_rs: &PathBuf,
59    root_path: &PathBuf,
60    include_paths: &[&str],
61    wrapper_h: &PathBuf,
62    check_c_path: Option<&PathBuf>,
63) {
64    println!("cargo:rerun-if-changed={}", wrapper_h.to_str().unwrap());
65    let include_paths = include_paths
66        .iter()
67        .map(|v| {
68            root_path
69                .join(v)
70                .canonicalize()
71                .unwrap_or_else(|_| panic!("Include path not found : {:?}", root_path.join(v)))
72        })
73        .map(|v| String::from(v.to_str().unwrap()));
74
75    if let Some(p) = check_c_path {
76        println!("cargo:rerun-if-changed={}", p.to_str().unwrap());
77        run_checks(include_paths.clone(), p.to_str().unwrap());
78    }
79
80    let clang_args = include_paths.map(|v| format!("-I{}", v));
81
82    let bindings = bindgen::Builder::default()
83        .header(wrapper_h.to_str().unwrap())
84        .parse_callbacks(Box::new(build_ignored_macros()))
85        // Emit struct-layout checks. Modern bindgen emits these as compile-time
86        // `const _` assertions (size_of/align_of/offset_of!), so layout is
87        // verified on every `cargo check`/`build` - unlike the 0.57 output, whose
88        // runtime layout tests dereferenced a null pointer and abort on current Rust.
89        .layout_tests(true)
90        // NOTE: bindgen >=0.60 anchors allowlist patterns and rejects a leading
91        // `(?i)` inline flag, so the original case-insensitive patterns matched
92        // nothing. PHP/Zend C symbols are only ever lower- or UPPER-snake-case
93        // (lowercase functions/types, UPPERCASE macro constants), so each rule is
94        // listed in both cases instead of relying on `(?i)`.
95        .allowlist_function(r"php_.*")
96        .allowlist_function(r"[_]?zend.*")
97        .allowlist_function(r".*zend")
98        .allowlist_type(r"zend.*")
99        .allowlist_type(r"ZEND.*")
100        .allowlist_var(r"[_]?zend.*")
101        .allowlist_var(r"[_]?ZEND.*")
102        .allowlist_var(r"php.*")
103        .allowlist_var(r"PHP.*")
104        .allowlist_var(r"[_]?is_.*")
105        .allowlist_var(r"[_]?IS_.*")
106        .allowlist_var(r"configure_.*")
107        .allowlist_var(r"CONFIGURE_.*")
108        .allowlist_var(r"gc_.*")
109        .allowlist_var(r"GC_.*")
110        .allowlist_var(r"using.*")
111        .allowlist_var(r"USING.*")
112        .allowlist_var(r"z_.*")
113        .allowlist_var(r"Z_.*")
114        .allowlist_var(r"zmsg")
115        .allowlist_var(r"ZMSG")
116        .allowlist_var(r"module.*")
117        .allowlist_var(r"MODULE.*")
118        .allowlist_var(r"hash_.*")
119        .allowlist_var(r"HASH_.*")
120        .allowlist_var(r"debug.*")
121        .allowlist_var(r"DEBUG.*")
122        .allowlist_var(r".*_globals")
123        .allowlist_var(r".*_GLOBALS")
124        .allowlist_var(r"module_registry")
125        .allowlist_var(r"MODULE_REGISTRY")
126        .allowlist_var(r"empty_fcall.*")
127        .allowlist_var(r"EMPTY_FCALL.*")
128        .allowlist_var(r"std_object_handlers")
129        .allowlist_var(r"STD_OBJECT_HANDLERS")
130        .allowlist_var(r".*zval.*")
131        .allowlist_var(r".*ZVAL.*")
132        .raw_line("#![allow(clippy::all)]") // trusting bindgen - lets disable clippy reported things, as we can't do anything
133        .raw_line("#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case, clashing_extern_declarations)]\n")
134        .clang_args(clang_args)
135        .generate()
136        .expect("Unable to generate bindings");
137
138    bindings
139        .write_to_file(bindings_rs)
140        .expect("Couldn't write bindings!");
141}