Compare commits

..

34 Commits

Author SHA1 Message Date
Joseph Bielawski
be32190829 Sanity check extensions by inline their test code 2023-09-13 23:42:10 +08:00
crazywhalecc
f1d5916090 remove unused old deploy command 2023-09-13 21:45:19 +08:00
Joseph Bielawski
b2ea479fac Remove the phar workflow temporary 2023-09-13 21:45:19 +08:00
Joseph Bielawski
16cc5df66d Temporary disable phar build in GH actions 2023-09-13 21:45:19 +08:00
Joseph Bielawski
a63e3f4575 Add version into phar 2023-09-13 21:45:19 +08:00
Joseph Bielawski
6b061e6332 Adjust box configuration for smaller phar size 2023-09-13 21:45:19 +08:00
Joseph Bielawski
4a17491aaa Initial code to get Box for phar building 2023-09-13 21:45:19 +08:00
Jerry Ma
06f29712e2 Update README.md 2023-09-13 10:03:31 +08:00
Kévin Dunglas
4b1d59c5a9 Link to FrankenPHP docs about embed SAPI in README-en.md 2023-09-13 10:03:31 +08:00
crazywhalecc
1a81fe6a0d change help message for dev:extensions 2023-09-12 23:30:23 +08:00
crazywhalecc
c63136d484 change dev:extensions argument to comma separated 2023-09-12 23:30:23 +08:00
crazywhalecc
4b500f2dd8 remove redundant libssh2 cmake build target option 2023-09-12 23:09:58 +08:00
crazywhalecc
9da20497cc fix ln src and dst for brotli 2023-09-12 22:53:47 +08:00
crazywhalecc
5efc15b404 fix libwebp pkgconfig patch, add custom patch argument 2023-09-12 22:52:20 +08:00
Joseph Bielawski
fa0740f216 Explicitly define application commands 2023-09-12 22:51:50 +08:00
Joseph Bielawski
52430cbdde Rework doctor command 2023-09-12 22:46:44 +08:00
crazywhalecc
01c4538ce0 update README for embed SAPI 2023-09-12 22:44:19 +08:00
Kévin Dunglas
3183ecceaf --disable-opcache-jit 2023-09-12 22:30:13 +08:00
Kévin Dunglas
dd2e7cc129 static opcache patch 2023-09-12 22:30:13 +08:00
Kévin Dunglas
adfa620ef4 Revert "always enable micro"
This reverts commit eb8cefd9ca.
2023-09-12 22:30:13 +08:00
Kévin Dunglas
1a32ddc70a Revert "temporary workaround for PECL certificate expiration"
This reverts commit 7c78003c9f.
2023-09-12 22:30:13 +08:00
Kévin Dunglas
4bfca6fe93 always enable micro 2023-09-12 22:30:13 +08:00
Kévin Dunglas
dbec043894 temporary workaround for PECL certificate expiration 2023-09-12 22:30:13 +08:00
Kévin Dunglas
727d78a689 Revert "fix: replace down URL"
This reverts commit 6eadbca21bf9a3b57058160a2f7ea8f4cc5f21dd.
2023-09-12 22:30:13 +08:00
Kévin Dunglas
9b1e784604 fix: replace down URL 2023-09-12 22:30:13 +08:00
Kévin Dunglas
50d44d8310 explicitly disable unneeded SAPIs 2023-09-12 22:30:13 +08:00
Kévin Dunglas
3b300698f3 use lixml GH mirror because gitlab.gnome.org is down 2023-09-12 22:30:13 +08:00
Kévin Dunglas
899eb94b8b remove useless ls 2023-09-12 22:30:13 +08:00
Kévin Dunglas
451a0c0e34 workaround for macOS 2023-09-12 22:30:13 +08:00
Kévin Dunglas
085c1a159c feat: add support for libphp and the embed SAPI 2023-09-12 22:30:13 +08:00
Joseph Bielawski
ca3f8a350d Prevent fatal error when php source files are missing 2023-09-12 18:19:32 +08:00
crazywhalecc
5025850f71 fix Alpine docker install packages 2023-09-12 12:45:51 +08:00
crazywhalecc
fe2f658e08 dev:sort-config add \n at the end of json 2023-09-12 00:24:01 +08:00
crazywhalecc
00a49c662b add build:libs option --rebuild 2023-09-11 23:47:02 +08:00
26 changed files with 310 additions and 324 deletions

View File

@@ -2,7 +2,7 @@
Build single static PHP binary, with PHP project together, with popular extensions included.
The project name is static-php-cli, but it actually supports cli, fpm, micro and embed (on the way) SAPI 😎
The project name is static-php-cli, but it actually supports cli, fpm, micro and embed SAPI 😎
Compile a purely static php-cli binary file with various extensions to make PHP applications more portable! (cli SAPI)
@@ -126,6 +126,7 @@ Now we support `cli`, `micro`, `fpm`, you can use one or more of the following p
- `--build-cli`: build static cli executable
- `--build-micro`: build static phpmicro self-extracted executable
- `--build-fpm`: build static fpm binary
- `--build-embed`: build embed (libphp)
- `--build-all`: build all
If anything goes wrong, use `--debug` option to display full terminal output:
@@ -194,9 +195,20 @@ Because php-fpm must specify a configuration file before running, the php-fpm co
Specifying `php-fpm.conf` can use the command parameter `-y`, for example: `./php-fpm -y php-fpm.conf`.
### Embed Usage
When using the project parameters `--build-embed` or `--build-all`,
the final compilation result will output a `libphp.a`, `php-config` and a series of header files,
stored in `buildroot/`. You can introduce them in your other projects.
If you know [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed), you should know how to use it.
You may require the introduction of other libraries during compilation,
you can use `buildroot/bin/php-config` to obtain the compile-time configuration.
For an advanced example of how to use this feature, take a look at [how to use it to build a static version of FrankenPHP](https://github.com/dunglas/frankenphp/blob/main/docs/static.md).
## Contribution
Currently, there are only a few supported extensions.
If the extension you need is missing, you can create an issue.
If you are familiar with this project, you are also welcome to initiate a pull request.

View File

@@ -19,7 +19,7 @@ Build single static PHP binary, with PHP project together, with popular extensio
[![](https://img.shields.io/badge/Extension%20Counter-55+-yellow.svg?style=flat-square)]()
[![](https://img.shields.io/github/search/crazywhalecc/static-php-cli/TODO?label=TODO%20Counter&style=flat-square)]()
> 项目名称是 static-php-cli但其实支持 cli、fpm、micro 和 embed(正在路上)SAPI 😎
> 项目名称是 static-php-cli但其实支持 cli、fpm、micro 和 embed SAPI 😎
## 编译环境需求
@@ -120,6 +120,7 @@ chmod +x bin/spc
- `--build-cli`:构建 cli 二进制
- `--build-micro`:构建 phpmicro 自执行二进制
- `--build-fpm`:构建 fpm
- `--build-embed`:构建 embedlibphp
- `--build-all`:构建所有
如果出现了任何错误,可以使用 `--debug` 参数来展示完整的输出日志,以供排查错误:
@@ -181,9 +182,17 @@ cat micro.sfx code.php > single-app && chmod +x single-app
指定 `php-fpm.conf` 可以使用命令参数 `-y`,例如:`./php-fpm -y php-fpm.conf`
### 使用 php-embed
采用项目参数 `--build-embed``--build-all` 时,最后编译结果会输出一个 `libphp.a``php-config` 以及一系列头文件,存放在 `buildroot/`,你可以在你的其他代码中引入它们。
如果你知道 [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed),你应该知道如何使用它。对于有可能编译用到引入其他库的问题,你可以使用 `buildroot/bin/php-config` 来获取编译时的配置。
另外,有关如何使用此功能的高级示例,请查看[如何使用它构建 FrankenPHP 的静态版本](https://github.com/dunglas/frankenphp/blob/main/docs/static.md)。
## 贡献
目前支持的扩展较少,如果缺少你需要的扩展,可发起 Issue。如果你对本项目较熟悉也欢迎为本项目发起 Pull Request。
如果缺少你需要的扩展,可发起 Issue。如果你对本项目较熟悉也欢迎为本项目发起 Pull Request。
贡献基本原则如下:
@@ -202,7 +211,7 @@ cat micro.sfx code.php > single-app && chmod +x single-app
## 开源协议
本项目依据旧版本惯例采用 MIT License 开源,自身的部分代码引用或修改自以下项目:
本项目依据旧版本惯例采用 MIT License 开源,部分扩展的集成编译命令参考或修改自以下项目:
- [dixyes/lwmbs](https://github.com/dixyes/lwmbs)(木兰宽松许可证)
- [swoole/swoole-cli](https://github.com/swoole/swoole-cli)Apache 2.0 LICENSE、SWOOLE-CLI LICENSE

View File

@@ -64,7 +64,6 @@ RUN apk update; \
bison \
build-base \
cmake \
composer \
curl \
file \
flex \
@@ -77,16 +76,20 @@ RUN apk update; \
linux-headers \
m4 \
make \
php81 \
php81-common \
php81-pcntl \
php81-phar \
php81-posix \
php81-tokenizer \
php81-xml \
php82 \
php82-common \
php82-pcntl \
php82-phar \
php82-posix \
php82-tokenizer \
php82-dom \
php82-xml \
php82-xmlwriter \
composer \
pkgconfig \
wget \
xz
RUN ln -s /usr/bin/php82 /usr/bin/php
WORKDIR /app
ADD ./src /app/src
ADD ./composer.json /app/composer.json

18
box.json Normal file
View File

@@ -0,0 +1,18 @@
{
"alias": "spc-php.phar",
"banner": false,
"blacklist": [
".github"
],
"directories": [
"config",
"src",
"vendor/psr",
"vendor/laravel/prompts",
"vendor/symfony",
"vendor/zhamao"
],
"git-commit-short": "git_commit_short",
"metadata": "ConsoleApplication::VERSION",
"output": "spc.phar"
}

View File

@@ -20,6 +20,7 @@
"captainhook/captainhook": "^5.10",
"captainhook/plugin-composer": "^5.3",
"friendsofphp/php-cs-fixer": "^3.25",
"humbug/box": "^4.3",
"nunomaduro/collision": "^7.8",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.3"
@@ -44,7 +45,8 @@
"scripts": {
"analyse": "phpstan analyse --memory-limit 300M",
"cs-fix": "php-cs-fixer fix",
"test": "vendor/bin/phpunit tests/ --no-coverage"
"test": "vendor/bin/phpunit tests/ --no-coverage",
"build:phar": "vendor/bin/box compile"
},
"config": {
"allow-plugins": {

View File

@@ -249,7 +249,7 @@
},
"libxml2": {
"type": "url",
"url": "https://gitlab.gnome.org/GNOME/libxml2/-/archive/v2.9.14/libxml2-v2.9.14.tar.gz",
"url": "https://github.com/GNOME/libxml2/archive/refs/tags/v2.9.14.tar.gz",
"license": {
"type": "file",
"path": "Copyright"

View File

@@ -4,7 +4,16 @@ declare(strict_types=1);
namespace SPC;
use SPC\store\FileSystem;
use SPC\command\BuildCliCommand;
use SPC\command\BuildLibsCommand;
use SPC\command\dev\AllExtCommand;
use SPC\command\dev\PhpVerCommand;
use SPC\command\dev\SortConfigCommand;
use SPC\command\DoctorCommand;
use SPC\command\DownloadCommand;
use SPC\command\DumpLicenseCommand;
use SPC\command\ExtractCommand;
use SPC\command\MicroCombineCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
@@ -12,14 +21,10 @@ use Symfony\Component\Console\Command\ListCommand;
/**
* static-php-cli console app entry
*/
class ConsoleApplication extends Application
final class ConsoleApplication extends Application
{
public const VERSION = '2.0-rc6';
/**
* @throws \ReflectionException
* @throws exception\FileSystemException
*/
public function __construct()
{
parent::__construct('static-php-cli', self::VERSION);
@@ -29,21 +34,22 @@ class ConsoleApplication extends Application
// Detailed debugging errors are not displayed in the production environment. Only the error display provided by Symfony console is used.
$this->setCatchExceptions(file_exists(ROOT_DIR . '/.prod') || !in_array('--debug', $argv));
// Add subcommands by scanning the directory src/static-php-cli/command/
$commands = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/command', 'SPC\\command');
$phar = class_exists('\\Phar') && \Phar::running() || !class_exists('\\Phar');
$commands = array_filter($commands, function ($y) use ($phar) {
$archive_blacklist = [
'SPC\command\dev\SortConfigCommand',
'SPC\command\DeployCommand',
];
if ($phar && in_array($y, $archive_blacklist)) {
return false;
}
$reflection = new \ReflectionClass($y);
return !$reflection->isAbstract() && !$reflection->isInterface();
});
$this->addCommands(array_map(function ($x) { return new $x(); }, $commands));
$this->addCommands(
[
new BuildCliCommand(),
new BuildLibsCommand(),
new DoctorCommand(),
new DownloadCommand(),
new DumpLicenseCommand(),
new ExtractCommand(),
new MicroCombineCommand(),
// Dev commands
new AllExtCommand(),
new PhpVerCommand(),
new SortConfigCommand(),
]
);
}
protected function getDefaultCommands(): array

View File

@@ -85,7 +85,7 @@ abstract class BuilderBase
// build all libs
foreach ($this->libs as $lib) {
match ($lib->tryBuild()) {
match ($lib->tryBuild($this->getOption('rebuild', false))) {
BUILD_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] build success'),
BUILD_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'),
BUILD_STATUS_FAILED => logger()->error('lib [' . $lib::NAME . '] build failed'),
@@ -252,6 +252,9 @@ abstract class BuilderBase
if (($type & BUILD_TARGET_FPM) === BUILD_TARGET_FPM) {
$ls[] = 'fpm';
}
if (($type & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED) {
$ls[] = 'embed';
}
return implode(', ', $ls);
}

View File

@@ -179,6 +179,11 @@ class LinuxBuilder extends BuilderBase
$zts = '';
}
$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;
shell()->cd(SOURCE_PATH . '/php-src')
->exec(
'./configure ' .
@@ -189,12 +194,13 @@ class LinuxBuilder extends BuilderBase
'--disable-all ' .
'--disable-cgi ' .
'--disable-phpdbg ' .
'--enable-cli ' .
'--enable-fpm ' .
($enableCli ? '--enable-cli ' : '--disable-cli ') .
($enableFpm ? '--enable-fpm ' : '--disable-fpm ') .
($enableEmbed ? '--enable-embed=static --disable-opcache-jit ' : '--disable-embed ') .
$json_74 .
$zts .
$maxExecutionTimers .
'--enable-micro=all-static ' .
($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
$this->makeExtensionArgs() . ' ' .
$envs
);
@@ -203,18 +209,22 @@ class LinuxBuilder extends BuilderBase
$this->cleanMake();
if (($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
if ($enableCli) {
logger()->info('building cli');
$this->buildCli($extra_libs, $use_lld);
}
if (($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM) {
if ($enableFpm) {
logger()->info('building fpm');
$this->buildFpm($extra_libs, $use_lld);
}
if (($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
if ($enableMicro) {
logger()->info('building micro');
$this->buildMicro($extra_libs, $use_lld, $cflags);
}
if ($enableEmbed) {
logger()->info('building embed');
$this->buildEmbed($extra_libs, $use_lld);
}
if (php_uname('m') === $this->getOption('arch')) {
$this->sanityCheck($build_target);
@@ -306,4 +316,18 @@ class LinuxBuilder extends BuilderBase
$this->deployBinary(BUILD_TARGET_FPM);
}
public function buildEmbed(string $extra_libs, string $use_lld): void
{
$vars = SystemUtil::makeEnvVarString([
'EXTRA_CFLAGS' => '-g -Os -fno-ident ' . implode(' ', array_map(fn ($x) => "-Xcompiler {$x}", $this->tune_c_flags)),
'EXTRA_LIBS' => $extra_libs,
'EXTRA_LDFLAGS_PROGRAM' => "{$use_lld} -all-static",
]);
shell()
->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec('make INSTALL_ROOT=' . BUILD_ROOT_PATH . " -j{$this->concurrency} {$vars} install");
}
}

View File

@@ -151,6 +151,11 @@ class MacOSBuilder extends BuilderBase
$json_74 = $this->getPHPVersionID() < 80000 ? '--enable-json ' : '';
$zts = $this->getOption('enable-zts', false) ? '--enable-zts --disable-zend-signals ' : '';
$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;
shell()->cd(SOURCE_PATH . '/php-src')
->exec(
'./configure ' .
@@ -162,9 +167,10 @@ class MacOSBuilder extends BuilderBase
'--disable-all ' .
'--disable-cgi ' .
'--disable-phpdbg ' .
'--enable-cli ' .
'--enable-fpm ' .
'--enable-micro ' .
($enableCli ? '--enable-cli ' : '--disable-cli ') .
($enableFpm ? '--enable-fpm ' : '--disable-fpm ') .
($enableEmbed ? '--enable-embed=static ' : '--disable-embed ') .
($enableMicro ? '--enable-micro ' : '--disable-micro ') .
$json_74 .
$zts .
$this->makeExtensionArgs() . ' ' .
@@ -175,18 +181,22 @@ class MacOSBuilder extends BuilderBase
$this->cleanMake();
if (($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
if ($enableCli) {
logger()->info('building cli');
$this->buildCli();
}
if (($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM) {
if ($enableFpm) {
logger()->info('building fpm');
$this->buildFpm();
}
if (($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
if ($enableMicro) {
logger()->info('building micro');
$this->buildMicro();
}
if ($enableEmbed) {
logger()->info('building embed');
$this->buildEmbed();
}
if (php_uname('m') === $this->getOption('arch')) {
$this->sanityCheck($build_target);
@@ -207,7 +217,7 @@ class MacOSBuilder extends BuilderBase
{
$vars = SystemUtil::makeEnvVarString([
'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size
'EXTRA_LIBS' => "{$this->getOption('extra-libs')} -lresolv", // link resolv library (macOS need it)
'EXTRA_LIBS' => "{$this->getOption('extra-libs')} -lresolv", // link resolv library (macOS needs it)
]);
$shell = shell()->cd(SOURCE_PATH . '/php-src');
@@ -237,7 +247,7 @@ class MacOSBuilder extends BuilderBase
$vars = [
// with debug information, optimize for size, remove identifiers, patch fake cli for micro
'EXTRA_CFLAGS' => '-g -Os -fno-ident' . $enable_fake_cli,
// link resolv library (macOS need it)
// link resolv library (macOS needs it)
'EXTRA_LIBS' => "{$this->getOption('extra-libs')} -lresolv",
];
if (!$this->getOption('no-strip', false)) {
@@ -260,7 +270,7 @@ class MacOSBuilder extends BuilderBase
{
$vars = SystemUtil::makeEnvVarString([
'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size
'EXTRA_LIBS' => "{$this->getOption('extra-libs')} -lresolv", // link resolv library (macOS need it)
'EXTRA_LIBS' => "{$this->getOption('extra-libs')} -lresolv", // link resolv library (macOS needs it)
]);
$shell = shell()->cd(SOURCE_PATH . '/php-src');
@@ -270,4 +280,24 @@ class MacOSBuilder extends BuilderBase
}
$this->deployBinary(BUILD_TARGET_FPM);
}
public function buildEmbed(): void
{
$vars = SystemUtil::makeEnvVarString([
'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size
'EXTRA_LIBS' => "{$this->getOption('extra-libs')} -lresolv", // link resolv library (macOS needs it)
]);
shell()
->cd(SOURCE_PATH . '/php-src')
->exec('make INSTALL_ROOT=' . BUILD_ROOT_PATH . " -j{$this->concurrency} {$vars} install")
// Workaround for https://github.com/php/php-src/issues/12082
->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o')
->exec('mkdir ' . BUILD_ROOT_PATH . '/lib/php-o')
->cd(BUILD_ROOT_PATH . '/lib/php-o')
->exec('ar x ' . BUILD_ROOT_PATH . '/lib/libphp.a')
->exec('rm ' . BUILD_ROOT_PATH . '/lib/libphp.a')
->exec('ar rcs ' . BUILD_ROOT_PATH . '/lib/libphp.a *.o')
->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o');
}
}

View File

@@ -66,14 +66,23 @@ trait UnixBuilderTrait
if ($ret !== 0 || trim(implode('', $output)) !== 'hello') {
throw new RuntimeException('cli failed sanity check');
}
foreach ($this->exts as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "' . $ext->getDistName() . '"', false);
if ($ret !== 0) {
throw new RuntimeException('extension ' . $ext->getName() . ' failed compile check');
}
if (file_exists(ROOT_DIR . '/src/globals/tests/' . $ext->getName() . '.php')) {
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php ' . ROOT_DIR . '/src/globals/tests/' . $ext->getName() . '.php');
// Trim additional content & escape special characters to allow inline usage
$test = str_replace(
['<?php', 'declare(strict_types=1);', "\n", '"', '$'],
['', '', '', '\"', '\$'],
file_get_contents(ROOT_DIR . '/src/globals/tests/' . $ext->getName() . '.php')
);
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "' . trim($test) . '"');
if ($ret !== 0) {
throw new RuntimeException('extension ' . $ext->getName() . ' failed sanity check');
}

View File

@@ -66,7 +66,7 @@ trait UnixLibraryTrait
* @throws FileSystemException
* @throws RuntimeException
*/
public function patchPkgconfPrefix(array $files, int $patch_option = PKGCONF_PATCH_ALL): void
public function patchPkgconfPrefix(array $files, int $patch_option = PKGCONF_PATCH_ALL, ?array $custom_replace = null): void
{
logger()->info('Patching library [' . static::NAME . '] pkgconfig');
foreach ($files as $name) {
@@ -81,6 +81,7 @@ trait UnixLibraryTrait
$file = ($patch_option & PKGCONF_PATCH_EXEC_PREFIX) === PKGCONF_PATCH_EXEC_PREFIX ? preg_replace('/^exec_prefix=.*$/m', 'exec_prefix=${prefix}', $file) : $file;
$file = ($patch_option & PKGCONF_PATCH_LIBDIR) === PKGCONF_PATCH_LIBDIR ? preg_replace('/^libdir=.*$/m', 'libdir=${prefix}/lib', $file) : $file;
$file = ($patch_option & PKGCONF_PATCH_INCLUDEDIR) === PKGCONF_PATCH_INCLUDEDIR ? preg_replace('/^includedir=.*$/m', 'includedir=${prefix}/include', $file) : $file;
$file = ($patch_option & PKGCONF_PATCH_CUSTOM) === PKGCONF_PATCH_CUSTOM && $custom_replace !== null ? preg_replace($custom_replace[0], $custom_replace[1], $file) : $file;
FileSystem::writeFile($realpath, $file);
}
}

View File

@@ -28,9 +28,9 @@ trait brotli
->exec('make install DESTDIR=' . BUILD_ROOT_PATH);
$this->patchPkgconfPrefix(['libbrotlicommon.pc', 'libbrotlidec.pc', 'libbrotlienc.pc']);
shell()->cd(BUILD_ROOT_PATH . '/lib')
->exec('ln -s libbrotlicommon-static.a libbrotlicommon.a')
->exec('ln -s libbrotlidec-static.a libbrotlidec.a')
->exec('ln -s libbrotlienc-static.a libbrotlienc.a');
->exec('ln -s libbrotlicommon.a libbrotlicommon-static.a')
->exec('ln -s libbrotlidec.a libbrotlidec-static.a')
->exec('ln -s libbrotlienc.a libbrotlienc-static.a');
foreach (FileSystem::scanDirFiles(BUILD_ROOT_PATH . '/lib/', false, true) as $filename) {
if (str_starts_with($filename, 'libbrotli') && (str_contains($filename, '.so') || str_ends_with($filename, '.dylib'))) {
unlink(BUILD_ROOT_PATH . '/lib/' . $filename);

View File

@@ -29,7 +29,7 @@ trait libssh2
"-DENABLE_ZLIB_COMPRESSION={$enable_zlib} " .
'..'
)
->exec("cmake --build . -j {$this->builder->concurrency} --target libssh2")
->exec("cmake --build . -j {$this->builder->concurrency}")
->exec('make install DESTDIR=' . BUILD_ROOT_PATH);
$this->patchPkgconfPrefix(['libssh2.pc']);
}

View File

@@ -32,7 +32,8 @@ trait libwebp
->exec("cmake --build . -j {$this->builder->concurrency}")
->exec('make install DESTDIR=' . BUILD_ROOT_PATH);
// patch pkgconfig
$this->patchPkgconfPrefix(['libsharpyuv.pc', 'libwebp.pc', 'libwebpdecoder.pc', 'libwebpdemux.pc', 'libwebpmux.pc'], PKGCONF_PATCH_PREFIX);
$this->patchPkgconfPrefix(['libsharpyuv.pc', 'libwebp.pc', 'libwebpdecoder.pc', 'libwebpdemux.pc', 'libwebpmux.pc'], PKGCONF_PATCH_PREFIX | PKGCONF_PATCH_LIBDIR);
$this->patchPkgconfPrefix(['libsharpyuv.pc'], PKGCONF_PATCH_CUSTOM, ['/^includedir=.*$/m', 'includedir=${prefix}/include/webp']);
$this->cleanLaFiles();
}
}

View File

@@ -25,7 +25,8 @@ class BuildCliCommand extends BuildCommand
$this->addOption('build-micro', null, null, 'build micro');
$this->addOption('build-cli', null, null, 'build cli');
$this->addOption('build-fpm', null, null, 'build fpm');
$this->addOption('build-all', null, null, 'build cli, micro, fpm');
$this->addOption('build-embed', null, null, 'build embed');
$this->addOption('build-all', null, null, 'build cli, micro, fpm, embed');
$this->addOption('no-strip', null, null, 'build without strip, in order to debug and load external extensions');
$this->addOption('enable-zts', null, null, 'enable ZTS support');
$this->addOption('with-hardcoded-ini', 'I', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Patch PHP source code, inject hardcoded INI');
@@ -43,13 +44,15 @@ class BuildCliCommand extends BuildCommand
$rule = $rule | ($this->getOption('build-cli') ? BUILD_TARGET_CLI : BUILD_TARGET_NONE);
$rule = $rule | ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE);
$rule = $rule | ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : BUILD_TARGET_NONE);
$rule = $rule | ($this->getOption('build-embed') ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE);
$rule = $rule | ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE);
if ($rule === BUILD_TARGET_NONE) {
$this->output->writeln('<error>Please add at least one build target!</error>');
$this->output->writeln("<comment>\t--build-cli\tBuild php-cli SAPI</comment>");
$this->output->writeln("<comment>\t--build-micro\tBuild phpmicro SAPI</comment>");
$this->output->writeln("<comment>\t--build-fpm\tBuild php-fpm SAPI</comment>");
$this->output->writeln("<comment>\t--build-all\tBuild all SAPI: cli, micro, fpm</comment>");
$this->output->writeln("<comment>\t--build-embed\tBuild embed SAPI/libphp</comment>");
$this->output->writeln("<comment>\t--build-all\tBuild all SAPI: cli, micro, fpm, embed</comment>");
return static::FAILURE;
}
try {

View File

@@ -20,6 +20,7 @@ class BuildLibsCommand extends BuildCommand
$this->addArgument('libraries', InputArgument::REQUIRED, 'The libraries will be compiled, comma separated');
$this->addOption('clean', null, null, 'Clean old download cache and source before fetch');
$this->addOption('all', 'A', null, 'Build all libs that static-php-cli needed');
$this->addOption('rebuild', 'r', null, 'Delete old build and rebuild');
}
public function initialize(InputInterface $input, OutputInterface $output): void

View File

@@ -1,166 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\store\FileSystem;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\text;
#[AsCommand('deploy', 'Deploy static-php-cli self to an .phar application')]
class DeployCommand extends BaseCommand
{
public function configure(): void
{
$this->addArgument('target', InputArgument::OPTIONAL, 'The file or directory to pack.');
$this->addOption('auto-phar-fix', null, InputOption::VALUE_NONE, 'Automatically fix ini option.');
$this->addOption('overwrite', 'W', InputOption::VALUE_NONE, 'Overwrite existing files.');
$this->addOption('with-no-dev', 'D', InputOption::VALUE_NONE, 'Automatically use non-dev composer dependencies to reduce size');
$this->addOption('with-dev', 'd', InputOption::VALUE_NONE, 'Automatically use dev composer dependencies');
}
/**
* @throws \PharException
*/
public function handle(): int
{
$composer = require ROOT_DIR . '/vendor/composer/installed.php';
if (($composer['root']['dev'] ?? false) === true) {
if (!$this->getOption('with-no-dev')) {
$this->output->writeln('<comment>Current static-php-cli dependencies have installed dev-dependencies</comment>');
$this->output->writeln('<comment>If you want to remove, you can choose "Yes" to run command "composer update --no-dev" to remove.</comment>');
$this->output->writeln('<comment>Or choose "No", just pack, deploy.</comment>');
$ask = confirm('Do you want to remove dev-dependencies to reduce size of phar file?');
} elseif (!$this->getOption('with-dev')) {
$ask = true;
} else {
$ask = false;
}
if ($ask) {
[$code] = shell()->execWithResult('composer update --no-dev');
if ($code !== 0) {
$this->output->writeln('<error>"composer update --no-dev" failed with exit code [' . $code . ']</error>');
$this->output->writeln('<error>You may need to run this command by your own.</error>');
return static::FAILURE;
}
$this->output->writeln('<info>Update successfully, you need to re-run deploy command to pack.</info>');
return static::SUCCESS;
}
}
// 首先得确认是不是关闭了readonly模式
if (ini_get('phar.readonly') == 1) {
if ($this->getOption('auto-phar-fix')) {
$ask = true;
} else {
$this->output->writeln('<comment>pack command needs "phar.readonly" = "Off" !</comment>');
$ask = confirm('Do you want to automatically set it and continue ?');
// $ask = $prompt->requireBool('<comment>pack command needs "phar.readonly" = "Off" !</comment>' . PHP_EOL . 'If you want to automatically set it and continue, just Enter', true);
}
if ($ask) {
global $argv;
$args = array_merge(['-d', 'phar.readonly=0'], $_SERVER['argv'], ['--no-motd']);
if (function_exists('pcntl_exec')) {
$this->output->writeln('<info>Changing to phar.readonly=0 mode ...</info>');
if (pcntl_exec(PHP_BINARY, $args) === false) {
throw new \PharException('Switching to read write mode failed, please check the environment.');
}
} else {
$this->output->writeln('<info>Now running command in child process.</info>');
passthru(PHP_BINARY . ' -d phar.readonly=0 ' . implode(' ', $argv), $retcode);
exit($retcode);
}
}
}
// 获取路径
$path = WORKING_DIR;
// 如果是目录,则将目录下的所有文件打包
$phar_path = text('Please input the phar target filename', default: '/tmp/static-php-cli.phar');
// $phar_path = $prompt->requireArgument('target', 'Please input the phar target filename', 'static-php-cli.phar');
if (FileSystem::isRelativePath($phar_path)) {
$phar_path = WORKING_DIR . '/' . $phar_path;
}
if (file_exists($phar_path)) {
if (!$this->getOption('overwrite')) {
$this->output->writeln('<comment>The file "' . $phar_path . '" already exists.</comment>');
$ask = confirm('Do you want to overwrite it?');
} else {
$ask = true;
}
if (!$ask) {
$this->output->writeln('<comment>User canceled.</comment>');
return static::FAILURE;
}
@unlink($phar_path);
}
$phar = new \Phar($phar_path);
$phar->startBuffering();
$all = FileSystem::scanDirFiles($path, true, true);
$all = array_filter($all, function ($x) {
$dirs = preg_match('/(^(config|src|vendor)\\/|^(composer\\.json|README\\.md|source\\.json|LICENSE|README-en\\.md)$)/', $x);
return !($dirs !== 1);
});
sort($all);
$map = [];
foreach ($all as $v) {
$map[$v] = $path . '/' . $v;
}
$this->output->writeln('<info>Start packing files...</info>');
try {
foreach ($this->progress()->iterate($map) as $file => $origin_file) {
$phar->addFromString($file, php_strip_whitespace($origin_file));
}
// $phar->buildFromIterator(new SeekableArrayIterator($map, new ProgressBar($output)));
$phar->addFromString(
'.phar-entry.php',
str_replace(
'/../vendor/autoload.php',
'/vendor/autoload.php',
file_get_contents(ROOT_DIR . '/bin/spc')
)
);
$stub = '.phar-entry.php';
$phar->setStub($phar->createDefaultStub($stub));
} catch (\Throwable $e) {
$this->output->writeln($e);
return static::FAILURE;
}
$phar->addFromString('.prod', 'true');
$phar->stopBuffering();
$this->output->writeln(PHP_EOL . 'Done! Phar file is generated at "' . $phar_path . '".');
if (file_exists(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx')) {
$this->output->writeln('Detected you have already compiled micro binary, I will make executable now for you!');
file_put_contents(
pathinfo($phar_path, PATHINFO_DIRNAME) . '/spc',
file_get_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx') .
file_get_contents($phar_path)
);
chmod(pathinfo($phar_path, PATHINFO_DIRNAME) . '/spc', 0755);
$this->output->writeln('<info>Binary Executable: ' . pathinfo($phar_path, PATHINFO_DIRNAME) . '/spc</info>');
}
chmod($phar_path, 0755);
$this->output->writeln('<info>Phar Executable: ' . $phar_path . '</info>');
return static::SUCCESS;
}
private function progress(): ProgressBar
{
$progress = new ProgressBar($this->output, 0);
$progress->setBarCharacter('<fg=green>⚬</>');
$progress->setEmptyBarCharacter('<fg=red>⚬</>');
$progress->setProgressCharacter('<fg=green>➤</>');
$progress->setFormat(
"%current%/%max% [%bar%] %percent:3s%%\n🪅 %estimated:-20s% %memory:20s%" . PHP_EOL
);
return $progress;
}
}

View File

@@ -5,7 +5,11 @@ declare(strict_types=1);
namespace SPC\command;
use SPC\doctor\CheckListHandler;
use SPC\doctor\CheckResult;
use SPC\exception\RuntimeException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Question\ConfirmationQuestion;
#[AsCommand('doctor', 'Diagnose whether the current environment can compile normally')]
class DoctorCommand extends BaseCommand
@@ -18,14 +22,65 @@ class DoctorCommand extends BaseCommand
public function handle(): int
{
try {
$checker = new CheckListHandler($this->input, $this->output);
$checker->runCheck($this->input->getOption('auto-fix') ? FIX_POLICY_AUTOFIX : FIX_POLICY_PROMPT);
$checker = new CheckListHandler();
$fix_policy = $this->input->getOption('auto-fix') ? FIX_POLICY_AUTOFIX : FIX_POLICY_PROMPT;
foreach ($checker->runChecks() as $check) {
if ($check->limit_os !== null && $check->limit_os !== PHP_OS_FAMILY) {
continue;
}
$this->output->write('Checking <comment>' . $check->item_name . '</comment> ... ');
$result = call_user_func($check->callback);
if ($result === null) {
$this->output->writeln('skipped');
} elseif ($result instanceof CheckResult) {
if ($result->isOK()) {
$this->output->writeln($result->getMessage() ?? 'ok');
continue;
}
// Failed
$this->output->writeln('<error>' . $result->getMessage() . '</error>');
switch ($fix_policy) {
case FIX_POLICY_DIE:
throw new RuntimeException('Some check items can not be fixed !');
case FIX_POLICY_PROMPT:
if ($result->getFixItem() !== '') {
$helper = new QuestionHelper();
$question = new ConfirmationQuestion('Do you want to fix it? [Y/n] ', true);
if ($helper->ask($this->input, $this->output, $question)) {
$checker->emitFix($this->output, $result);
} else {
throw new RuntimeException('You cancelled fix');
}
} else {
throw new RuntimeException('Some check items can not be fixed !');
}
break;
case FIX_POLICY_AUTOFIX:
if ($result->getFixItem() !== '') {
$this->output->writeln('Automatically fixing ' . $result->getFixItem() . ' ...');
$checker->emitFix($this->output, $result);
} else {
throw new RuntimeException('Some check items can not be fixed !');
}
break;
}
}
}
$this->output->writeln('<info>Doctor check complete !</info>');
} catch (\Throwable $e) {
$this->output->writeln('<error>' . $e->getMessage() . '</error>');
pcntl_signal(SIGINT, SIG_IGN);
return static::FAILURE;
}
return static::SUCCESS;
}
}

View File

@@ -6,6 +6,7 @@ namespace SPC\command\dev;
use SPC\command\BaseCommand;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\util\DependencyUtil;
@@ -18,15 +19,17 @@ class AllExtCommand extends BaseCommand
{
public function configure(): void
{
$this->addArgument('extensions', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Extension name', null);
$this->addArgument('extensions', InputArgument::OPTIONAL, 'List of extensions that will be displayed, comma separated');
}
/**
* @throws FileSystemException
* @throws WrongUsageException
* @throws RuntimeException
*/
public function handle(): int
{
$extensions = $this->input->getArgument('extensions') ?: [];
$extensions = array_map('trim', array_filter(explode(',', $this->getArgument('extensions') ?? '')));
$style = new SymfonyStyle($this->input, $this->output);
$style->writeln($extensions ? 'Available extensions:' : 'Extensions:');

View File

@@ -9,7 +9,7 @@ use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand('dev:php-ver', 'Dev command')]
#[AsCommand('dev:php-version', 'Returns version of PHP located source directory', ['dev:php-ver'])]
class PhpVerCommand extends BaseCommand
{
public function initialize(InputInterface $input, OutputInterface $output): void
@@ -22,11 +22,19 @@ class PhpVerCommand extends BaseCommand
{
// Find php from source/php-src
$file = SOURCE_PATH . '/php-src/main/php_version.h';
if (!file_exists($file)) {
$this->output->writeln('<error>PHP source not found, maybe you need to extract first ?</error>');
return static::FAILURE;
}
$result = preg_match('/#define PHP_VERSION "([^"]+)"/', file_get_contents($file), $match);
if ($result === false) {
$this->output->writeln('<error>PHP source not found, maybe you need to extract first ?</error>');
return static::FAILURE;
}
$this->output->writeln('<info>' . $match[1] . '</info>');
return static::SUCCESS;
}

View File

@@ -15,7 +15,7 @@ use Symfony\Component\Console\Input\InputArgument;
/**
* Modify config file: sort lib, ext, source by name.
*/
#[AsCommand('dev:sort-config', 'After config edited, sort it by alphabet', ['sort-config'])]
#[AsCommand('dev:sort-config', 'After config edited, sort it by alphabet', ['sort-config'], true)]
class SortConfigCommand extends BaseCommand
{
public function configure(): void
@@ -34,7 +34,7 @@ class SortConfigCommand extends BaseCommand
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/lib.json'), true);
ConfigValidator::validateLibs($file);
ksort($file);
if (!file_put_contents(ROOT_DIR . '/config/lib.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
if (!file_put_contents(ROOT_DIR . '/config/lib.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n")) {
$this->output->writeln('<error>Write file lib.json failed!</error>');
return static::FAILURE;
}
@@ -43,7 +43,7 @@ class SortConfigCommand extends BaseCommand
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/source.json'), true);
ConfigValidator::validateSource($file);
uksort($file, fn ($a, $b) => $a === 'php-src' ? -1 : ($b === 'php-src' ? 1 : ($a < $b ? -1 : 1)));
if (!file_put_contents(ROOT_DIR . '/config/source.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
if (!file_put_contents(ROOT_DIR . '/config/source.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n")) {
$this->output->writeln('<error>Write file source.json failed!</error>');
return static::FAILURE;
}
@@ -52,7 +52,7 @@ class SortConfigCommand extends BaseCommand
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/ext.json'), true);
ConfigValidator::validateExts($file);
ksort($file);
if (!file_put_contents(ROOT_DIR . '/config/ext.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
if (!file_put_contents(ROOT_DIR . '/config/ext.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n")) {
$this->output->writeln('<error>Write file ext.json failed!</error>');
return static::FAILURE;
}

View File

@@ -7,105 +7,62 @@ namespace SPC\doctor;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class CheckListHandler
final class CheckListHandler
{
/** @var AsCheckItem[] */
private array $check_list = [];
private array $fix_map = [];
public function __construct() {}
/**
* @return array<AsCheckItem>
* @throws \ReflectionException
* @throws RuntimeException
* @throws FileSystemException
* @throws RuntimeException
*/
public function __construct(private readonly InputInterface $input, private readonly OutputInterface $output, bool $include_manual = false)
public function runChecks(bool $include_manual = false): array
{
$this->loadCheckList($include_manual);
return $this->loadCheckList($include_manual);
}
/**
* @throws RuntimeException
*/
public function runSingleCheck(string $item_name, int $fix_policy = FIX_POLICY_DIE): void
public function emitFix(OutputInterface $output, CheckResult $result): void
{
foreach ($this->check_list as $item) {
if ($item->item_name === $item_name) {
$this->check_list = [$item];
break;
}
}
$this->runCheck($fix_policy);
}
pcntl_signal(SIGINT, function () use ($output) {
$output->writeln('<error>You cancelled fix</error>');
});
/**
* @throws RuntimeException
*/
public function runCheck(int $fix_policy = FIX_POLICY_DIE): void
{
foreach ($this->check_list as $item) {
if ($item->limit_os !== null && $item->limit_os !== PHP_OS_FAMILY) {
continue;
}
$this->output->write('Checking <comment>' . $item->item_name . '</comment> ... ');
$result = call_user_func($item->callback);
if ($result === null) {
$this->output->writeln('skipped');
} elseif ($result instanceof CheckResult) {
if ($result->isOK()) {
$this->output->writeln($result->getMessage() ?? 'ok');
continue;
}
// Failed
$this->output->writeln('<error>' . $result->getMessage() . '</error>');
switch ($fix_policy) {
case FIX_POLICY_DIE:
throw new RuntimeException('Some check items can not be fixed !');
case FIX_POLICY_PROMPT:
if ($result->getFixItem() !== '') {
$helper = new QuestionHelper();
$question = new ConfirmationQuestion('Do you want to fix it? [Y/n] ', true);
if ($helper->ask($this->input, $this->output, $question)) {
$this->emitFix($result);
} else {
throw new RuntimeException('You cancelled fix');
}
} else {
throw new RuntimeException('Some check items can not be fixed !');
}
break;
case FIX_POLICY_AUTOFIX:
if ($result->getFixItem() !== '') {
$this->output->writeln('Automatically fixing ' . $result->getFixItem() . ' ...');
$this->emitFix($result);
} else {
throw new RuntimeException('Some check items can not be fixed !');
}
break;
}
}
$fix_result = call_user_func($this->fix_map[$result->getFixItem()], ...$result->getFixParams());
pcntl_signal(SIGINT, SIG_IGN);
if ($fix_result) {
$output->writeln('<info>Fix done</info>');
} else {
$output->writeln('<error>Fix failed</error>');
throw new RuntimeException('Some check item are not fixed');
}
}
/**
* Load Doctor check item list
*
* @return array<AsCheckItem>
* @throws \ReflectionException
* @throws RuntimeException
* @throws FileSystemException
*/
private function loadCheckList(bool $include_manual = false): void
private function loadCheckList(bool $include_manual = false): array
{
foreach (FileSystem::getClassesPsr4(__DIR__ . '/item', 'SPC\\doctor\\item') as $class) {
$ref = new \ReflectionClass($class);
foreach ($ref->getMethods() as $method) {
$attr = $method->getAttributes();
foreach ($attr as $a) {
foreach ($method->getAttributes() as $a) {
if (is_a($a->getName(), AsCheckItem::class, true)) {
/** @var AsCheckItem $instance */
$instance = $a->newInstance();
@@ -126,26 +83,10 @@ class CheckListHandler
}
}
}
// sort check list by level
usort($this->check_list, fn ($a, $b) => $a->level > $b->level ? -1 : ($a->level == $b->level ? 0 : 1));
}
/**
* @throws RuntimeException
*/
private function emitFix(CheckResult $result): void
{
pcntl_signal(SIGINT, function () {
$this->output->writeln('<error>You cancelled fix</error>');
});
$fix = $this->fix_map[$result->getFixItem()];
$fix_result = call_user_func($fix, ...$result->getFixParams());
pcntl_signal(SIGINT, SIG_IGN);
if ($fix_result) {
$this->output->writeln('<info>Fix done</info>');
} else {
$this->output->writeln('<error>Fix failed</error>');
throw new RuntimeException('Some check item are not fixed');
}
// sort check list by level
usort($this->check_list, fn (AsCheckItem $a, AsCheckItem $b) => $a->level > $b->level ? -1 : ($a->level == $b->level ? 0 : 1));
return $this->check_list;
}
}

View File

@@ -43,7 +43,8 @@ const BUILD_TARGET_NONE = 0; // no target
const BUILD_TARGET_CLI = 1; // build cli
const BUILD_TARGET_MICRO = 2; // build micro
const BUILD_TARGET_FPM = 4; // build fpm
const BUILD_TARGET_ALL = 7; // build all
const BUILD_TARGET_EMBED = 8; // build embed
const BUILD_TARGET_ALL = 15; // build all
// doctor error fix policy
const FIX_POLICY_DIE = 1; // die directly
@@ -55,6 +56,7 @@ const PKGCONF_PATCH_PREFIX = 1;
const PKGCONF_PATCH_EXEC_PREFIX = 2;
const PKGCONF_PATCH_LIBDIR = 4;
const PKGCONF_PATCH_INCLUDEDIR = 8;
const PKGCONF_PATCH_ALL = 15;
const PKGCONF_PATCH_CUSTOM = 16;
const PKGCONF_PATCH_ALL = 31;
ConsoleLogger::$date_format = 'H:i:s';

View File

@@ -4,5 +4,5 @@ declare(strict_types=1);
assert(class_exists('\\DOMDocument'));
$doc = new DOMDocument();
$doc->loadHtml("<html><head><meta charset=\"UTF-8\"><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head><body id='app'>Hello</body></html>");
$doc->loadHtml('<html><head><meta charset="UTF-8"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head><body id="app">Hello</body></html>');
assert($doc->getElementById('app')->nodeValue === 'Hello');

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace SPC\Tests\doctor;
use PHPUnit\Framework\TestCase;
use SPC\doctor\CheckListHandler;
/**
* @internal
*/
final class CheckListHandlerTest extends TestCase
{
public function testRunChecksReturnsListOfCheck(): void
{
$list = new CheckListHandler();
$this->assertCount(6, $list->runChecks());
}
}