2626use StaticPHP \Runtime \SystemTarget ;
2727use StaticPHP \Toolchain \Interface \ToolchainInterface ;
2828use StaticPHP \Toolchain \ToolchainManager ;
29+ use StaticPHP \Util \DirDiff ;
2930use StaticPHP \Util \FileSystem ;
3031use StaticPHP \Util \InteractiveTerm ;
3132use StaticPHP \Util \SourcePatcher ;
3233use StaticPHP \Util \SPCConfigUtil ;
34+ use StaticPHP \Util \System \UnixUtil ;
3335use StaticPHP \Util \V2CompatLayer ;
3436use Symfony \Component \Console \Input \InputArgument ;
3537use Symfony \Component \Console \Input \InputOption ;
@@ -312,11 +314,17 @@ public function makeForUnix(TargetPackage $package, PackageInstaller $installer)
312314 if ($ installer ->isBuildPackage ('php-cli ' )) {
313315 $ package ->runStage ('unix-make-cli ' );
314316 }
317+ if ($ installer ->isBuildPackage ('php-cgi ' )) {
318+ $ package ->runStage ('unix-make-cgi ' );
319+ }
315320 if ($ installer ->isBuildPackage ('php-fpm ' )) {
316321 $ package ->runStage ('unix-make-fpm ' );
317322 }
318- if ($ installer ->isBuildPackage ('php-cgi ' )) {
319- $ package ->runStage ('unix-make-cgi ' );
323+ if ($ installer ->isBuildPackage ('php-micro ' )) {
324+ $ package ->runStage ('unix-make-micro ' );
325+ }
326+ if ($ installer ->isBuildPackage ('php-embed ' )) {
327+ $ package ->runStage ('unix-make-embed ' );
320328 }
321329 }
322330
@@ -330,9 +338,103 @@ public function makeCliForUnix(TargetPackage $package, PackageInstaller $install
330338 ->exec ("make -j {$ concurrency } cli " );
331339 }
332340
341+ #[Stage('unix-make-cgi ' )]
342+ public function makeCgiForUnix (TargetPackage $ package , PackageInstaller $ installer , PackageBuilder $ builder ): void
343+ {
344+ InteractiveTerm::setMessage ('Building php: ' . ConsoleColor::yellow ('make cgi ' ));
345+ $ concurrency = $ builder ->concurrency ;
346+ shell ()->cd ($ package ->getSourceDir ())
347+ ->setEnv ($ this ->makeVars ($ installer ))
348+ ->exec ("make -j {$ concurrency } cgi " );
349+ }
350+
351+ #[Stage('unix-make-fpm ' )]
352+ public function makeFpmForUnix (TargetPackage $ package , PackageInstaller $ installer , PackageBuilder $ builder ): void
353+ {
354+ InteractiveTerm::setMessage ('Building php: ' . ConsoleColor::yellow ('make fpm ' ));
355+ $ concurrency = $ builder ->concurrency ;
356+ shell ()->cd ($ package ->getSourceDir ())
357+ ->setEnv ($ this ->makeVars ($ installer ))
358+ ->exec ("make -j {$ concurrency } fpm " );
359+ }
360+
361+ #[Stage('unix-make-micro ' )]
362+ public function makeMicroForUnix (TargetPackage $ package , PackageInstaller $ installer , PackageBuilder $ builder ): void
363+ {
364+ $ phar_patched = false ;
365+ try {
366+ if ($ installer ->isPackageResolved ('ext-phar ' )) {
367+ $ phar_patched = true ;
368+ SourcePatcher::patchMicroPhar (self ::getPHPVersionID ());
369+ }
370+ InteractiveTerm::setMessage ('Building php: ' . ConsoleColor::yellow ('make micro ' ));
371+ // apply --with-micro-fake-cli option
372+ $ vars = $ this ->makeVars ($ installer );
373+ $ vars ['EXTRA_CFLAGS ' ] .= $ package ->getBuildOption ('with-micro-fake-cli ' , false ) ? ' -DPHP_MICRO_FAKE_CLI ' : '' ;
374+ // build
375+ shell ()->cd ($ package ->getSourceDir ())
376+ ->setEnv ($ vars )
377+ ->exec ("make -j {$ builder ->concurrency } micro " );
378+ } finally {
379+ if ($ phar_patched ) {
380+ SourcePatcher::unpatchMicroPhar ();
381+ }
382+ }
383+ }
384+
385+ #[Stage('unix-make-embed ' )]
386+ public function makeEmbedForUnix (TargetPackage $ package , PackageInstaller $ installer , PackageBuilder $ builder ): void
387+ {
388+ $ shared_exts = array_filter (
389+ $ installer ->getResolvedPackages (),
390+ static fn ($ x ) => $ x instanceof PhpExtensionPackage && $ x ->isBuildShared () && $ x ->isBuildWithPhp ()
391+ );
392+ $ install_modules = $ shared_exts ? 'install-modules ' : '' ;
393+
394+ // detect changes in module path
395+ $ diff = new DirDiff (BUILD_MODULES_PATH , true );
396+
397+ $ root = BUILD_ROOT_PATH ;
398+ shell ()->cd ($ package ->getSourceDir ())
399+ ->setEnv ($ this ->makeVars ($ installer ))
400+ ->exec ('sed -i "s|^EXTENSION_DIR = .*|EXTENSION_DIR = / ' . basename (BUILD_MODULES_PATH ) . '|" Makefile ' )
401+ ->exec ("make -j {$ builder ->concurrency } INSTALL_ROOT= {$ root } install-sapi {$ install_modules } install-build install-headers install-programs " );
402+
403+ // ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=shared -------------
404+
405+ // process libphp.so for shared embed
406+ $ suffix = SystemTarget::getTargetOS () === 'Darwin ' ? 'dylib ' : 'so ' ;
407+ $ libphp_so = "{$ package ->getLibDir ()}/libphp. {$ suffix }" ;
408+ if (file_exists ($ libphp_so )) {
409+ // rename libphp.so if -release is set
410+ if (SystemTarget::getTargetOS () === 'Linux ' ) {
411+ $ this ->processLibphpSoFile ($ libphp_so , $ installer );
412+ }
413+ // deploy
414+ $ builder ->deployBinary ($ libphp_so , $ libphp_so , false );
415+ }
416+
417+ // process shared extensions that built-with-php
418+ $ increment_files = $ diff ->getChangedFiles ();
419+ foreach ($ increment_files as $ increment_file ) {
420+ $ builder ->deployBinary ($ increment_file , $ libphp_so , false );
421+ }
422+
423+ // ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=static -------------
424+
425+ // process libphp.a for static embed
426+ $ ar = getenv ('AR ' ) ?: 'ar ' ;
427+ $ libphp_a = "{$ package ->getLibDir ()}/libphp.a " ;
428+ shell ()->exec ("{$ ar } -t {$ libphp_a } | grep ' \\.a$' | xargs -n1 {$ ar } d {$ libphp_a }" );
429+ UnixUtil::exportDynamicSymbols ($ libphp_a );
430+
431+ // deploy embed php scripts
432+ $ package ->runStage ('patch-embed-scripts ' );
433+ }
434+
333435 #[BuildFor('Darwin ' )]
334436 #[BuildFor('Linux ' )]
335- public function build (TargetPackage $ package ): void
437+ public function build (TargetPackage $ package, PackageInstaller $ installer , ToolchainInterface $ toolchain ): void
336438 {
337439 // virtual target, do nothing
338440 if ($ package ->getName () !== 'php ' ) {
@@ -342,6 +444,68 @@ public function build(TargetPackage $package): void
342444 $ package ->runStage ('unix-buildconf ' );
343445 $ package ->runStage ('unix-configure ' );
344446 $ package ->runStage ('unix-make ' );
447+
448+ // collect shared extensions
449+ /** @var PhpExtensionPackage[] $shared_extensions */
450+ $ shared_extensions = array_filter (
451+ $ installer ->getResolvedPackages (PhpExtensionPackage::class),
452+ fn ($ x ) => $ x ->isBuildShared () && !$ x ->isBuildWithPhp ()
453+ );
454+ if (!empty ($ shared_extensions )) {
455+ if ($ toolchain ->isStatic ()) {
456+ throw new WrongUsageException (
457+ "You're building against musl libc statically (the default on Linux), but you're trying to build shared extensions. \n" .
458+ 'Static musl libc does not implement `dlopen`, so your php binary is not able to load shared extensions. ' . "\n" .
459+ 'Either use SPC_LIBC=glibc to link against glibc on a glibc OS, or use SPC_TARGET="native-native-musl -dynamic" to link against musl libc dynamically using `zig cc`. '
460+ );
461+ }
462+ FileSystem::createDir (BUILD_MODULES_PATH );
463+
464+ // backup
465+ FileSystem::backupFile (BUILD_BIN_PATH . '/php-config ' );
466+ FileSystem::backupFile (BUILD_LIB_PATH . '/php/build/phpize.m4 ' );
467+
468+ FileSystem::replaceFileLineContainsString (BUILD_BIN_PATH . '/php-config ' , 'extension_dir= ' , 'extension_dir=" ' . BUILD_MODULES_PATH . '" ' );
469+ FileSystem::replaceFileStr (BUILD_LIB_PATH . '/php/build/phpize.m4 ' , 'test "[$]$1" = "no" && $1=yes ' , '# test "[$]$1" = "no" && $1=yes ' );
470+ }
471+
472+ try {
473+ foreach ($ shared_extensions as $ extension ) {
474+ logger ()->info ('Building shared extensions... ' );
475+ $ extension ->buildSharedExtension ();
476+ }
477+ } finally {
478+ // restore php-config
479+ if (!empty ($ shared_extensions )) {
480+ FileSystem::restoreBackupFile (BUILD_BIN_PATH . '/php-config ' );
481+ FileSystem::restoreBackupFile (BUILD_LIB_PATH . '/php/build/phpize.m4 ' );
482+ }
483+ }
484+ }
485+
486+ /**
487+ * Patch phpize and php-config if needed
488+ */
489+ #[Stage('patch-embed-scripts ' )]
490+ public function patchPhpScripts (): void
491+ {
492+ // patch phpize
493+ if (file_exists (BUILD_BIN_PATH . '/phpize ' )) {
494+ logger ()->debug ('Patching phpize prefix ' );
495+ FileSystem::replaceFileStr (BUILD_BIN_PATH . '/phpize ' , "prefix='' " , "prefix=' " . BUILD_ROOT_PATH . "' " );
496+ FileSystem::replaceFileStr (BUILD_BIN_PATH . '/phpize ' , 's## ' , 's#/usr/local# ' );
497+ }
498+ // patch php-config
499+ if (file_exists (BUILD_BIN_PATH . '/php-config ' )) {
500+ logger ()->debug ('Patching php-config prefix and libs order ' );
501+ $ php_config_str = FileSystem::readFile (BUILD_BIN_PATH . '/php-config ' );
502+ $ php_config_str = str_replace ('prefix="" ' , 'prefix=" ' . BUILD_ROOT_PATH . '" ' , $ php_config_str );
503+ // move mimalloc to the beginning of libs
504+ $ php_config_str = preg_replace ('/(libs=")(.*?)\s*( ' . preg_quote (BUILD_LIB_PATH , '/ ' ) . '\/mimalloc\.o)\s*(.*?)"/ ' , '$1$3 $2 $4" ' , $ php_config_str );
505+ // move lstdc++ to the end of libs
506+ $ php_config_str = preg_replace ('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/ ' , '$1$2 $4 $3" ' , $ php_config_str );
507+ FileSystem::writeFile (BUILD_BIN_PATH . '/php-config ' , $ php_config_str );
508+ }
345509 }
346510
347511 /**
@@ -381,6 +545,10 @@ private function makeStaticExtensionString(PackageInstaller $installer): string
381545 return $ str ;
382546 }
383547
548+ /**
549+ * Make environment variables for php make.
550+ * This will call SPCConfigUtil to generate proper LDFLAGS and LIBS for static linking.
551+ */
384552 private function makeVars (PackageInstaller $ installer ): array
385553 {
386554 $ config = (new SPCConfigUtil (['libs_only_deps ' => true ]))->config (array_map (fn ($ x ) => $ x ->getName (), $ installer ->getResolvedPackages ()));
@@ -394,4 +562,75 @@ private function makeVars(PackageInstaller $installer): array
394562 'EXTRA_LIBS ' => $ config ['libs ' ],
395563 ]);
396564 }
565+
566+ /**
567+ * Rename libphp.so to libphp-<release>.so if -release is set in LDFLAGS.
568+ */
569+ private function processLibphpSoFile (string $ libphpSo , PackageInstaller $ installer ): void
570+ {
571+ $ ldflags = getenv ('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS ' ) ?: '' ;
572+ $ libDir = BUILD_LIB_PATH ;
573+ $ modulesDir = BUILD_MODULES_PATH ;
574+ $ realLibName = 'libphp.so ' ;
575+ $ cwd = getcwd ();
576+
577+ if (preg_match ('/-release\s+(\S+)/ ' , $ ldflags , $ matches )) {
578+ $ release = $ matches [1 ];
579+ $ realLibName = "libphp- {$ release }.so " ;
580+ $ libphpRelease = "{$ libDir }/ {$ realLibName }" ;
581+ if (!file_exists ($ libphpRelease ) && file_exists ($ libphpSo )) {
582+ rename ($ libphpSo , $ libphpRelease );
583+ }
584+ if (file_exists ($ libphpRelease )) {
585+ chdir ($ libDir );
586+ if (file_exists ($ libphpSo )) {
587+ unlink ($ libphpSo );
588+ }
589+ symlink ($ realLibName , 'libphp.so ' );
590+ shell ()->exec (sprintf (
591+ 'patchelf --set-soname %s %s ' ,
592+ escapeshellarg ($ realLibName ),
593+ escapeshellarg ($ libphpRelease )
594+ ));
595+ }
596+ if (is_dir ($ modulesDir )) {
597+ chdir ($ modulesDir );
598+ foreach ($ installer ->getResolvedPackages (PhpExtensionPackage::class) as $ ext ) {
599+ if (!$ ext ->isBuildShared ()) {
600+ continue ;
601+ }
602+ $ name = $ ext ->getName ();
603+ $ versioned = "{$ name }- {$ release }.so " ;
604+ $ unversioned = "{$ name }.so " ;
605+ $ src = "{$ modulesDir }/ {$ versioned }" ;
606+ $ dst = "{$ modulesDir }/ {$ unversioned }" ;
607+ if (is_file ($ src )) {
608+ rename ($ src , $ dst );
609+ shell ()->exec (sprintf (
610+ 'patchelf --set-soname %s %s ' ,
611+ escapeshellarg ($ unversioned ),
612+ escapeshellarg ($ dst )
613+ ));
614+ }
615+ }
616+ }
617+ chdir ($ cwd );
618+ }
619+
620+ $ target = "{$ libDir }/ {$ realLibName }" ;
621+ if (file_exists ($ target )) {
622+ [, $ output ] = shell ()->execWithResult ('readelf -d ' . escapeshellarg ($ target ));
623+ $ output = implode ("\n" , $ output );
624+ if (preg_match ('/SONAME.*\[(.+)]/ ' , $ output , $ sonameMatch )) {
625+ $ currentSoname = $ sonameMatch [1 ];
626+ if ($ currentSoname !== basename ($ target )) {
627+ shell ()->exec (sprintf (
628+ 'patchelf --set-soname %s %s ' ,
629+ escapeshellarg (basename ($ target )),
630+ escapeshellarg ($ target )
631+ ));
632+ }
633+ }
634+ }
635+ }
397636}
0 commit comments