diff --git a/bin/spc-alpine-docker b/bin/spc-alpine-docker index 2790a5c3..2640ffba 100755 --- a/bin/spc-alpine-docker +++ b/bin/spc-alpine-docker @@ -3,7 +3,7 @@ set -e # This file is using docker to run commands -SPC_DOCKER_VERSION=v6 +SPC_DOCKER_VERSION=v7 # Detect docker can run if ! which docker >/dev/null; then @@ -123,6 +123,7 @@ COPY ./composer.* /app/ ADD ./bin /app/bin RUN composer install --no-dev ADD ./config /app/config +ADD ./spc.registry.json /app/spc.registry.json RUN bin/spc doctor --auto-fix RUN bin/spc install-pkg upx diff --git a/src/Package/Extension/readline.php b/src/Package/Extension/readline.php new file mode 100644 index 00000000..2ecc533a --- /dev/null +++ b/src/Package/Extension/readline.php @@ -0,0 +1,34 @@ +isStatic()) { + $php_src = $installer->getBuildPackage('php')->getSourceDir(); + SourcePatcher::patchFile('musl_static_readline.patch', $php_src); + } + } + + #[AfterStage('php', 'unix-make-cli')] + public function afterMakeLinuxCli(PackageInstaller $installer, ToolchainInterface $toolchain): void + { + if ($toolchain->isStatic()) { + $php_src = $installer->getBuildPackage('php')->getSourceDir(); + SourcePatcher::patchFile('musl_static_readline.patch', $php_src, true); + } + } +} diff --git a/src/Package/Library/imap.php b/src/Package/Library/imap.php new file mode 100644 index 00000000..58e9397f --- /dev/null +++ b/src/Package/Library/imap.php @@ -0,0 +1,24 @@ +isBuildPackage('php-cli')) { $package->runStage('unix-make-cli'); } + if ($installer->isBuildPackage('php-cgi')) { + $package->runStage('unix-make-cgi'); + } if ($installer->isBuildPackage('php-fpm')) { $package->runStage('unix-make-fpm'); } - if ($installer->isBuildPackage('php-cgi')) { - $package->runStage('unix-make-cgi'); + if ($installer->isBuildPackage('php-micro')) { + $package->runStage('unix-make-micro'); + } + if ($installer->isBuildPackage('php-embed')) { + $package->runStage('unix-make-embed'); } } @@ -330,9 +338,103 @@ class php ->exec("make -j{$concurrency} cli"); } + #[Stage('unix-make-cgi')] + public function makeCgiForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cgi')); + $concurrency = $builder->concurrency; + shell()->cd($package->getSourceDir()) + ->setEnv($this->makeVars($installer)) + ->exec("make -j{$concurrency} cgi"); + } + + #[Stage('unix-make-fpm')] + public function makeFpmForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make fpm')); + $concurrency = $builder->concurrency; + shell()->cd($package->getSourceDir()) + ->setEnv($this->makeVars($installer)) + ->exec("make -j{$concurrency} fpm"); + } + + #[Stage('unix-make-micro')] + public function makeMicroForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void + { + $phar_patched = false; + try { + if ($installer->isPackageResolved('ext-phar')) { + $phar_patched = true; + SourcePatcher::patchMicroPhar(self::getPHPVersionID()); + } + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make micro')); + // apply --with-micro-fake-cli option + $vars = $this->makeVars($installer); + $vars['EXTRA_CFLAGS'] .= $package->getBuildOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; + // build + shell()->cd($package->getSourceDir()) + ->setEnv($vars) + ->exec("make -j{$builder->concurrency} micro"); + } finally { + if ($phar_patched) { + SourcePatcher::unpatchMicroPhar(); + } + } + } + + #[Stage('unix-make-embed')] + public function makeEmbedForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void + { + $shared_exts = array_filter( + $installer->getResolvedPackages(), + static fn ($x) => $x instanceof PhpExtensionPackage && $x->isBuildShared() && $x->isBuildWithPhp() + ); + $install_modules = $shared_exts ? 'install-modules' : ''; + + // detect changes in module path + $diff = new DirDiff(BUILD_MODULES_PATH, true); + + $root = BUILD_ROOT_PATH; + shell()->cd($package->getSourceDir()) + ->setEnv($this->makeVars($installer)) + ->exec('sed -i "s|^EXTENSION_DIR = .*|EXTENSION_DIR = /' . basename(BUILD_MODULES_PATH) . '|" Makefile') + ->exec("make -j{$builder->concurrency} INSTALL_ROOT={$root} install-sapi {$install_modules} install-build install-headers install-programs"); + + // ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=shared ------------- + + // process libphp.so for shared embed + $suffix = SystemTarget::getTargetOS() === 'Darwin' ? 'dylib' : 'so'; + $libphp_so = "{$package->getLibDir()}/libphp.{$suffix}"; + if (file_exists($libphp_so)) { + // rename libphp.so if -release is set + if (SystemTarget::getTargetOS() === 'Linux') { + $this->processLibphpSoFile($libphp_so, $installer); + } + // deploy + $builder->deployBinary($libphp_so, $libphp_so, false); + } + + // process shared extensions that built-with-php + $increment_files = $diff->getChangedFiles(); + foreach ($increment_files as $increment_file) { + $builder->deployBinary($increment_file, $libphp_so, false); + } + + // ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=static ------------- + + // process libphp.a for static embed + $ar = getenv('AR') ?: 'ar'; + $libphp_a = "{$package->getLibDir()}/libphp.a"; + shell()->exec("{$ar} -t {$libphp_a} | grep '\\.a$' | xargs -n1 {$ar} d {$libphp_a}"); + UnixUtil::exportDynamicSymbols($libphp_a); + + // deploy embed php scripts + $package->runStage('patch-embed-scripts'); + } + #[BuildFor('Darwin')] #[BuildFor('Linux')] - public function build(TargetPackage $package): void + public function build(TargetPackage $package, PackageInstaller $installer, ToolchainInterface $toolchain): void { // virtual target, do nothing if ($package->getName() !== 'php') { @@ -342,6 +444,68 @@ class php $package->runStage('unix-buildconf'); $package->runStage('unix-configure'); $package->runStage('unix-make'); + + // collect shared extensions + /** @var PhpExtensionPackage[] $shared_extensions */ + $shared_extensions = array_filter( + $installer->getResolvedPackages(PhpExtensionPackage::class), + fn ($x) => $x->isBuildShared() && !$x->isBuildWithPhp() + ); + if (!empty($shared_extensions)) { + if ($toolchain->isStatic()) { + throw new WrongUsageException( + "You're building against musl libc statically (the default on Linux), but you're trying to build shared extensions.\n" . + 'Static musl libc does not implement `dlopen`, so your php binary is not able to load shared extensions.' . "\n" . + '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`.' + ); + } + FileSystem::createDir(BUILD_MODULES_PATH); + + // backup + FileSystem::backupFile(BUILD_BIN_PATH . '/php-config'); + FileSystem::backupFile(BUILD_LIB_PATH . '/php/build/phpize.m4'); + + FileSystem::replaceFileLineContainsString(BUILD_BIN_PATH . '/php-config', 'extension_dir=', 'extension_dir="' . BUILD_MODULES_PATH . '"'); + FileSystem::replaceFileStr(BUILD_LIB_PATH . '/php/build/phpize.m4', 'test "[$]$1" = "no" && $1=yes', '# test "[$]$1" = "no" && $1=yes'); + } + + try { + foreach ($shared_extensions as $extension) { + logger()->info('Building shared extensions...'); + $extension->buildSharedExtension(); + } + } finally { + // restore php-config + if (!empty($shared_extensions)) { + FileSystem::restoreBackupFile(BUILD_BIN_PATH . '/php-config'); + FileSystem::restoreBackupFile(BUILD_LIB_PATH . '/php/build/phpize.m4'); + } + } + } + + /** + * Patch phpize and php-config if needed + */ + #[Stage('patch-embed-scripts')] + public function patchPhpScripts(): void + { + // patch phpize + if (file_exists(BUILD_BIN_PATH . '/phpize')) { + logger()->debug('Patching phpize prefix'); + FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'"); + FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#'); + } + // patch php-config + if (file_exists(BUILD_BIN_PATH . '/php-config')) { + logger()->debug('Patching php-config prefix and libs order'); + $php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config'); + $php_config_str = str_replace('prefix=""', 'prefix="' . BUILD_ROOT_PATH . '"', $php_config_str); + // move mimalloc to the beginning of libs + $php_config_str = preg_replace('/(libs=")(.*?)\s*(' . preg_quote(BUILD_LIB_PATH, '/') . '\/mimalloc\.o)\s*(.*?)"/', '$1$3 $2 $4"', $php_config_str); + // move lstdc++ to the end of libs + $php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str); + FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str); + } } /** @@ -381,6 +545,10 @@ class php return $str; } + /** + * Make environment variables for php make. + * This will call SPCConfigUtil to generate proper LDFLAGS and LIBS for static linking. + */ private function makeVars(PackageInstaller $installer): array { $config = (new SPCConfigUtil(['libs_only_deps' => true]))->config(array_map(fn ($x) => $x->getName(), $installer->getResolvedPackages())); @@ -394,4 +562,75 @@ class php 'EXTRA_LIBS' => $config['libs'], ]); } + + /** + * Rename libphp.so to libphp-.so if -release is set in LDFLAGS. + */ + private function processLibphpSoFile(string $libphpSo, PackageInstaller $installer): void + { + $ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: ''; + $libDir = BUILD_LIB_PATH; + $modulesDir = BUILD_MODULES_PATH; + $realLibName = 'libphp.so'; + $cwd = getcwd(); + + if (preg_match('/-release\s+(\S+)/', $ldflags, $matches)) { + $release = $matches[1]; + $realLibName = "libphp-{$release}.so"; + $libphpRelease = "{$libDir}/{$realLibName}"; + if (!file_exists($libphpRelease) && file_exists($libphpSo)) { + rename($libphpSo, $libphpRelease); + } + if (file_exists($libphpRelease)) { + chdir($libDir); + if (file_exists($libphpSo)) { + unlink($libphpSo); + } + symlink($realLibName, 'libphp.so'); + shell()->exec(sprintf( + 'patchelf --set-soname %s %s', + escapeshellarg($realLibName), + escapeshellarg($libphpRelease) + )); + } + if (is_dir($modulesDir)) { + chdir($modulesDir); + foreach ($installer->getResolvedPackages(PhpExtensionPackage::class) as $ext) { + if (!$ext->isBuildShared()) { + continue; + } + $name = $ext->getName(); + $versioned = "{$name}-{$release}.so"; + $unversioned = "{$name}.so"; + $src = "{$modulesDir}/{$versioned}"; + $dst = "{$modulesDir}/{$unversioned}"; + if (is_file($src)) { + rename($src, $dst); + shell()->exec(sprintf( + 'patchelf --set-soname %s %s', + escapeshellarg($unversioned), + escapeshellarg($dst) + )); + } + } + } + chdir($cwd); + } + + $target = "{$libDir}/{$realLibName}"; + if (file_exists($target)) { + [, $output] = shell()->execWithResult('readelf -d ' . escapeshellarg($target)); + $output = implode("\n", $output); + if (preg_match('/SONAME.*\[(.+)]/', $output, $sonameMatch)) { + $currentSoname = $sonameMatch[1]; + if ($currentSoname !== basename($target)) { + shell()->exec(sprintf( + 'patchelf --set-soname %s %s', + escapeshellarg(basename($target)), + escapeshellarg($target) + )); + } + } + } + } } diff --git a/src/StaticPHP/ConsoleApplication.php b/src/StaticPHP/ConsoleApplication.php index b60fd10e..1f63190b 100644 --- a/src/StaticPHP/ConsoleApplication.php +++ b/src/StaticPHP/ConsoleApplication.php @@ -35,10 +35,11 @@ class ConsoleApplication extends Application // only add target that contains artifact.source if ($package->hasStage('build')) { logger()->debug("Registering build target command for package: {$name}"); - $this->add(new BuildTargetCommand($name)); + $this->addCommand(new BuildTargetCommand($name)); } } + // add core commands $this->addCommands([ new DownloadCommand(), new DoctorCommand(), diff --git a/src/StaticPHP/Package/PackageBuilder.php b/src/StaticPHP/Package/PackageBuilder.php index b4088872..c87b7f29 100644 --- a/src/StaticPHP/Package/PackageBuilder.php +++ b/src/StaticPHP/Package/PackageBuilder.php @@ -9,9 +9,11 @@ use StaticPHP\DI\ApplicationContext; use StaticPHP\Exception\SPCInternalException; use StaticPHP\Exception\WrongUsageException; use StaticPHP\Runtime\Shell\Shell; +use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\FileSystem; use StaticPHP\Util\GlobalEnvManager; use StaticPHP\Util\InteractiveTerm; +use StaticPHP\Util\System\LinuxUtil; class PackageBuilder { @@ -85,6 +87,92 @@ class PackageBuilder return $this->options[$key] ?? $default; } + /** + * Deploy the binary file from src to dst. + */ + public function deployBinary(string $src, string $dst, bool $executable = true): string + { + logger()->debug("Deploying binary from {$src} to {$dst}"); + + // file must exists + if (!file_exists($src)) { + throw new SPCInternalException("Deploy failed. Cannot find file: {$src}"); + } + // dst dir must exists + FileSystem::createDir(dirname($dst)); + + // ignore copy to self + if (realpath($src) !== realpath($dst)) { + shell()->exec('cp ' . escapeshellarg($src) . ' ' . escapeshellarg($dst)); + } + + // file exist + if (!file_exists($dst)) { + throw new SPCInternalException("Deploy failed. Cannot find file after copy: {$dst}"); + } + + // extract debug info + $this->extractDebugInfo($dst); + + // strip + if (!$this->getOption('no-strip')) { + $this->stripBinary($dst); + } + + // UPX for linux + $upx_option = $this->getOption('with-upx-pack'); + if ($upx_option && SystemTarget::getTargetOS() === 'Linux' && $executable) { + if ($this->getOption('no-strip')) { + logger()->warning('UPX compression is not recommended when --no-strip is enabled.'); + } + logger()->info("Compressing {$dst} with UPX"); + shell()->exec(getenv('UPX_EXEC') . " --best {$dst}"); + } + + return $dst; + } + + /** + * Extract debug information from binary file. + * + * @param string $binary_path the path to the binary file, including executables, shared libraries, etc + */ + public function extractDebugInfo(string $binary_path): string + { + $target_dir = BUILD_ROOT_PATH . '/debug'; + FileSystem::createDir($target_dir); + $basename = basename($binary_path); + $debug_file = "{$target_dir}/{$basename}" . (SystemTarget::getTargetOS() === 'Darwin' ? '.dwarf' : '.debug'); + if (SystemTarget::getTargetOS() === 'Darwin') { + shell()->exec("dsymutil -f {$binary_path} -o {$debug_file}"); + } elseif (SystemTarget::getTargetOS() === 'Linux') { + if ($eu_strip = LinuxUtil::findCommand('eu-strip')) { + shell() + ->exec("{$eu_strip} -f {$debug_file} {$binary_path}") + ->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}"); + } else { + shell() + ->exec("objcopy --only-keep-debug {$binary_path} {$debug_file}") + ->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}"); + } + } else { + throw new SPCInternalException('extractDebugInfo is only supported on Linux and macOS'); + } + return $debug_file; + } + + /** + * Strip unneeded symbols from binary file. + */ + public function stripBinary(string $binary_path): void + { + shell()->exec(match (SystemTarget::getTargetOS()) { + 'Darwin' => "strip -S {$binary_path}", + 'Linux' => "strip --strip-unneeded {$binary_path}", + default => throw new SPCInternalException('stripBinary is only supported on Linux and macOS'), + }); + } + private function installLicense(Package $package, array $license): void { $dir = BUILD_ROOT_PATH . '/source-licenses/' . $package->getName(); diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 15bab0dc..34e24621 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -195,15 +195,20 @@ class PackageInstaller /** * Get all resolved packages. + * You can filter by package type class if needed. * - * @return array + * @template T + * @param class-string $package_type Filter by package type + * @return array */ - public function getResolvedPackages(): array + public function getResolvedPackages(mixed $package_type = Package::class): array { - return $this->packages; + return array_filter($this->packages, function (Package $pkg) use ($package_type): bool { + return $pkg instanceof $package_type; + }); } - public function isPackageBeingResolved(string $package_name): bool + public function isPackageResolved(string $package_name): bool { return isset($this->packages[$package_name]); } diff --git a/src/StaticPHP/Package/PackageLoader.php b/src/StaticPHP/Package/PackageLoader.php index cdbb2196..c89bf392 100644 --- a/src/StaticPHP/Package/PackageLoader.php +++ b/src/StaticPHP/Package/PackageLoader.php @@ -232,7 +232,7 @@ class PackageLoader $installer = ApplicationContext::get(PackageInstaller::class); $stages = self::$before_stages[$package_name][$stage] ?? []; foreach ($stages as [$callback, $only_when_package_resolved]) { - if ($only_when_package_resolved !== null && !$installer->isPackageBeingResolved($only_when_package_resolved)) { + if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) { continue; } yield $callback; @@ -246,7 +246,7 @@ class PackageLoader $stages = self::$after_stage[$package_name][$stage] ?? []; $result = []; foreach ($stages as [$callback, $only_when_package_resolved]) { - if ($only_when_package_resolved !== null && !$installer->isPackageBeingResolved($only_when_package_resolved)) { + if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) { continue; } $result[] = $callback; diff --git a/src/StaticPHP/Package/PhpExtensionPackage.php b/src/StaticPHP/Package/PhpExtensionPackage.php index 673d1c82..562d8f8e 100644 --- a/src/StaticPHP/Package/PhpExtensionPackage.php +++ b/src/StaticPHP/Package/PhpExtensionPackage.php @@ -107,4 +107,9 @@ class PhpExtensionPackage extends Package { return $this->build_with_php; } + + public function buildSharedExtension(): void + { + // TODO: build common shared extensions code here... + } } diff --git a/src/StaticPHP/Registry/Registry.php b/src/StaticPHP/Registry/Registry.php index 8e598863..57ac82d1 100644 --- a/src/StaticPHP/Registry/Registry.php +++ b/src/StaticPHP/Registry/Registry.php @@ -252,6 +252,12 @@ class Registry ); } + /** + * Return full path, resolving relative paths against a base path. + * + * @param string $path Input path (relative or absolute) + * @param string $relative_path_base Base path for relative paths + */ private static function fullpath(string $path, string $relative_path_base): string { if (FileSystem::isRelativePath($path)) { diff --git a/src/StaticPHP/Util/DirDiff.php b/src/StaticPHP/Util/DirDiff.php new file mode 100644 index 00000000..7f80f364 --- /dev/null +++ b/src/StaticPHP/Util/DirDiff.php @@ -0,0 +1,95 @@ +reset(); + } + + /** + * Reset the baseline to current state. + */ + public function reset(): void + { + $this->before = FileSystem::scanDirFiles($this->dir, relative: true) ?: []; + + if ($this->track_content_changes) { + $this->before_file_hashes = []; + foreach ($this->before as $file) { + $this->before_file_hashes[$file] = md5_file($this->dir . DIRECTORY_SEPARATOR . $file); + } + } + } + + /** + * Get the list of incremented files. + * + * @param bool $relative Return relative paths or absolute paths + * @return array List of incremented files + */ + public function getIncrementFiles(bool $relative = false): array + { + $after = FileSystem::scanDirFiles($this->dir, relative: true) ?: []; + $diff = array_diff($after, $this->before); + if ($relative) { + return $diff; + } + return array_map(fn ($f) => $this->dir . DIRECTORY_SEPARATOR . $f, $diff); + } + + /** + * Get the list of changed files (including new files). + * + * @param bool $relative Return relative paths or absolute paths + * @param bool $include_new_files Include new files as changed files + * @return array List of changed files + */ + public function getChangedFiles(bool $relative = false, bool $include_new_files = true): array + { + $after = FileSystem::scanDirFiles($this->dir, relative: true) ?: []; + $changed = []; + foreach ($after as $file) { + if (isset($this->before_file_hashes[$file])) { + $after_hash = md5_file($this->dir . DIRECTORY_SEPARATOR . $file); + if ($after_hash !== $this->before_file_hashes[$file]) { + $changed[] = $file; + } + } elseif ($include_new_files) { + // New file, consider as changed + $changed[] = $file; + } + } + if ($relative) { + return $changed; + } + return array_map(fn ($f) => $this->dir . DIRECTORY_SEPARATOR . $f, $changed); + } + + /** + * Get the list of removed files. + * + * @param bool $relative Return relative paths or absolute paths + * @return array List of removed files + */ + public function getRemovedFiles(bool $relative = false): array + { + $after = FileSystem::scanDirFiles($this->dir, relative: true) ?: []; + $removed = array_diff($this->before, $after); + if ($relative) { + return $removed; + } + return array_map(fn ($f) => $this->dir . DIRECTORY_SEPARATOR . $f, $removed); + } +} diff --git a/src/StaticPHP/Util/SourcePatcher.php b/src/StaticPHP/Util/SourcePatcher.php index 6f57cb00..42ea8c36 100644 --- a/src/StaticPHP/Util/SourcePatcher.php +++ b/src/StaticPHP/Util/SourcePatcher.php @@ -159,4 +159,39 @@ class SourcePatcher return $result; } + + /** + * Patch micro SAPI to support compressed phar loading from the current executable. + * + * @param int $version_id PHP version ID + */ + public static function patchMicroPhar(int $version_id): void + { + FileSystem::backupFile(SOURCE_PATH . '/php-src/ext/phar/phar.c'); + FileSystem::replaceFileStr( + SOURCE_PATH . '/php-src/ext/phar/phar.c', + 'static zend_op_array *phar_compile_file', + "char *micro_get_filename(void);\n\nstatic zend_op_array *phar_compile_file" + ); + if ($version_id < 80100) { + // PHP 8.0.x + FileSystem::replaceFileStr( + SOURCE_PATH . '/php-src/ext/phar/phar.c', + 'if (strstr(file_handle->filename, ".phar") && !strstr(file_handle->filename, "://")) {', + 'if ((strstr(file_handle->filename, micro_get_filename()) || strstr(file_handle->filename, ".phar")) && !strstr(file_handle->filename, "://")) {' + ); + } else { + // PHP >= 8.1 + FileSystem::replaceFileStr( + SOURCE_PATH . '/php-src/ext/phar/phar.c', + 'if (strstr(ZSTR_VAL(file_handle->filename), ".phar") && !strstr(ZSTR_VAL(file_handle->filename), "://")) {', + 'if ((strstr(ZSTR_VAL(file_handle->filename), micro_get_filename()) || strstr(ZSTR_VAL(file_handle->filename), ".phar")) && !strstr(ZSTR_VAL(file_handle->filename), "://")) {' + ); + } + } + + public static function unpatchMicroPhar(): void + { + FileSystem::restoreBackupFile(SOURCE_PATH . '/php-src/ext/phar/phar.c'); + } }