Add DirDiff utility and enhance package build process

- Introduced DirDiff class for tracking directory file changes.
- Updated ConsoleApplication to use addCommand for build targets.
- Enhanced PackageBuilder with methods for deploying binaries and extracting debug info.
- Improved package installation logic to support shared extensions.
- Added readline extension with patching for static builds.
This commit is contained in:
crazywhalecc 2025-12-04 10:53:49 +08:00
parent c38f174a6b
commit daa87e1350
No known key found for this signature in database
GPG Key ID: 1F4BDD59391F2680
12 changed files with 544 additions and 11 deletions

View File

@ -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

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Package\Extension;
use StaticPHP\Attribute\Package\AfterStage;
use StaticPHP\Attribute\Package\BeforeStage;
use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Toolchain\Interface\ToolchainInterface;
use StaticPHP\Util\SourcePatcher;
#[Extension('readline')]
class readline
{
#[BeforeStage('php', 'unix-make-cli')]
public function beforeMakeLinuxCli(PackageInstaller $installer, ToolchainInterface $toolchain): void
{
if ($toolchain->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);
}
}
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Package\Library;
use StaticPHP\Attribute\Package\AfterStage;
use StaticPHP\Attribute\Package\Library;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\FileSystem;
#[Library('imap')]
class imap
{
#[AfterStage('php', 'patch-embed-scripts')]
#[PatchDescription('Fix missing -lcrypt in php-config libs on glibc systems')]
public function afterPatchScripts(): void
{
if (SystemTarget::getLibc() === 'glibc') {
FileSystem::replaceFileRegex(BUILD_BIN_PATH . '/php-config', '/^libs="(.*)"$/m', 'libs="$1 -lcrypt"');
}
}
}

View File

@ -26,10 +26,12 @@ use StaticPHP\Package\TargetPackage;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Toolchain\Interface\ToolchainInterface;
use StaticPHP\Toolchain\ToolchainManager;
use StaticPHP\Util\DirDiff;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\InteractiveTerm;
use StaticPHP\Util\SourcePatcher;
use StaticPHP\Util\SPCConfigUtil;
use StaticPHP\Util\System\UnixUtil;
use StaticPHP\Util\V2CompatLayer;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
@ -312,11 +314,17 @@ class php
if ($installer->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-<release>.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)
));
}
}
}
}
}

View File

@ -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(),

View File

@ -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();

View File

@ -195,15 +195,20 @@ class PackageInstaller
/**
* Get all resolved packages.
* You can filter by package type class if needed.
*
* @return array<string, Package>
* @template T
* @param class-string<T> $package_type Filter by package type
* @return array<T>
*/
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]);
}

View File

@ -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;

View File

@ -107,4 +107,9 @@ class PhpExtensionPackage extends Package
{
return $this->build_with_php;
}
public function buildSharedExtension(): void
{
// TODO: build common shared extensions code here...
}
}

View File

@ -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)) {

View File

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Util;
/**
* A util class to diff directory file increments.
*/
class DirDiff
{
protected array $before = [];
protected array $before_file_hashes = [];
public function __construct(protected string $dir, protected bool $track_content_changes = false)
{
$this->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<string> 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<string> 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<string> 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);
}
}

View File

@ -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');
}
}