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-09-30 00:12:59 +02:00
use SPC\builder\Extension ;
2025-04-18 14:38:22 +08:00
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
* }
*/
2025-07-24 21:57:56 +07:00
public function config ( array $extensions = [], array $libraries = [], bool $include_suggest_ext = false , bool $include_suggest_lib = false ) : array
2024-12-10 23:06:47 +08:00
{
2025-08-25 14:55:30 +07:00
$extra_exts = [];
foreach ( $extensions as $ext ) {
$extra_exts = array_merge ( $extra_exts , Config :: getExt ( $ext , 'ext-suggests' , []));
}
foreach ( $extra_exts as $ext ) {
if ( $this -> builder ? -> getExt ( $ext ) && ! in_array ( $ext , $extensions )) {
$extensions [] = $ext ;
}
}
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 );
2024-12-10 23:06:47 +08:00
2025-07-22 19:59:44 +08:00
// additional OS-specific libraries (e.g. macOS -lresolv)
2024-12-10 23:06:47 +08:00
// embed
2025-07-04 14:27:48 +07:00
if ( $extra_libs = SPCTarget :: getRuntimeLibs ()) {
$libs .= " { $extra_libs } " ;
2024-12-10 23:06:47 +08:00
}
2025-07-22 19:59:44 +08:00
$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-09-30 00:12:59 +02:00
if ( $this -> hasCpp ( $extensions , $libraries )) {
2025-07-24 21:42:31 +07:00
$libcpp = SPCTarget :: getTargetOS () === 'Darwin' ? '-lc++' : '-lstdc++' ;
2025-08-19 10:27:52 +07:00
$libs = str_replace ( $libcpp , '' , $libs ) . " { $libcpp } " ;
2025-07-22 22:46:13 +08:00
}
2025-07-23 10:46:30 +07:00
2025-07-22 19:59:44 +08:00
if ( $this -> libs_only_deps ) {
2025-07-24 10:53:49 +07:00
// mimalloc must come first
2025-07-26 13:51:34 +07:00
if ( $this -> builder -> getLib ( 'mimalloc' ) && file_exists ( BUILD_LIB_PATH . '/libmimalloc.a' )) {
$libs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace ([ BUILD_LIB_PATH . '/libmimalloc.a' , '-lmimalloc' ], [ '' , '' ], $libs );
2025-07-24 10:53:49 +07:00
}
2025-07-22 19:59:44 +08:00
return [
2025-07-25 10:04:06 +07:00
'cflags' => clean_spaces ( getenv ( 'CFLAGS' ) . ' ' . $cflags ),
'ldflags' => clean_spaces ( getenv ( 'LDFLAGS' ) . ' ' . $ldflags ),
'libs' => clean_spaces ( getenv ( 'LIBS' ) . ' ' . $libs ),
2025-07-22 19:59:44 +08:00
];
2024-12-10 23:06:47 +08:00
}
// embed
2025-07-22 19:59:44 +08:00
if ( ! $this -> no_php ) {
2025-07-23 10:46:30 +07:00
$libs = " -lphp { $libs } -lc " ;
2025-07-22 17:23:13 +08:00
}
2025-07-23 10:46:30 +07:00
2025-07-23 10:49:23 +07:00
$allLibs = getenv ( 'LIBS' ) . ' ' . $libs ;
2025-03-20 07:27:38 +01:00
// mimalloc must come first
2025-07-26 13:51:34 +07:00
if ( $this -> builder -> getLib ( 'mimalloc' ) && file_exists ( BUILD_LIB_PATH . '/libmimalloc.a' )) {
$allLibs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace ([ BUILD_LIB_PATH . '/libmimalloc.a' , '-lmimalloc' ], [ '' , '' ], $allLibs );
2025-03-20 07:27:38 +01:00
}
2025-07-23 10:46:30 +07:00
2024-12-10 23:06:47 +08:00
return [
2025-07-25 10:04:06 +07:00
'cflags' => clean_spaces ( getenv ( 'CFLAGS' ) . ' ' . $cflags ),
'ldflags' => clean_spaces ( getenv ( 'LDFLAGS' ) . ' ' . $ldflags ),
'libs' => clean_spaces ( $allLibs ),
2024-12-10 23:06:47 +08:00
];
}
2025-09-30 00:12:59 +02:00
private function hasCpp ( array $extensions , array $libraries ) : bool
{
// judge cpp-extension
$builderExtNames = array_keys ( $this -> builder -> getExts ( false ));
2025-09-30 00:34:30 +02:00
$exts = array_unique ([ ... $builderExtNames , ... $extensions ]);
2025-09-30 00:12:59 +02:00
foreach ( $exts as $ext ) {
if ( Config :: getExt ( $ext , 'cpp-extension' , false ) === true ) {
return true ;
}
}
$builderLibNames = array_keys ( $this -> builder -> getLibs ());
2025-09-30 00:34:30 +02:00
$libs = array_unique ([ ... $builderLibNames , ... $libraries ]);
2025-09-30 00:12:59 +02:00
foreach ( $libs as $lib ) {
if ( Config :: getLib ( $lib , 'cpp-library' , false ) === true ) {
return true ;
}
}
return false ;
}
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-23 09:46:32 +07:00
if ( $pc_cflags !== '' && ( $pc_cflags = PkgConfigUtil :: getCflags ( $pc_cflags )) !== '' ) {
2025-07-23 17:33:49 +07:00
$arr = explode ( ' ' , $pc_cflags );
$arr = array_unique ( $arr );
$arr = array_filter ( $arr , fn ( $x ) => ! str_starts_with ( $x , 'SHELL:-Xarch_' ));
$pc_cflags = implode ( ' ' , $arr );
2025-07-22 17:23:13 +08:00
$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 ) {
// 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-23 13:16:23 +07:00
// static libs with dependencies come in reverse order, so reverse this too
2025-07-23 10:21:36 +07:00
$pc_libs = array_reverse ( PkgConfigUtil :: getLibsArray ( $pkg_configs ));
2025-07-22 19:59:44 +08:00
$lib_names = [ ... $lib_names , ... $pc_libs ];
2025-07-22 17:23:13 +08:00
}
2025-07-23 14:13:22 +07:00
// convert all static-libs to short names
2025-07-24 21:29:34 +07:00
$libs = array_reverse ( Config :: getLib ( $library , 'static-libs' , []));
2024-12-10 23:06:47 +08:00
foreach ( $libs as $lib ) {
2025-07-23 14:13:22 +07: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-23 14:13:22 +07:00
$lib_names [] = $this -> getShortLibName ( $lib );
2024-12-10 23:06:47 +08:00
}
2025-07-23 14:13:22 +07: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
}
// post-process
2025-07-23 14:13:22 +07:00
$lib_names = array_filter ( $lib_names , fn ( $x ) => $x !== '' );
$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-23 10:46:30 +07:00
if ( ! $use_short_libs ) {
$lib_names = array_map ( fn ( $l ) => $this -> getFullLibName ( $l ), $lib_names );
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
2025-07-23 10:46:30 +07:00
private function getFullLibName ( string $lib )
{
if ( ! str_starts_with ( $lib , '-l' )) {
return $lib ;
}
$libname = substr ( $lib , 2 );
$staticLib = BUILD_LIB_PATH . '/' . " lib { $libname } .a " ;
if ( file_exists ( $staticLib )) {
return $staticLib ;
}
return $lib ;
}
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
}