mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-06 00:05:42 +08:00
initial commit for macOS support
This commit is contained in:
86
src/SPC/command/BuildCliCommand.php
Normal file
86
src/SPC/command/BuildCliCommand.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use SPC\builder\BuilderProvider;
|
||||
use SPC\exception\ExceptionHandler;
|
||||
use SPC\util\DependencyUtil;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
class BuildCliCommand extends BuildCommand
|
||||
{
|
||||
protected static $defaultName = 'build';
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setDescription('Build CLI binary');
|
||||
$this->addArgument('extensions', InputArgument::REQUIRED, 'The extensions will be compiled, comma separated');
|
||||
$this->addOption('with-libs', null, InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', '');
|
||||
$this->addOption('build-micro', null, null, 'build micro only');
|
||||
$this->addOption('build-all', null, null, 'build both cli and micro');
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// 从参数中获取要编译的 libraries,并转换为数组
|
||||
$libraries = array_map('trim', array_filter(explode(',', $input->getOption('with-libs'))));
|
||||
// 从参数中获取要编译的 extensions,并转换为数组
|
||||
$extensions = array_map('trim', array_filter(explode(',', $input->getArgument('extensions'))));
|
||||
|
||||
define('BUILD_ALL_STATIC', true);
|
||||
|
||||
if ($input->getOption('build-all')) {
|
||||
$rule = BUILD_MICRO_BOTH;
|
||||
logger()->info('Builder will build php-cli and phpmicro SAPI');
|
||||
} elseif ($input->getOption('build-micro')) {
|
||||
$rule = BUILD_MICRO_ONLY;
|
||||
logger()->info('Builder will build phpmicro SAPI');
|
||||
} else {
|
||||
$rule = BUILD_MICRO_NONE;
|
||||
logger()->info('Builder will build php-cli SAPI');
|
||||
}
|
||||
try {
|
||||
// 构建对象
|
||||
$builder = BuilderProvider::makeBuilderByInput($input);
|
||||
// 根据提供的扩展列表获取依赖库列表并编译
|
||||
[$extensions, $libraries, $not_included] = DependencyUtil::getExtLibsByDeps($extensions, $libraries);
|
||||
|
||||
logger()->info('Enabled extensions: ' . implode(', ', $extensions));
|
||||
logger()->info('Required libraries: ' . implode(', ', $libraries));
|
||||
if (!empty($not_included)) {
|
||||
logger()->warning('some extensions will be enabled due to dependencies: ' . implode(',', $not_included));
|
||||
}
|
||||
sleep(2);
|
||||
// 编译和检查库是否完整
|
||||
$builder->buildLibs($libraries);
|
||||
// 执行扩展检测
|
||||
$builder->proveExts($extensions);
|
||||
// 构建
|
||||
$builder->buildPHP($rule, $input->getOption('with-clean'), $input->getOption('bloat'));
|
||||
// 统计时间
|
||||
$time = round(microtime(true) - START_TIME, 3);
|
||||
logger()->info('Build complete, used ' . $time . ' s !');
|
||||
if ($rule !== BUILD_MICRO_ONLY) {
|
||||
logger()->info('Static php binary path: ' . SOURCE_PATH . '/php-src/sapi/cli/php');
|
||||
}
|
||||
if ($rule !== BUILD_MICRO_NONE) {
|
||||
logger()->info('phpmicro binary path: ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx');
|
||||
}
|
||||
return 0;
|
||||
} catch (\Throwable $e) {
|
||||
if ($input->getOption('debug')) {
|
||||
ExceptionHandler::getInstance()->handle($e);
|
||||
} else {
|
||||
logger()->emergency('Build failed, please check terminal output, or build with --debug option to see more details.');
|
||||
logger()->emergency($e->getMessage());
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/SPC/command/BuildCommand.php
Normal file
37
src/SPC/command/BuildCommand.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
abstract class BuildCommand extends BaseCommand
|
||||
{
|
||||
public function __construct(string $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
|
||||
// 根据运行的操作系统分配允许不同的命令行参数,Windows 需要额外的 VS 和 SDK等,*nix 需要提供架构
|
||||
switch (PHP_OS_FAMILY) {
|
||||
case 'Windows':
|
||||
$this->addOption('with-sdk-binary-dir', null, InputOption::VALUE_REQUIRED, 'path to binary sdk');
|
||||
$this->addOption('vs-ver', null, InputOption::VALUE_REQUIRED, 'vs version, e.g. "17" for Visual Studio 2022');
|
||||
$this->addOption('arch', null, InputOption::VALUE_REQUIRED, 'architecture, "x64" or "arm64"', 'x64');
|
||||
break;
|
||||
case 'Linux':
|
||||
$this->addOption('no-system-static', null, null, 'do not use system static libraries');
|
||||
// no break
|
||||
case 'Darwin':
|
||||
$this->addOption('cc', null, InputOption::VALUE_REQUIRED, 'C compiler');
|
||||
$this->addOption('cxx', null, InputOption::VALUE_REQUIRED, 'C++ compiler');
|
||||
$this->addOption('arch', null, InputOption::VALUE_REQUIRED, 'architecture', php_uname('m'));
|
||||
break;
|
||||
}
|
||||
|
||||
// 是否在编译 make 前清除旧的文件
|
||||
$this->addOption('with-clean', null, null, 'fresh build, `make clean` before `make`');
|
||||
// 是否采用强制链接,让链接器强制加载静态库文件
|
||||
$this->addOption('bloat', null, null, 'add all libraries into binary');
|
||||
}
|
||||
}
|
||||
69
src/SPC/command/BuildLibsCommand.php
Normal file
69
src/SPC/command/BuildLibsCommand.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use SPC\builder\BuilderProvider;
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
class BuildLibsCommand extends BuildCommand
|
||||
{
|
||||
protected static $defaultName = 'build:libs';
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setDescription('Build dependencies');
|
||||
$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');
|
||||
}
|
||||
|
||||
public function initialize(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// --all 等于 ""
|
||||
if ($input->getOption('all')) {
|
||||
$input->setArgument('libraries', '');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// 从参数中获取要编译的 libraries,并转换为数组
|
||||
$libraries = array_map('trim', array_filter(explode(',', $input->getArgument('libraries'))));
|
||||
|
||||
// 删除旧资源
|
||||
if ($input->getOption('clean')) {
|
||||
logger()->warning('You are doing some operations that not recoverable: removing directories below');
|
||||
logger()->warning(BUILD_ROOT_PATH);
|
||||
logger()->warning('I will remove these dir after you press [Enter] !');
|
||||
echo 'Confirm operation? [Yes] ';
|
||||
fgets(STDIN);
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
f_passthru('rmdir /s /q ' . BUILD_ROOT_PATH);
|
||||
} else {
|
||||
f_passthru('rm -rf ' . BUILD_ROOT_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建对象
|
||||
$builder = BuilderProvider::makeBuilderByInput($input);
|
||||
// 只编译 library 的情况下,标记
|
||||
$builder->setLibsOnly();
|
||||
// 编译和检查库完整
|
||||
$builder->buildLibs($libraries);
|
||||
|
||||
$time = round(microtime(true) - START_TIME, 3);
|
||||
logger()->info('Build libs complete, used ' . $time . ' s !');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
116
src/SPC/command/DeployCommand.php
Normal file
116
src/SPC/command/DeployCommand.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use CliHelper\Tools\ArgFixer;
|
||||
use CliHelper\Tools\DataProvider;
|
||||
use CliHelper\Tools\SeekableArrayIterator;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
class DeployCommand extends BaseCommand
|
||||
{
|
||||
protected static $defaultName = 'deploy-self';
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setDescription('Deploy static-php-cli self to an .phar application');
|
||||
$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('disable-gzip', 'z', InputOption::VALUE_NONE, 'disable gzip archive mode');
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// 第一阶段流程:如果没有写path,将会提示输入要打包的path
|
||||
$prompt = new ArgFixer($input, $output);
|
||||
// 首先得确认是不是关闭了readonly模式
|
||||
if (ini_get('phar.readonly') == 1) {
|
||||
if ($input->getOption('auto-phar-fix')) {
|
||||
$ask = true;
|
||||
} else {
|
||||
$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);
|
||||
}
|
||||
$output->writeln('<info>Now running command in child process.</info>');
|
||||
if ($ask) {
|
||||
global $argv;
|
||||
passthru(PHP_BINARY . ' -d phar.readonly=0 ' . implode(' ', $argv), $retcode);
|
||||
exit($retcode);
|
||||
}
|
||||
}
|
||||
// 获取路径
|
||||
$path = WORKING_DIR;
|
||||
// 如果是目录,则将目录下的所有文件打包
|
||||
$phar_path = $prompt->requireArgument('target', 'Please input the phar target filename', 'static-php-cli.phar');
|
||||
|
||||
if (DataProvider::isRelativePath($phar_path)) {
|
||||
$phar_path = '/tmp/' . $phar_path;
|
||||
}
|
||||
if (file_exists($phar_path)) {
|
||||
$ask = $input->getOption('overwrite') ? true : $prompt->requireBool('<comment>The file "' . $phar_path . '" already exists, do you want to overwrite it?</comment>' . PHP_EOL . 'If you want to, just Enter');
|
||||
if (!$ask) {
|
||||
$output->writeln('<comment>User canceled.</comment>');
|
||||
return 1;
|
||||
}
|
||||
@unlink($phar_path);
|
||||
}
|
||||
$phar = new \Phar($phar_path);
|
||||
$phar->startBuffering();
|
||||
|
||||
$all = DataProvider::scanDirFiles($path, true, true);
|
||||
|
||||
$all = array_filter($all, function ($x) {
|
||||
$dirs = preg_match('/(^(bin|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;
|
||||
}
|
||||
|
||||
$output->writeln('<info>Start packing files...</info>');
|
||||
try {
|
||||
$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/static-php-cli')
|
||||
)
|
||||
);
|
||||
$stub = '.phar-entry.php';
|
||||
$phar->setStub($phar->createDefaultStub($stub));
|
||||
} catch (\Throwable $e) {
|
||||
$output->writeln($e);
|
||||
return 1;
|
||||
}
|
||||
$phar->addFromString('.prod', 'true');
|
||||
if (!$input->getOption('disable-gzip')) {
|
||||
$phar->compressFiles(\Phar::GZ);
|
||||
}
|
||||
$phar->stopBuffering();
|
||||
$output->writeln(PHP_EOL . 'Done! Phar file is generated at "' . $phar_path . '".');
|
||||
if (file_exists(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx')) {
|
||||
$output->writeln('Detected you have already compiled micro binary, I will make executable now for you!');
|
||||
file_put_contents(
|
||||
$phar_path . '.exe',
|
||||
file_get_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx') .
|
||||
file_get_contents($phar_path)
|
||||
);
|
||||
chmod($phar_path . '.exe', 0755);
|
||||
$output->writeln('<info>Static: ' . $phar_path . '.exe</info>');
|
||||
}
|
||||
chmod($phar_path, 0755);
|
||||
$output->writeln('<info>Phar: ' . $phar_path . '</info>');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
29
src/SPC/command/DumpLicenseCommand.php
Normal file
29
src/SPC/command/DumpLicenseCommand.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* 修改 config 后对其 kv 进行排序的操作
|
||||
*/
|
||||
class DumpLicenseCommand extends BaseCommand
|
||||
{
|
||||
protected static $defaultName = 'dump-license';
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setDescription('Dump licenses for required libraries');
|
||||
$this->addArgument('config-name', InputArgument::REQUIRED, 'Your config to be sorted, you can sort "lib", "source" and "ext".');
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln('<info>not implemented</info>');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
238
src/SPC/command/FetchSourceCommand.php
Normal file
238
src/SPC/command/FetchSourceCommand.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use SPC\exception\DownloaderException;
|
||||
use SPC\exception\ExceptionHandler;
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\InvalidArgumentException;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\util\Patcher;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
class FetchSourceCommand extends BaseCommand
|
||||
{
|
||||
protected static $defaultName = 'fetch';
|
||||
|
||||
protected string $php_major_ver;
|
||||
|
||||
protected InputInterface $input;
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setDescription('Fetch required sources');
|
||||
$this->addArgument('extensions', InputArgument::REQUIRED, 'The extensions will be compiled, comma separated');
|
||||
$this->addArgument('libraries', InputArgument::REQUIRED, 'The libraries will be compiled, comma separated');
|
||||
$this->addOption('hash', null, null, 'Hash only');
|
||||
$this->addOption('shallow-clone', null, null, 'Clone shallow');
|
||||
$this->addOption('with-openssl11', null, null, 'Use openssl 1.1');
|
||||
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'version in major.minor format like 8.1', '8.1');
|
||||
$this->addOption('clean', null, null, 'Clean old download cache and source before fetch');
|
||||
$this->addOption('all', 'A', null, 'Fetch all sources that static-php-cli needed');
|
||||
}
|
||||
|
||||
public function initialize(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// --all 等于 "" "",也就是所有东西都要下载
|
||||
if ($input->getOption('all')) {
|
||||
$input->setArgument('extensions', '');
|
||||
$input->setArgument('libraries', '');
|
||||
}
|
||||
parent::initialize($input, $output);
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->input = $input;
|
||||
try {
|
||||
// 匹配版本
|
||||
$ver = $this->php_major_ver = $input->getOption('with-php') ?? '8.1';
|
||||
preg_match('/^\d+\.\d+$/', $ver, $matches);
|
||||
if (!$matches) {
|
||||
logger()->error("bad version arg: {$ver}, x.y required!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 删除旧资源
|
||||
if ($input->getOption('clean')) {
|
||||
logger()->warning('You are doing some operations that not recoverable: removing directories below');
|
||||
logger()->warning(SOURCE_PATH);
|
||||
logger()->warning(DOWNLOAD_PATH);
|
||||
logger()->warning('I will remove these dir after you press [Enter] !');
|
||||
echo 'Confirm operation? [Yes] ';
|
||||
$r = strtolower(trim(fgets(STDIN)));
|
||||
if ($r !== 'yes' && $r !== '') {
|
||||
logger()->notice('Operation canceled.');
|
||||
return 1;
|
||||
}
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
f_passthru('rmdir /s /q ' . SOURCE_PATH);
|
||||
f_passthru('rmdir /s /q ' . DOWNLOAD_PATH);
|
||||
} else {
|
||||
f_passthru('rm -rf ' . SOURCE_PATH);
|
||||
f_passthru('rm -rf ' . DOWNLOAD_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用浅克隆可以减少调用 git 命令下载资源时的存储空间占用
|
||||
if ($input->getOption('shallow-clone')) {
|
||||
define('GIT_SHALLOW_CLONE', true);
|
||||
}
|
||||
|
||||
// 读取源配置,随便读一个source,用于缓存 source 配置
|
||||
Config::getSource('openssl');
|
||||
|
||||
// 是否启用openssl11
|
||||
if ($input->getOption('with-openssl11')) {
|
||||
logger()->debug('Using openssl 1.1');
|
||||
// 手动修改配置
|
||||
Config::$source['openssl']['regex'] = '/href="(?<file>openssl-(?<version>1.[^"]+)\.tar\.gz)\"/';
|
||||
}
|
||||
|
||||
// 默认预选 phpmicro
|
||||
$chosen_sources = ['micro'];
|
||||
|
||||
// 从参数中获取要编译的 libraries,并转换为数组
|
||||
$libraries = array_map('trim', array_filter(explode(',', $input->getArgument('libraries'))));
|
||||
if ($libraries) {
|
||||
foreach ($libraries as $lib) {
|
||||
// 从 lib 的 config 中找到对应 source 资源名称,组成一个 lib 的 source 列表
|
||||
$src_name = Config::getLib($lib, 'source');
|
||||
$chosen_sources[] = $src_name;
|
||||
}
|
||||
} else { // 如果传入了空串,那么代表 fetch 所有包
|
||||
$chosen_sources = [...$chosen_sources, ...array_map(fn ($x) => $x['source'], array_values(Config::getLibs()))];
|
||||
}
|
||||
|
||||
// 从参数中获取要编译的 extensions,并转换为数组
|
||||
$extensions = array_map('trim', array_filter(explode(',', $input->getArgument('extensions'))));
|
||||
if ($extensions) {
|
||||
foreach ($extensions as $lib) {
|
||||
if (Config::getExt($lib, 'type') !== 'builtin') {
|
||||
$src_name = Config::getExt($lib, 'source');
|
||||
$chosen_sources[] = $src_name;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach (Config::getExts() as $ext) {
|
||||
if ($ext['type'] !== 'builtin') {
|
||||
$chosen_sources[] = $ext['source'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$chosen_sources = array_unique($chosen_sources);
|
||||
|
||||
// 是否只hash,不下载资源
|
||||
if ($input->getOption('hash')) {
|
||||
$hash = $this->doHash($chosen_sources);
|
||||
$output->writeln($hash);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 创建目录
|
||||
f_mkdir(SOURCE_PATH);
|
||||
f_mkdir(DOWNLOAD_PATH);
|
||||
|
||||
// 下载 PHP
|
||||
Downloader::fetchSource('php-src', Downloader::getLatestPHPInfo($ver));
|
||||
|
||||
// 下载别的依赖资源
|
||||
$cnt = count($chosen_sources);
|
||||
$ni = 0;
|
||||
foreach ($chosen_sources as $name) {
|
||||
++$ni;
|
||||
logger()->info("Fetching source {$name} [{$ni}/{$cnt}]");
|
||||
Downloader::fetchSource($name, Config::getSource($name));
|
||||
}
|
||||
|
||||
// patch 每份资源只需一次,如果已经下载好的资源已经patch了,就标记一下不patch了
|
||||
if (!file_exists(SOURCE_PATH . '/.patched')) {
|
||||
$this->doPatch();
|
||||
} else {
|
||||
logger()->notice('sources already patched');
|
||||
}
|
||||
|
||||
// 打印拉取资源用时
|
||||
$time = round(microtime(true) - START_TIME, 3);
|
||||
logger()->info('Fetch complete, used ' . $time . ' s !');
|
||||
return 0;
|
||||
} catch (\Throwable $e) {
|
||||
// 不开 debug 模式就不要再显示复杂的调试栈信息了
|
||||
if ($input->getOption('debug')) {
|
||||
ExceptionHandler::getInstance()->handle($e);
|
||||
} else {
|
||||
logger()->emergency($e->getMessage() . ', previous message: ' . $e->getPrevious()->getMessage());
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算资源名称列表的 Hash
|
||||
*
|
||||
* @param array $chosen_sources 要计算 hash 的资源名称列表
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DownloaderException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
private function doHash(array $chosen_sources): string
|
||||
{
|
||||
$files = [];
|
||||
foreach ($chosen_sources as $name) {
|
||||
$source = Config::getSource($name);
|
||||
$filename = match ($source['type']) {
|
||||
'ghtar' => Downloader::getLatestGithubTarball($name, $source)[1],
|
||||
'ghtagtar' => Downloader::getLatestGithubTarball($name, $source, 'tags')[1],
|
||||
'ghrel' => Downloader::getLatestGithubRelease($name, $source)[1],
|
||||
'filelist' => Downloader::getFromFileList($name, $source)[1],
|
||||
'url' => $source['filename'] ?? basename($source['url']),
|
||||
'git' => null,
|
||||
default => throw new InvalidArgumentException('unknown source type: ' . $source['type']),
|
||||
};
|
||||
if ($filename !== null) {
|
||||
logger()->info("found {$name} source: {$filename}");
|
||||
$files[] = $filename;
|
||||
}
|
||||
}
|
||||
return hash('sha256', implode('|', $files));
|
||||
}
|
||||
|
||||
/**
|
||||
* 在拉回资源后,需要对一些文件做一些补丁 patch
|
||||
*
|
||||
* @throws FileSystemException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function doPatch(): void
|
||||
{
|
||||
// patch 一些 PHP 的资源,以便编译
|
||||
Patcher::patchPHPDepFiles();
|
||||
|
||||
// openssl 3 需要 patch 额外的东西
|
||||
if (!$this->input->getOption('with-openssl11') && $this->php_major_ver === '8.0') {
|
||||
Patcher::patchOpenssl3();
|
||||
}
|
||||
|
||||
// openssl1.1.1q 在 MacOS 上直接编译会报错,patch 一下
|
||||
// @see: https://github.com/openssl/openssl/issues/18720
|
||||
if ($this->input->getOption('with-openssl11') && file_exists(SOURCE_PATH . '/openssl/test/v3ext.c') && PHP_OS_FAMILY === 'Darwin') {
|
||||
Patcher::patchDarwinOpenssl11();
|
||||
}
|
||||
|
||||
// swow 需要软链接内部的文件夹才能正常编译
|
||||
if (!file_exists(SOURCE_PATH . '/php-src/ext/swow')) {
|
||||
Patcher::patchSwow();
|
||||
}
|
||||
|
||||
// 标记 patch 完成,避免重复 patch
|
||||
file_put_contents(SOURCE_PATH . '/.patched', '');
|
||||
}
|
||||
}
|
||||
30
src/SPC/command/ListExtCommand.php
Normal file
30
src/SPC/command/ListExtCommand.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use SPC\builder\traits\NoMotdTrait;
|
||||
use SPC\store\Config;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ListExtCommand extends BaseCommand
|
||||
{
|
||||
use NoMotdTrait;
|
||||
|
||||
protected static $defaultName = 'list-ext';
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setDescription('List supported extensions');
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
foreach (Config::getExts() as $ext => $meta) {
|
||||
echo $ext . PHP_EOL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
60
src/SPC/command/SortConfigCommand.php
Normal file
60
src/SPC/command/SortConfigCommand.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\ValidationException;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\util\ConfigValidator;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* 修改 config 后对其 kv 进行排序的操作
|
||||
*/
|
||||
class SortConfigCommand extends BaseCommand
|
||||
{
|
||||
protected static $defaultName = 'sort-config';
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setDescription('After config edited, sort it by alphabet');
|
||||
$this->addArgument('config-name', InputArgument::REQUIRED, 'Your config to be sorted, you can sort "lib", "source" and "ext".');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
switch ($name = $input->getArgument('config-name')) {
|
||||
case 'lib':
|
||||
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/lib.json'), true);
|
||||
ConfigValidator::validateLibs($file);
|
||||
ksort($file);
|
||||
file_put_contents(ROOT_DIR . '/config/lib.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
break;
|
||||
case 'source':
|
||||
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/source.json'), true);
|
||||
ConfigValidator::validateSource($file);
|
||||
ksort($file);
|
||||
file_put_contents(ROOT_DIR . '/config/source.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
break;
|
||||
case 'ext':
|
||||
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/ext.json'), true);
|
||||
ConfigValidator::validateExts($file);
|
||||
ksort($file);
|
||||
file_put_contents(ROOT_DIR . '/config/ext.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
break;
|
||||
default:
|
||||
$output->writeln("<error>invalid config name: {$name}</error>");
|
||||
return 1;
|
||||
}
|
||||
$output->writeln('<info>sort success</info>');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user