2022-03-18 04:36:51 +01:00
#[ cfg_attr(windows, path = " windows_build.rs " ) ]
#[ cfg_attr(not(windows), path = " unix_build.rs " ) ]
mod impl_ ;
2021-04-02 03:18:45 +02:00
use std ::{
env ,
2022-03-18 04:36:51 +01:00
fs ::File ,
io ::{ BufWriter , Write } ,
2022-11-28 14:57:34 +01:00
path ::{ Path , PathBuf } ,
process ::Command ,
str ::FromStr ,
2021-04-02 03:18:45 +02:00
} ;
2021-03-09 00:40:12 +01:00
2022-03-18 04:36:51 +01:00
use anyhow ::{ anyhow , bail , Context , Result } ;
use bindgen ::RustTarget ;
use impl_ ::Provider ;
2021-03-09 00:40:12 +01:00
2021-03-11 00:34:47 +01:00
const MIN_PHP_API_VER : u32 = 20200930 ;
2023-11-21 20:13:57 +01:00
const MAX_PHP_API_VER : u32 = 20230831 ;
2021-03-11 00:34:47 +01:00
2022-03-18 04:36:51 +01:00
pub trait PHPProvider < ' a > : Sized {
/// Create a new PHP provider.
2022-11-28 14:57:34 +01:00
fn new ( info : & ' a PHPInfo ) -> Result < Self > ;
2021-03-09 00:40:12 +01:00
2022-03-18 04:36:51 +01:00
/// Retrieve a list of absolute include paths.
fn get_includes ( & self ) -> Result < Vec < PathBuf > > ;
2021-09-05 08:18:09 +02:00
2022-03-18 04:36:51 +01:00
/// Retrieve a list of macro definitions to pass to the compiler.
fn get_defines ( & self ) -> Result < Vec < ( & 'static str , & 'static str ) > > ;
2021-09-05 08:18:09 +02:00
2022-03-18 04:36:51 +01:00
/// Writes the bindings to a file.
fn write_bindings ( & self , bindings : String , writer : & mut impl Write ) -> Result < ( ) > {
for line in bindings . lines ( ) {
2023-02-07 22:52:56 +01:00
writeln! ( writer , " {line} " ) ? ;
2022-03-18 04:36:51 +01:00
}
Ok ( ( ) )
2021-09-05 08:18:09 +02:00
}
2022-03-18 04:36:51 +01:00
/// Prints any extra link arguments.
fn print_extra_link_args ( & self ) -> Result < ( ) > {
Ok ( ( ) )
}
}
2021-03-09 00:40:12 +01:00
2022-11-28 14:57:34 +01:00
/// Finds the location of an executable `name`.
2022-12-11 20:08:50 +01:00
pub fn find_executable ( name : & str ) -> Option < PathBuf > {
2022-11-28 14:57:34 +01:00
const WHICH : & str = if cfg! ( windows ) { " where " } else { " which " } ;
let cmd = Command ::new ( WHICH ) . arg ( name ) . output ( ) . ok ( ) ? ;
if cmd . status . success ( ) {
let stdout = String ::from_utf8_lossy ( & cmd . stdout ) ;
Some ( stdout . trim ( ) . into ( ) )
} else {
None
}
}
2022-12-11 20:08:50 +01:00
/// Returns an environment variable's value as a PathBuf
pub fn path_from_env ( key : & str ) -> Option < PathBuf > {
std ::env ::var_os ( key ) . map ( PathBuf ::from )
}
2022-03-18 04:36:51 +01:00
/// Finds the location of the PHP executable.
2022-11-28 14:57:34 +01:00
fn find_php ( ) -> Result < PathBuf > {
2022-12-11 20:08:50 +01:00
// If path is given via env, it takes priority.
if let Some ( path ) = path_from_env ( " PHP " ) {
if ! path . try_exists ( ) ? {
// If path was explicitly given and it can't be found, this is a hard error
bail! ( " php executable not found at {:?} " , path ) ;
}
return Ok ( path ) ;
2022-11-28 14:57:34 +01:00
}
2022-12-11 20:08:50 +01:00
find_executable ( " php " ) . with_context ( | | {
" Could not find PHP executable. \
Please ensure ` php ` is in your PATH or the ` PHP ` environment variable is set . "
} )
2022-11-28 14:57:34 +01:00
}
2022-03-18 04:36:51 +01:00
2022-11-28 14:57:34 +01:00
pub struct PHPInfo ( String ) ;
2021-11-21 08:00:51 +01:00
2022-11-28 14:57:34 +01:00
impl PHPInfo {
pub fn get ( php : & Path ) -> Result < Self > {
let cmd = Command ::new ( php )
. arg ( " -i " )
. output ( )
. context ( " Failed to call `php -i` " ) ? ;
if ! cmd . status . success ( ) {
bail! ( " Failed to call `php -i` status code {} " , cmd . status ) ;
}
let stdout = String ::from_utf8_lossy ( & cmd . stdout ) ;
Ok ( Self ( stdout . to_string ( ) ) )
}
// Only present on Windows.
#[ cfg(windows) ]
pub fn architecture ( & self ) -> Result < impl_ ::Arch > {
use std ::convert ::TryInto ;
self . get_key ( " Architecture " )
. context ( " Could not find architecture of PHP " ) ?
. try_into ( )
}
pub fn thread_safety ( & self ) -> Result < bool > {
Ok ( self
. get_key ( " Thread Safety " )
. context ( " Could not find thread safety of PHP " ) ?
= = " enabled " )
}
pub fn debug ( & self ) -> Result < bool > {
Ok ( self
. get_key ( " Debug Build " )
. context ( " Could not find debug build of PHP " ) ?
= = " yes " )
}
2022-11-26 22:09:59 +01:00
2022-11-28 14:57:34 +01:00
pub fn version ( & self ) -> Result < & str > {
self . get_key ( " PHP Version " )
. context ( " Failed to get PHP version " )
}
pub fn zend_version ( & self ) -> Result < u32 > {
self . get_key ( " PHP API " )
. context ( " Failed to get Zend version " )
. and_then ( | s | u32 ::from_str ( s ) . context ( " Failed to convert Zend version to integer " ) )
}
fn get_key ( & self , key : & str ) -> Option < & str > {
2023-02-07 22:52:56 +01:00
let split = format! ( " {key} => " ) ;
2022-11-28 14:57:34 +01:00
for line in self . 0. lines ( ) {
let components : Vec < _ > = line . split ( & split ) . collect ( ) ;
if components . len ( ) > 1 {
return Some ( components [ 1 ] ) ;
}
}
None
}
2022-03-18 04:36:51 +01:00
}
2021-03-09 00:40:12 +01:00
2022-03-18 04:36:51 +01:00
/// Builds the wrapper library.
fn build_wrapper ( defines : & [ ( & str , & str ) ] , includes : & [ PathBuf ] ) -> Result < ( ) > {
let mut build = cc ::Build ::new ( ) ;
for ( var , val ) in defines {
2022-08-13 18:29:15 +02:00
build . define ( var , * val ) ;
2022-03-18 04:36:51 +01:00
}
build
2021-10-10 06:51:55 +02:00
. file ( " src/wrapper.c " )
2022-03-18 04:36:51 +01:00
. includes ( includes )
. try_compile ( " wrapper " )
. context ( " Failed to compile ext-php-rs C interface " ) ? ;
Ok ( ( ) )
}
2021-04-02 03:18:45 +02:00
2023-10-20 14:08:10 +02:00
#[ cfg(feature = " embed " ) ]
/// Builds the embed library.
fn build_embed ( defines : & [ ( & str , & str ) ] , includes : & [ PathBuf ] ) -> Result < ( ) > {
let mut build = cc ::Build ::new ( ) ;
for ( var , val ) in defines {
build . define ( var , * val ) ;
}
build
. file ( " src/embed/embed.c " )
. includes ( includes )
. try_compile ( " embed " )
. context ( " Failed to compile ext-php-rs C embed interface " ) ? ;
Ok ( ( ) )
}
2022-03-18 04:36:51 +01:00
/// Generates bindings to the Zend API.
fn generate_bindings ( defines : & [ ( & str , & str ) ] , includes : & [ PathBuf ] ) -> Result < String > {
2023-10-20 14:08:10 +02:00
let mut bindgen = bindgen ::Builder ::default ( ) ;
#[ cfg(feature = " embed " ) ]
{
bindgen = bindgen . header ( " src/embed/embed.h " ) ;
}
bindgen = bindgen
2021-10-10 06:51:55 +02:00
. header ( " src/wrapper.h " )
2022-03-18 04:36:51 +01:00
. clang_args (
includes
. iter ( )
. map ( | inc | format! ( " -I {} " , inc . to_string_lossy ( ) ) ) ,
)
2023-02-07 22:52:56 +01:00
. clang_args ( defines . iter ( ) . map ( | ( var , val ) | format! ( " -D {var} = {val} " ) ) )
2023-05-16 22:49:57 +02:00
. formatter ( bindgen ::Formatter ::Rustfmt )
2021-12-13 10:43:34 +01:00
. no_copy ( " _zval_struct " )
2021-09-27 13:33:16 +02:00
. no_copy ( " _zend_string " )
2021-10-05 05:59:41 +02:00
. no_copy ( " _zend_array " )
2022-03-18 04:36:51 +01:00
. no_debug ( " _zend_function_entry " ) // On Windows when the handler uses vectorcall, Debug cannot be derived so we do it in code.
. layout_tests ( env ::var ( " EXT_PHP_RS_TEST " ) . is_ok ( ) )
. rust_target ( RustTarget ::Nightly ) ;
2021-09-05 07:56:29 +02:00
for binding in ALLOWED_BINDINGS . iter ( ) {
bindgen = bindgen
. allowlist_function ( binding )
. allowlist_type ( binding )
. allowlist_var ( binding ) ;
}
2022-03-18 04:36:51 +01:00
let bindings = bindgen
2021-03-09 00:40:12 +01:00
. generate ( )
2022-03-18 04:36:51 +01:00
. map_err ( | _ | anyhow! ( " Unable to generate bindings for PHP " ) ) ?
. to_string ( ) ;
2021-05-19 10:45:39 +02:00
2022-03-18 04:36:51 +01:00
Ok ( bindings )
}
2021-09-04 14:53:29 +02:00
2022-11-28 14:57:34 +01:00
/// Checks the PHP Zend API version for compatibility with ext-php-rs, setting
/// any configuration flags required.
fn check_php_version ( info : & PHPInfo ) -> Result < ( ) > {
let version = info . zend_version ( ) ? ;
if ! ( MIN_PHP_API_VER ..= MAX_PHP_API_VER ) . contains ( & version ) {
bail! ( " The current version of PHP is not supported. Current PHP API version: {}, requires a version between {} and {} " , version , MIN_PHP_API_VER , MAX_PHP_API_VER ) ;
}
// Infra cfg flags - use these for things that change in the Zend API that don't
// rely on a feature and the crate user won't care about (e.g. struct field
// changes). Use a feature flag for an actual feature (e.g. enums being
// introduced in PHP 8.1).
//
// PHP 8.0 is the baseline - no feature flags will be introduced here.
//
// The PHP version cfg flags should also stack - if you compile on PHP 8.2 you
// should get both the `php81` and `php82` flags.
const PHP_81_API_VER : u32 = 20210902 ;
2022-12-20 17:11:35 +01:00
const PHP_82_API_VER : u32 = 20220829 ;
2023-11-21 20:26:49 +01:00
const PHP_83_API_VER : u32 = 20230831 ;
2022-12-20 17:11:35 +01:00
println! ( " cargo:rustc-cfg=php80 " ) ;
if ( PHP_81_API_VER .. PHP_82_API_VER ) . contains ( & version ) {
2022-11-28 14:57:34 +01:00
println! ( " cargo:rustc-cfg=php81 " ) ;
}
2022-12-20 17:11:35 +01:00
if version > = PHP_82_API_VER {
println! ( " cargo:rustc-cfg=php82 " ) ;
}
2023-11-21 20:26:49 +01:00
if version > = PHP_83_API_VER {
println! ( " cargo:rustc-cfg=php83 " ) ;
}
2022-11-28 14:57:34 +01:00
Ok ( ( ) )
}
2022-03-18 04:36:51 +01:00
fn main ( ) -> Result < ( ) > {
2022-10-16 01:49:02 +02:00
let out_dir = env ::var_os ( " OUT_DIR " ) . context ( " Failed to get OUT_DIR " ) ? ;
let out_path = PathBuf ::from ( out_dir ) . join ( " bindings.rs " ) ;
2022-03-18 04:36:51 +01:00
let manifest : PathBuf = std ::env ::var ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) . into ( ) ;
for path in [
manifest . join ( " src " ) . join ( " wrapper.h " ) ,
manifest . join ( " src " ) . join ( " wrapper.c " ) ,
2023-10-25 15:15:43 +02:00
manifest . join ( " src " ) . join ( " embed " ) . join ( " embed.h " ) ,
manifest . join ( " src " ) . join ( " embed " ) . join ( " embed.c " ) ,
2022-03-18 04:36:51 +01:00
manifest . join ( " allowed_bindings.rs " ) ,
manifest . join ( " windows_build.rs " ) ,
manifest . join ( " unix_build.rs " ) ,
] {
println! ( " cargo:rerun-if-changed= {} " , path . to_string_lossy ( ) ) ;
}
2022-12-11 20:08:50 +01:00
for env_var in [ " PHP " , " PHP_CONFIG " , " PATH " ] {
2023-02-07 22:52:56 +01:00
println! ( " cargo:rerun-if-env-changed= {env_var} " ) ;
2022-12-11 20:08:50 +01:00
}
2021-09-04 14:53:29 +02:00
2022-12-11 22:10:25 +01:00
println! ( " cargo:rerun-if-changed=build.rs " ) ;
2022-10-16 01:49:02 +02:00
// docs.rs runners only have PHP 7.4 - use pre-generated bindings
if env ::var ( " DOCS_RS " ) . is_ok ( ) {
println! ( " cargo:warning=docs.rs detected - using stub bindings " ) ;
println! ( " cargo:rustc-cfg=php_debug " ) ;
2022-12-20 17:11:35 +01:00
println! ( " cargo:rustc-cfg=php82 " ) ;
2022-10-16 01:49:02 +02:00
std ::fs ::copy ( " docsrs_bindings.rs " , out_path )
. expect ( " failed to copy docs.rs stub bindings to out directory " ) ;
return Ok ( ( ) ) ;
}
2022-11-28 14:57:34 +01:00
let php = find_php ( ) ? ;
let info = PHPInfo ::get ( & php ) ? ;
let provider = Provider ::new ( & info ) ? ;
2021-05-19 10:45:39 +02:00
2022-03-18 04:36:51 +01:00
let includes = provider . get_includes ( ) ? ;
let defines = provider . get_defines ( ) ? ;
2021-09-04 14:53:29 +02:00
2022-11-28 14:57:34 +01:00
check_php_version ( & info ) ? ;
2022-03-18 04:36:51 +01:00
build_wrapper ( & defines , & includes ) ? ;
2023-10-20 14:08:10 +02:00
#[ cfg(feature = " embed " ) ]
build_embed ( & defines , & includes ) ? ;
2022-03-18 04:36:51 +01:00
let bindings = generate_bindings ( & defines , & includes ) ? ;
2021-05-19 10:45:39 +02:00
2022-03-18 04:36:51 +01:00
let out_file =
File ::create ( & out_path ) . context ( " Failed to open output bindings file for writing " ) ? ;
let mut out_writer = BufWriter ::new ( out_file ) ;
provider . write_bindings ( bindings , & mut out_writer ) ? ;
2021-09-04 14:53:29 +02:00
2022-11-28 14:57:34 +01:00
if info . debug ( ) ? {
2022-03-18 04:36:51 +01:00
println! ( " cargo:rustc-cfg=php_debug " ) ;
2021-09-04 14:53:29 +02:00
}
2022-11-28 14:57:34 +01:00
if info . thread_safety ( ) ? {
2022-03-18 04:36:51 +01:00
println! ( " cargo:rustc-cfg=php_zts " ) ;
}
provider . print_extra_link_args ( ) ? ;
// Generate guide tests
let test_md = skeptic ::markdown_files_of_directory ( " guide " ) ;
#[ cfg(not(feature = " closure " )) ]
let test_md : Vec < _ > = test_md
. into_iter ( )
. filter ( | p | p . file_stem ( ) ! = Some ( std ::ffi ::OsStr ::new ( " closure " ) ) )
. collect ( ) ;
skeptic ::generate_doc_tests ( & test_md ) ;
Ok ( ( ) )
2021-03-09 00:40:12 +01:00
}
2021-09-05 07:56:29 +02:00
2021-11-20 02:19:02 +01:00
// Mock macro for the `allowed_bindings.rs` script.
macro_rules ! bind {
( $( $s : ident ) , * ) = > {
& [ $(
stringify! ( $s ) ,
) * ]
}
}
2021-10-10 06:51:55 +02:00
/// 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).
2021-11-20 02:19:02 +01:00
const ALLOWED_BINDINGS : & [ & str ] = include! ( " allowed_bindings.rs " ) ;