2024-12-10 23:06:47 +08:00
< ? php
declare ( strict_types = 1 );
namespace SPC\util ;
use SPC\builder\BuilderBase ;
use SPC\builder\BuilderProvider ;
2025-07-22 21:13:35 +08:00
use SPC\builder\macos\MacOSBuilder ;
2025-04-18 14:38:22 +08:00
use SPC\exception\FileSystemException ;
use SPC\exception\RuntimeException ;
use SPC\exception\WrongUsageException ;
2024-12-10 23:06:47 +08:00
use SPC\store\Config ;
use Symfony\Component\Console\Input\ArgvInput ;
class SPCConfigUtil
{
2025-03-26 12:38:53 +08:00
private ? BuilderBase $builder = null ;
2025-07-22 19:59:44 +08:00
private bool $no_php ;
private bool $libs_only_deps ;
private bool $absolute_libs ;
/**
* @ param array {
* no_php ? : bool ,
* libs_only_deps ? : bool ,
* absolute_libs ? : bool
* } $options Options pass to spc - config
*/
public function __construct ( ? BuilderBase $builder = null , array $options = [])
2024-12-10 23:06:47 +08:00
{
2025-03-26 12:38:53 +08:00
if ( $builder !== null ) {
$this -> builder = $builder ; // BuilderProvider::makeBuilderByInput($input ?? new ArgvInput());
2024-12-10 23:06:47 +08:00
}
2025-07-22 19:59:44 +08:00
$this -> no_php = $options [ 'no_php' ] ? ? false ;
$this -> libs_only_deps = $options [ 'libs_only_deps' ] ? ? false ;
$this -> absolute_libs = $options [ 'absolute_libs' ] ? ? false ;
2024-12-10 23:06:47 +08:00
}
2025-04-18 14:38:22 +08:00
/**
* Generate configuration for building PHP extensions .
*
* @ param array $extensions Extension name list
* @ param array $libraries Additional library name list
* @ param bool $include_suggest_ext Include suggested extensions
* @ param bool $include_suggest_lib Include suggested libraries
* @ return array {
* cflags : string ,
* ldflags : string ,
* libs : string
* }
* @ throws \ReflectionException
* @ throws FileSystemException
* @ throws RuntimeException
* @ throws WrongUsageException
* @ throws \Throwable
*/
2025-06-07 23:00:26 +07:00
public function config ( array $extensions = [], array $libraries = [], bool $include_suggest_ext = false , bool $include_suggest_lib = false , bool $with_dependencies = false ) : array
2024-12-10 23:06:47 +08:00
{
[ $extensions , $libraries ] = DependencyUtil :: getExtsAndLibs ( $extensions , $libraries , $include_suggest_ext , $include_suggest_lib );
ob_start ();
2025-03-26 12:38:53 +08:00
if ( $this -> builder === null ) {
$this -> builder = BuilderProvider :: makeBuilderByInput ( new ArgvInput ());
$this -> builder -> proveLibs ( $libraries );
2025-04-18 14:50:58 +08:00
$this -> builder -> proveExts ( $extensions , skip_extract : true );
2025-03-26 12:38:53 +08:00
}
2024-12-10 23:06:47 +08:00
ob_get_clean ();
$ldflags = $this -> getLdflagsString ();
2025-07-22 19:59:44 +08:00
$cflags = $this -> getIncludesString ( $libraries );
$libs = $this -> getLibsString ( $libraries , ! $this -> absolute_libs );
// additional OS-specific libraries (e.g. macOS -lresolv)
$extra_env = getenv ( 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS' );
if ( is_string ( $extra_env )) {
$libs .= ' ' . trim ( $extra_env , '"' );
}
$extra_env = getenv ( 'SPC_EXTRA_LIBS' );
2025-07-22 21:13:35 +08:00
if ( is_string ( $extra_env ) && ! empty ( $extra_env )) {
2025-07-22 19:59:44 +08:00
$libs .= " { $extra_env } " ;
}
// extension frameworks
2025-06-29 16:00:17 +08:00
if ( SPCTarget :: getTargetOS () === 'Darwin' ) {
2025-06-12 01:16:57 +08:00
$libs .= " { $this -> getFrameworksString ( $extensions ) } " ;
}
2025-07-22 22:46:13 +08:00
if ( $this -> builder -> hasCpp ()) {
$libs .= $this -> builder instanceof MacOSBuilder ? ' -lc++' : ' -lstdc++' ;
}
2025-07-22 19:59:44 +08:00
if ( $this -> libs_only_deps ) {
return [
'cflags' => trim ( getenv ( 'CFLAGS' ) . ' ' . $cflags ),
'ldflags' => trim ( getenv ( 'LDFLAGS' ) . ' ' . $ldflags ),
'libs' => trim ( getenv ( 'LIBS' ) . ' ' . $libs ),
];
}
2024-12-10 23:06:47 +08:00
// embed
2025-07-22 19:59:44 +08:00
if ( ! $this -> no_php ) {
2025-07-22 21:13:35 +08:00
$libs = " -lphp -lc { $libs } " ;
2025-07-22 17:23:13 +08:00
}
2025-03-20 07:27:38 +01:00
// mimalloc must come first
if ( str_contains ( $libs , BUILD_LIB_PATH . '/mimalloc.o' )) {
$libs = BUILD_LIB_PATH . '/mimalloc.o ' . str_replace ( BUILD_LIB_PATH . '/mimalloc.o' , '' , $libs );
}
2024-12-10 23:06:47 +08:00
return [
2025-03-27 11:12:19 +07:00
'cflags' => trim ( getenv ( 'CFLAGS' ) . ' ' . $cflags ),
'ldflags' => trim ( getenv ( 'LDFLAGS' ) . ' ' . $ldflags ),
'libs' => trim ( getenv ( 'LIBS' ) . ' ' . $libs ),
2024-12-10 23:06:47 +08:00
];
}
2025-07-22 17:23:13 +08:00
private function getIncludesString ( array $libraries ) : string
2024-12-10 23:06:47 +08:00
{
$base = BUILD_INCLUDE_PATH ;
2025-07-22 17:23:13 +08:00
$includes = [ " -I { $base } " ];
// link with libphp
2025-07-22 19:59:44 +08:00
if ( ! $this -> no_php ) {
2025-07-22 17:23:13 +08:00
$includes = [
... $includes ,
" -I { $base } /php " ,
" -I { $base } /php/main " ,
" -I { $base } /php/TSRM " ,
" -I { $base } /php/Zend " ,
" -I { $base } /php/ext " ,
];
}
// parse pkg-configs
foreach ( $libraries as $library ) {
2025-07-22 22:46:13 +08:00
$pc = Config :: getLib ( $library , 'pkg-configs' , []);
foreach ( $pc as $file ) {
if ( ! file_exists ( BUILD_LIB_PATH . " /pkgconfig/ { $file } .pc " )) {
throw new WrongUsageException ( " pkg-config file ' { $file } .pc' for lib [ { $library } ] does not exist in ' " . BUILD_LIB_PATH . " /pkgconfig'. Please build it first. " );
}
}
$pc_cflags = implode ( ' ' , $pc );
2025-07-22 17:23:13 +08:00
if ( $pc_cflags !== '' ) {
$pc_cflags = PkgConfigUtil :: getCflags ( $pc_cflags );
$includes [] = $pc_cflags ;
}
}
$includes = array_unique ( $includes );
return implode ( ' ' , $includes );
2024-12-10 23:06:47 +08:00
}
private function getLdflagsString () : string
{
return '-L' . BUILD_LIB_PATH ;
}
2025-07-22 19:59:44 +08:00
private function getLibsString ( array $libraries , bool $use_short_libs = true ) : string
2024-12-10 23:06:47 +08:00
{
2025-07-22 19:59:44 +08:00
$lib_names = [];
2025-07-22 17:23:13 +08:00
$frameworks = [];
foreach ( $libraries as $library ) {
// convert all static-libs to short names
2024-12-10 23:06:47 +08:00
$libs = Config :: getLib ( $library , 'static-libs' , []);
foreach ( $libs as $lib ) {
2025-07-22 17:23:13 +08:00
// check file existence
if ( ! file_exists ( BUILD_LIB_PATH . " / { $lib } " )) {
throw new WrongUsageException ( " Library file ' { $lib } ' for lib [ { $library } ] does not exist in ' " . BUILD_LIB_PATH . " '. Please build it first. " );
2025-05-22 12:27:41 +07:00
}
2025-07-22 19:59:44 +08:00
$lib_names [] = $use_short_libs ? $this -> getShortLibName ( $lib ) : ( BUILD_LIB_PATH . " / { $lib } " );
2024-12-10 23:06:47 +08:00
}
2025-07-22 17:23:13 +08:00
// add frameworks for macOS
if ( SPCTarget :: getTargetOS () === 'Darwin' ) {
$frameworks = array_merge ( $frameworks , Config :: getLib ( $library , 'frameworks' , []));
2024-12-10 23:06:47 +08:00
}
2025-07-22 17:23:13 +08:00
// add pkg-configs libs
$pkg_configs = Config :: getLib ( $library , 'pkg-configs' , []);
foreach ( $pkg_configs as $pkg_config ) {
if ( ! file_exists ( BUILD_LIB_PATH . " /pkgconfig/ { $pkg_config } .pc " )) {
throw new WrongUsageException ( " pkg-config file ' { $pkg_config } .pc' for lib [ { $library } ] does not exist in ' " . BUILD_LIB_PATH . " /pkgconfig'. Please build it first. " );
}
}
$pkg_configs = implode ( ' ' , $pkg_configs );
if ( $pkg_configs !== '' ) {
2025-07-22 19:59:44 +08:00
$pc_libs = array_reverse ( PkgConfigUtil :: getLibsArray ( $pkg_configs , $use_short_libs ));
$lib_names = [ ... $lib_names , ... $pc_libs ];
2025-07-22 17:23:13 +08:00
}
}
// post-process
2025-07-22 22:25:16 +08:00
$lib_names = array_reverse ( array_unique ( $lib_names ));
2025-07-22 19:59:44 +08:00
$frameworks = array_unique ( $frameworks );
2025-07-22 17:23:13 +08:00
// process frameworks to short_name
if ( SPCTarget :: getTargetOS () === 'Darwin' ) {
foreach ( $frameworks as $fw ) {
2024-12-10 23:06:47 +08:00
$ks = '-framework ' . $fw ;
2025-07-22 19:59:44 +08:00
if ( ! in_array ( $ks , $lib_names )) {
$lib_names [] = $ks ;
2024-12-10 23:06:47 +08:00
}
}
}
2025-07-22 17:23:13 +08:00
2025-07-10 12:59:27 +08:00
if ( in_array ( 'imap' , $libraries ) && SPCTarget :: getLibc () === 'glibc' ) {
2025-07-22 19:59:44 +08:00
$lib_names [] = '-lcrypt' ;
2025-07-10 12:59:27 +08:00
}
2025-07-22 19:59:44 +08:00
return implode ( ' ' , $lib_names );
2024-12-10 23:06:47 +08:00
}
private function getShortLibName ( string $lib ) : string
{
if ( ! str_starts_with ( $lib , 'lib' ) || ! str_ends_with ( $lib , '.a' )) {
return BUILD_LIB_PATH . '/' . $lib ;
}
// get short name
return '-l' . substr ( $lib , 3 , - 2 );
}
2025-06-12 01:16:57 +08:00
private function getFrameworksString ( array $extensions ) : string
{
$list = [];
foreach ( $extensions as $extension ) {
foreach ( Config :: getExt ( $extension , 'frameworks' , []) as $fw ) {
$ks = '-framework ' . $fw ;
if ( ! in_array ( $ks , $list )) {
$list [] = $ks ;
}
}
}
return implode ( ' ' , $list );
}
2024-12-10 23:06:47 +08:00
}