static-php-cli/src/SPC/builder/linux/LinuxBuilder.php

399 lines
17 KiB
PHP
Raw Normal View History

2023-03-21 00:25:46 +08:00
<?php
declare(strict_types=1);
namespace SPC\builder\linux;
2024-01-10 21:08:25 +08:00
use SPC\builder\unix\UnixBuilderBase;
2023-03-21 00:25:46 +08:00
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
2023-03-29 21:39:36 +08:00
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
2023-04-30 12:42:19 +08:00
use SPC\store\SourcePatcher;
2024-04-07 15:52:24 +08:00
use SPC\util\GlobalEnvManager;
2023-03-21 00:25:46 +08:00
2024-01-10 21:08:25 +08:00
class LinuxBuilder extends UnixBuilderBase
2023-03-21 00:25:46 +08:00
{
/** @var bool Micro patch phar flag */
2023-03-21 00:25:46 +08:00
private bool $phar_patched = false;
/**
* @throws FileSystemException
2023-04-15 18:45:34 +08:00
* @throws WrongUsageException
2023-03-21 00:25:46 +08:00
*/
public function __construct(array $options = [])
2023-03-21 00:25:46 +08:00
{
$this->options = $options;
2024-04-07 15:52:24 +08:00
// check musl-cross make installed if we use musl-cross-make
$arch = arch2gnu(php_uname('m'));
2024-04-07 15:52:24 +08:00
GlobalEnvManager::init($this);
if (getenv('SPC_LIBC') === 'musl' && !SystemUtil::isMuslDist()) {
$this->setOptionIfNotExist('library_path', "LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\"");
$this->setOptionIfNotExist('ld_library_path', "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\"");
$configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE');
$configure = "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\" " . $configure;
GlobalEnvManager::putenv("SPC_CMD_PREFIX_PHP_CONFIGURE={$configure}");
if (!file_exists("/usr/local/musl/{$arch}-linux-musl/lib/libc.a")) {
throw new WrongUsageException('You are building with musl-libc target in glibc distro, but musl-toolchain is not installed, please install musl-toolchain first. (You can use `doctor` command to install it)');
}
2024-04-07 16:26:21 +08:00
}
// concurrency
2024-04-07 15:52:24 +08:00
$this->concurrency = intval(getenv('SPC_CONCURRENCY'));
// cflags
2024-04-07 15:52:24 +08:00
$this->arch_c_flags = getenv('SPC_DEFAULT_C_FLAGS');
$this->arch_cxx_flags = getenv('SPC_DEFAULT_CXX_FLAGS');
2023-10-16 14:09:49 +02:00
// cross-compiling is not supported yet
/*if (php_uname('m') !== $this->arch) {
2023-03-21 00:25:46 +08:00
$this->cross_compile_prefix = SystemUtil::getCrossCompilePrefix($this->cc, $this->arch);
logger()->info('using cross compile prefix: ' . $this->cross_compile_prefix);
$this->configure_env .= " CROSS_COMPILE='{$this->cross_compile_prefix}'";
}*/
2023-03-21 00:25:46 +08:00
// create pkgconfig and include dir (some libs cannot create them automatically)
2023-03-21 00:25:46 +08:00
f_mkdir(BUILD_LIB_PATH . '/pkgconfig', recursive: true);
f_mkdir(BUILD_INCLUDE_PATH, recursive: true);
}
/**
* Build PHP from source.
*
* @param int $build_target Build target, use `BUILD_TARGET_*` constants
2023-03-21 00:25:46 +08:00
* @throws RuntimeException
* @throws FileSystemException
* @throws WrongUsageException
2023-03-21 00:25:46 +08:00
*/
public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
2023-03-21 00:25:46 +08:00
{
// ---------- Update extra-libs ----------
2024-04-07 15:52:24 +08:00
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
// bloat means force-load all static libraries, even if they are not used
if (!$this->getOption('bloat', false)) {
$extra_libs .= (empty($extra_libs) ? '' : ' ') . implode(' ', $this->getAllStaticLibFiles());
2023-03-21 00:25:46 +08:00
} else {
$extra_libs .= (empty($extra_libs) ? '' : ' ') . implode(' ', array_map(fn ($x) => "-Xcompiler {$x}", array_filter($this->getAllStaticLibFiles())));
2023-03-21 00:25:46 +08:00
}
2023-10-16 14:09:49 +02:00
// add libstdc++, some extensions or libraries need it
$extra_libs .= (empty($extra_libs) ? '' : ' ') . ($this->hasCpp() ? '-lstdc++ ' : '');
2024-04-07 15:52:24 +08:00
f_putenv('SPC_EXTRA_LIBS=' . $extra_libs);
2023-03-21 00:25:46 +08:00
$cflags = $this->arch_c_flags;
f_putenv('CFLAGS=' . $cflags);
2023-03-21 00:25:46 +08:00
2024-01-03 15:57:05 +08:00
$this->emitPatchPoint('before-php-buildconf');
SourcePatcher::patchBeforeBuildconf($this);
2023-03-21 00:25:46 +08:00
2024-04-07 15:52:24 +08:00
shell()->cd(SOURCE_PATH . '/php-src')->exec(getenv('SPC_CMD_PREFIX_PHP_BUILDCONF'));
2023-03-21 00:25:46 +08:00
2024-01-03 15:57:05 +08:00
$this->emitPatchPoint('before-php-configure');
SourcePatcher::patchBeforeConfigure($this);
2023-08-21 12:54:36 +02:00
$phpVersionID = $this->getPHPVersionID();
$json_74 = $phpVersionID < 80000 ? '--enable-json ' : '';
2023-08-21 18:37:00 +02:00
if ($this->getOption('enable-zts', false)) {
$maxExecutionTimers = $phpVersionID >= 80100 ? '--enable-zend-max-execution-timers ' : '';
$zts = '--enable-zts --disable-zend-signals ';
} else {
$maxExecutionTimers = '';
$zts = '';
}
$disable_jit = $this->getOption('disable-opcache-jit', false) ? '--disable-opcache-jit ' : '';
2023-03-21 00:25:46 +08:00
$config_file_path = $this->getOption('with-config-file-path', false) ?
('--with-config-file-path=' . $this->getOption('with-config-file-path') . ' ') : '';
$config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ?
('--with-config-file-scan-dir=' . $this->getOption('with-config-file-scan-dir') . ' ') : '';
2025-06-18 11:20:05 +07:00
$enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
2025-03-20 07:27:38 +01:00
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
2024-04-07 15:52:24 +08:00
// prepare build php envs
$envs_build_php = SystemUtil::makeEnvVarString([
'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'),
'CPPFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS'),
'LDFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS'),
2025-03-20 07:27:38 +01:00
'LIBS' => $mimallocLibs . getenv('SPC_CMD_VAR_PHP_CONFIGURE_LIBS'),
2024-04-07 15:52:24 +08:00
]);
// process micro upx patch if micro sapi enabled
2025-06-18 11:20:05 +07:00
if ($enableMicro) {
if (version_compare($this->getMicroVersion(), '0.2.0') < 0) {
// for phpmicro 0.1.x
$this->processMicroUPXLegacy();
}
// micro latest needs do strip and upx pack later (strip, upx, cut binary manually supported)
2024-02-19 12:17:03 +08:00
}
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
shell()->cd(SOURCE_PATH . '/php-src')
->exec(
2024-04-07 15:52:24 +08:00
getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
2025-06-18 11:20:05 +07:00
($enableCli ? '--enable-cli ' : '--disable-cli ') .
($enableFpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') .
($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
$config_file_path .
$config_file_scan_dir .
$disable_jit .
2023-05-04 11:29:14 +08:00
$json_74 .
$zts .
2023-08-21 12:54:36 +02:00
$maxExecutionTimers .
$this->makeStaticExtensionArgs() .
Feature perfect swoole extension config (#297) * improve swoole static build config * improve swoole static build config * improve swoole static build config * improve swoole static build config * improve swoole static build config * add cares config * update swoole depend config * update swoole depend config * update cares build config * update workflow tests.yaml config * fix setup-runtime * test with clang build * test with clang build * update cares build config * test * test * test * test * test * test * test * test * test * test * test * test * test * test * update cares license * test build * test build * test build * test build * test add enable libpq * test add enable libpq * test add enable libpq * test add enable libpq * test add enable libpq * test add enable libpq * test add enable libpq * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * update * update * update * update * update * update * update * update * update * compatible old * fix code format * fix code format * add swoole test case * add swoole test case * add phpstan ignore error * add phpstan ignore error * add phpstan ignore error * add phpstan ignore error * add phpstan ignore error * update phpstan.neon * update swoole extension test case * update swoole test case * adjust config order and depends * revert LinuxBuilder * remove swoole.phpt * re-adjust swoole args * update test-extensions and some PHPDoc * revert: debian and alpine clang doctor install * revert: MacOSBuilder * fix: extract hook for archive not working * revert: build tests * use addon mode to swoole database hook * add hook tests * test minimal * test minimal * sort config --------- Co-authored-by: crazywhalecc <jesse2061@outlook.com>
2024-01-03 10:31:21 +08:00
' ' . $envs_build_php . ' '
);
2023-03-21 00:25:46 +08:00
2024-01-03 15:57:05 +08:00
$this->emitPatchPoint('before-php-make');
SourcePatcher::patchBeforeMake($this);
2023-04-30 12:42:19 +08:00
$this->cleanMake();
2023-03-21 00:25:46 +08:00
2025-06-18 11:20:05 +07:00
if ($enableCli) {
2023-04-23 20:31:58 +08:00
logger()->info('building cli');
2023-10-30 22:14:47 +01:00
$this->buildCli();
2023-04-23 20:31:58 +08:00
}
2025-06-18 11:20:05 +07:00
if ($enableFpm) {
2023-04-23 20:31:58 +08:00
logger()->info('building fpm');
2023-10-30 22:14:47 +01:00
$this->buildFpm();
2023-04-23 20:31:58 +08:00
}
2025-06-18 11:20:05 +07:00
if ($enableMicro) {
2023-04-23 20:31:58 +08:00
logger()->info('building micro');
2023-10-30 22:14:47 +01:00
$this->buildMicro();
2023-03-21 00:25:46 +08:00
}
2025-06-18 11:20:05 +07:00
if ($enableEmbed) {
logger()->info('building embed');
2025-06-18 11:20:05 +07:00
if ($enableMicro) {
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la');
}
2023-10-30 22:14:47 +01:00
$this->buildEmbed();
}
2025-06-18 11:20:05 +07:00
if ($enableFrankenphp) {
2025-06-18 10:48:09 +07:00
logger()->info('building frankenphp');
$this->buildFrankenphp();
}
2025-05-21 18:35:48 +07:00
}
2023-03-21 00:25:46 +08:00
2025-05-21 18:35:48 +07:00
public function testPHP(int $build_target = BUILD_TARGET_NONE)
{
$this->emitPatchPoint('before-sanity-check');
$this->sanityCheck($build_target);
2023-03-21 00:25:46 +08:00
}
/**
* Build cli sapi
*
2023-03-21 00:25:46 +08:00
* @throws RuntimeException
* @throws FileSystemException
2023-03-21 00:25:46 +08:00
*/
protected function buildCli(): void
2023-03-21 00:25:46 +08:00
{
2024-04-07 15:52:24 +08:00
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
2025-05-25 10:47:32 +07:00
->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} cli");
2024-02-19 15:29:43 +08:00
if ($this->getOption('with-upx-pack')) {
2024-02-19 12:17:03 +08:00
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')
->exec('strip --strip-all php')
2024-04-07 15:52:24 +08:00
->exec(getenv('UPX_EXEC') . ' --best php');
2024-02-19 15:29:43 +08:00
} elseif (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-all php');
}
2023-04-23 20:31:58 +08:00
$this->deployBinary(BUILD_TARGET_CLI);
2023-03-21 00:25:46 +08:00
}
/**
* Build phpmicro sapi
*
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
2023-03-21 00:25:46 +08:00
*/
protected function buildMicro(): void
2023-03-21 00:25:46 +08:00
{
2023-04-03 20:47:24 +08:00
if ($this->getPHPVersionID() < 80000) {
throw new WrongUsageException('phpmicro only support PHP >= 8.0!');
2023-04-03 20:47:24 +08:00
}
2023-03-21 00:25:46 +08:00
if ($this->getExt('phar')) {
$this->phar_patched = true;
SourcePatcher::patchMicroPhar($this->getPHPVersionID());
2023-03-21 00:25:46 +08:00
}
2024-04-07 15:52:24 +08:00
$enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : '';
$vars = $this->getMakeExtraVars();
// patch fake cli for micro
$vars['EXTRA_CFLAGS'] .= $enable_fake_cli;
$vars = SystemUtil::makeEnvVarString($vars);
$SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
2024-04-07 15:52:24 +08:00
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
2025-05-25 10:47:32 +07:00
->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} micro");
2023-03-21 00:25:46 +08:00
$this->processMicroUPX();
2023-04-23 20:31:58 +08:00
$this->deployBinary(BUILD_TARGET_MICRO);
if ($this->phar_patched) {
SourcePatcher::unpatchMicroPhar();
}
2023-04-23 20:31:58 +08:00
}
/**
* Build fpm sapi
*
* @throws FileSystemException
* @throws RuntimeException
2023-04-23 20:31:58 +08:00
*/
protected function buildFpm(): void
2023-04-23 20:31:58 +08:00
{
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
2023-04-23 20:31:58 +08:00
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
2025-05-25 10:47:32 +07:00
->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} fpm");
2024-02-19 15:29:43 +08:00
if ($this->getOption('with-upx-pack')) {
2024-02-19 12:17:03 +08:00
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')
->exec('strip --strip-all php-fpm')
2024-04-07 15:52:24 +08:00
->exec(getenv('UPX_EXEC') . ' --best php-fpm');
2024-02-19 15:29:43 +08:00
} elseif (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-all php-fpm');
}
2023-04-23 20:31:58 +08:00
$this->deployBinary(BUILD_TARGET_FPM);
2023-03-21 00:25:46 +08:00
}
/**
* Build embed sapi
*
* @throws RuntimeException
*/
protected function buildEmbed(): void
{
2024-04-07 15:52:24 +08:00
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
2024-04-07 15:52:24 +08:00
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
2025-05-21 13:19:51 +07:00
->exec('sed -i "s|^EXTENSION_DIR = .*|EXTENSION_DIR = /' . basename(BUILD_MODULES_PATH) . '|" Makefile')
2024-04-07 15:52:24 +08:00
->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install");
$ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS');
if (preg_match('/-release\s+(\S+)/', $ldflags, $matches)) {
$release = $matches[1];
$realLibName = 'libphp-' . $release . '.so';
$realLib = BUILD_LIB_PATH . '/' . $realLibName;
rename(BUILD_LIB_PATH . '/libphp.so', $realLib);
$cwd = getcwd();
chdir(BUILD_LIB_PATH);
symlink($realLibName, 'libphp.so');
chdir(BUILD_MODULES_PATH);
foreach ($this->getExts() as $ext) {
if (!$ext->isBuildShared()) {
continue;
}
$name = $ext->getName();
$versioned = "{$name}-{$release}.so";
$unversioned = "{$name}.so";
if (is_file(BUILD_MODULES_PATH . "/{$versioned}")) {
rename(BUILD_MODULES_PATH . "/{$versioned}", BUILD_MODULES_PATH . "/{$unversioned}");
shell()->cd(BUILD_MODULES_PATH)
->exec(sprintf(
'patchelf --set-soname %s %s',
escapeshellarg($unversioned),
escapeshellarg($unversioned)
));
}
}
chdir($cwd);
}
$this->patchPhpScripts();
}
2024-04-07 15:52:24 +08:00
private function getMakeExtraVars(): array
2023-10-30 22:14:47 +01:00
{
return [
2024-04-07 15:52:24 +08:00
'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'),
2024-04-07 16:26:21 +08:00
'EXTRA_LIBS' => getenv('SPC_EXTRA_LIBS') . ' ' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS'),
'EXTRA_LDFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'),
2024-04-07 15:52:24 +08:00
'EXTRA_LDFLAGS_PROGRAM' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM'),
2023-10-30 22:14:47 +01:00
];
}
/**
* Apply option --no-strip and --with-upx-pack for micro sapi (only for phpmicro 0.1.x)
*
* @throws FileSystemException
*/
private function processMicroUPXLegacy(): void
{
// upx pack and strip for micro
// but always restore Makefile.frag.bak first
if (file_exists(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag.bak')) {
copy(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag.bak', SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag');
}
if ($this->getOption('with-upx-pack', false)) {
// judge $(MAKE) micro_2s_objs SFX_FILESIZE=`$(STAT_SIZE) $(SAPI_MICRO_PATH)` count
// if 2, replace src/globals/extra/micro-triple-Makefile.frag file content
if (substr_count(FileSystem::readFile(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag'), '$(MAKE) micro_2s_objs SFX_FILESIZE=`$(STAT_SIZE) $(SAPI_MICRO_PATH)`') === 2) {
// bak first
copy(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag', SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag.bak');
// replace Makefile.frag content
FileSystem::writeFile(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag', FileSystem::readFile(ROOT_DIR . '/src/globals/extra/micro-triple-Makefile.frag'));
}
// with upx pack always need strip
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
'/POST_MICRO_BUILD_COMMANDS=.*/',
'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH) && ' . getenv('UPX_EXEC') . ' --best \$(SAPI_MICRO_PATH)',
);
} elseif (!$this->getOption('no-strip', false)) {
// not-no-strip means strip (default behavior)
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
'/POST_MICRO_BUILD_COMMANDS=.*/',
'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH)',
);
} else {
// just no strip
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
'/POST_MICRO_BUILD_COMMANDS=.*/',
'POST_MICRO_BUILD_COMMANDS=true',
);
}
}
private function processMicroUPX(): void
{
if (version_compare($this->getMicroVersion(), '0.2.0') >= 0 && !$this->getOption('no-strip', false)) {
shell()->exec('strip --strip-all ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx');
if ($this->getOption('with-upx-pack')) {
// strip first
shell()->exec(getenv('UPX_EXEC') . ' --best ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx');
// cut binary with readelf
[$ret, $out] = shell()->execWithResult('readelf -l ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx | awk \'/LOAD|GNU_STACK/ {getline; print $1, $2, $3, $4, $6, $7}\'');
$out[1] = explode(' ', $out[1]);
$offset = $out[1][0];
if ($ret !== 0 || !str_starts_with($offset, '0x')) {
throw new RuntimeException('Cannot find offset in readelf output');
}
$offset = hexdec($offset);
// remove upx extra wastes
file_put_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx', substr(file_get_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx'), 0, $offset));
}
}
}
2023-03-21 00:25:46 +08:00
}