mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-17 20:34:51 +08:00
Add command to dump required PHP extensions based on vendor/composer/… (#599)
* Add command to dump required PHP extensions based on vendor/composer/installed.json, composer.lock, composer.json (in this order) * remove unused use * missing translation * Adjust dump-extensions * Add docs for dump-extension command --------- Co-authored-by: crazywhalecc <jesse2061@outlook.com>
This commit is contained in:
parent
34934368a2
commit
6b227d88ac
@ -194,6 +194,8 @@ Basic usage for building php with some extensions:
|
||||
|
||||
# fetch all libraries
|
||||
./bin/spc download --all
|
||||
# dump a list of extensions required by your project
|
||||
./bin/spc dump-extensions
|
||||
# only fetch necessary sources by needed extensions (recommended)
|
||||
./bin/spc download --for-extensions="openssl,pcntl,mbstring,pdo_sqlite"
|
||||
# download pre-built libraries first (save time for compiling dependencies)
|
||||
|
||||
@ -397,6 +397,31 @@ manually unpack and copy the package to a specified location, and we can use com
|
||||
bin/spc extract php-src,libxml2
|
||||
```
|
||||
|
||||
## Command - dump-extensions
|
||||
|
||||
Use the command `bin/spc dump-extensions` to export required extensions of the current project.
|
||||
|
||||
```bash
|
||||
# Print the extension list of the project, pass in the root directory of the project containing composer.json
|
||||
bin/spc dump-extensions /path/to/your/project/
|
||||
|
||||
# Print the extension list of the project, excluding development dependencies
|
||||
bin/spc dump-extensions /path-to/tour/project/ --no-dev
|
||||
|
||||
# Output in the extension list format acceptable to the spc command (comma separated)
|
||||
bin/spc dump-extensions /path-to/tour/project/ --format=text
|
||||
|
||||
# Output as a JSON list
|
||||
bin/spc dump-extensions /path-to/tour/project/ --format=json
|
||||
|
||||
# When the project does not have any extensions, output the specified extension combination instead of returning failure
|
||||
bin/spc dump-extensions /path-to/your/project/ --no-ext-output=mbstring,posix,pcntl,phar
|
||||
|
||||
# Do not exclude extensions not supported by spc when outputting
|
||||
bin/spc dump-extensions /path/to/your/project/ --no-spc-filter
|
||||
```
|
||||
It should be noted that the project directory must contain the `vendor/installed.json` and `composer.lock` files, otherwise they cannot be found normally.
|
||||
|
||||
## Dev Command - dev
|
||||
|
||||
Debug commands refer to a collection of commands that can assist in outputting some information
|
||||
|
||||
@ -353,6 +353,32 @@ memory_limit=1G
|
||||
bin/spc extract php-src,libxml2
|
||||
```
|
||||
|
||||
## 命令 dump-extensions - 导出项目扩展依赖
|
||||
|
||||
使用命令 `bin/spc dump-extensions` 可以导出当前项目的扩展依赖。
|
||||
|
||||
```bash
|
||||
# 打印项目的扩展列表,传入项目包含composer.json的根目录
|
||||
bin/spc dump-extensions /path/to/your/project/
|
||||
|
||||
# 打印项目的扩展列表,不包含开发依赖
|
||||
bin/spc dump-extensions /path-to/tour/project/ --no-dev
|
||||
|
||||
# 输出为 spc 命令可接受的扩展列表格式(逗号分割)
|
||||
bin/spc dump-extensions /path-to/tour/project/ --format=text
|
||||
|
||||
# 输出为 JSON 列表
|
||||
bin/spc dump-extensions /path-to/tour/project/ --format=json
|
||||
|
||||
# 当项目没有任何扩展时,输出指定扩展组合,而不是返回失败
|
||||
bin/spc dump-extensions /path-to/your/project/ --no-ext-output=mbstring,posix,pcntl,phar
|
||||
|
||||
# 输出时不排除 spc 不支持的扩展
|
||||
bin/spc dump-extensions /path/to/your/project/ --no-spc-filter
|
||||
```
|
||||
|
||||
需要注意的是,项目的目录下必须包含 `vendor/installed.json` 和 `composer.lock` 文件,否则无法正常获取。
|
||||
|
||||
## 调试命令 dev - 调试命令集合
|
||||
|
||||
调试命令指的是你在使用 static-php-cli 构建 PHP 或改造、增强 static-php-cli 项目本身的时候,可以辅助输出一些信息的命令集合。
|
||||
|
||||
@ -18,6 +18,7 @@ use SPC\command\dev\PhpVerCommand;
|
||||
use SPC\command\dev\SortConfigCommand;
|
||||
use SPC\command\DoctorCommand;
|
||||
use SPC\command\DownloadCommand;
|
||||
use SPC\command\DumpExtensionsCommand;
|
||||
use SPC\command\DumpLicenseCommand;
|
||||
use SPC\command\ExtractCommand;
|
||||
use SPC\command\InstallPkgCommand;
|
||||
@ -54,6 +55,7 @@ final class ConsoleApplication extends Application
|
||||
new MicroCombineCommand(),
|
||||
new SwitchPhpVersionCommand(),
|
||||
new SPCConfigCommand(),
|
||||
new DumpExtensionsCommand(),
|
||||
|
||||
// Dev commands
|
||||
new AllExtCommand(),
|
||||
|
||||
@ -154,24 +154,24 @@ abstract class BaseCommand extends Command
|
||||
/**
|
||||
* Parse extension list from string, replace alias and filter internal extensions.
|
||||
*
|
||||
* @param string $ext_list Extension string list, e.g. "mbstring,posix,sockets"
|
||||
* @param array|string $ext_list Extension string list, e.g. "mbstring,posix,sockets" or array
|
||||
*/
|
||||
protected function parseExtensionList(string $ext_list): array
|
||||
protected function parseExtensionList(array|string $ext_list): array
|
||||
{
|
||||
// replace alias
|
||||
$ls = array_map(function ($x) {
|
||||
$lower = strtolower(trim($x));
|
||||
if (isset(SPC_EXTENSION_ALIAS[$lower])) {
|
||||
logger()->notice("Extension [{$lower}] is an alias of [" . SPC_EXTENSION_ALIAS[$lower] . '], it will be replaced.');
|
||||
logger()->debug("Extension [{$lower}] is an alias of [" . SPC_EXTENSION_ALIAS[$lower] . '], it will be replaced.');
|
||||
return SPC_EXTENSION_ALIAS[$lower];
|
||||
}
|
||||
return $lower;
|
||||
}, explode(',', $ext_list));
|
||||
}, is_array($ext_list) ? $ext_list : explode(',', $ext_list));
|
||||
|
||||
// filter internals
|
||||
return array_values(array_filter($ls, function ($x) {
|
||||
if (in_array($x, SPC_INTERNAL_EXTENSIONS)) {
|
||||
logger()->warning("Extension [{$x}] is an builtin extension, it will be ignored.");
|
||||
logger()->debug("Extension [{$x}] is an builtin extension, it will be ignored.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
160
src/SPC/command/DumpExtensionsCommand.php
Normal file
160
src/SPC/command/DumpExtensionsCommand.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use SPC\store\FileSystem;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'dump-extensions', description: 'Determines the required php extensions')]
|
||||
class DumpExtensionsCommand extends BaseCommand
|
||||
{
|
||||
protected bool $no_motd = true;
|
||||
|
||||
public function configure(): void
|
||||
{
|
||||
// path to project files or specific composer file
|
||||
$this->addArgument('path', InputArgument::OPTIONAL, 'Path to project root', '.');
|
||||
$this->addOption('format', 'F', InputOption::VALUE_REQUIRED, 'Parsed output format', 'default');
|
||||
// output zero extension replacement rather than exit as failure
|
||||
$this->addOption('no-ext-output', 'N', InputOption::VALUE_REQUIRED, 'When no extensions found, output default combination (comma separated)');
|
||||
// no dev
|
||||
$this->addOption('no-dev', null, null, 'Do not include dev dependencies');
|
||||
// no spc filter
|
||||
$this->addOption('no-spc-filter', 'S', null, 'Do not use SPC filter to determine the required extensions');
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$path = FileSystem::convertPath($this->getArgument('path'));
|
||||
|
||||
$path_installed = FileSystem::convertPath(rtrim($path, '/\\') . '/vendor/composer/installed.json');
|
||||
$path_lock = FileSystem::convertPath(rtrim($path, '/\\') . '/composer.lock');
|
||||
|
||||
$ext_installed = $this->extractFromInstalledJson($path_installed, !$this->getOption('no-dev'));
|
||||
if ($ext_installed === null) {
|
||||
if ($this->getOption('format') === 'default') {
|
||||
$this->output->writeln('<comment>vendor/composer/installed.json load failed, skipped</comment>');
|
||||
}
|
||||
$ext_installed = [];
|
||||
}
|
||||
|
||||
$ext_lock = $this->extractFromComposerLock($path_lock, !$this->getOption('no-dev'));
|
||||
if ($ext_lock === null) {
|
||||
$this->output->writeln('<error>composer.lock load failed</error>');
|
||||
return static::FAILURE;
|
||||
}
|
||||
|
||||
$extensions = array_unique(array_merge($ext_installed, $ext_lock));
|
||||
sort($extensions);
|
||||
|
||||
if (empty($extensions)) {
|
||||
if ($this->getOption('no-ext-output')) {
|
||||
$this->outputExtensions(explode(',', $this->getOption('no-ext-output')));
|
||||
return static::SUCCESS;
|
||||
}
|
||||
$this->output->writeln('<error>No extensions found</error>');
|
||||
return static::FAILURE;
|
||||
}
|
||||
|
||||
$this->outputExtensions($extensions);
|
||||
return static::SUCCESS;
|
||||
}
|
||||
|
||||
private function filterExtensions(array $requirements): array
|
||||
{
|
||||
return array_map(
|
||||
fn ($key) => substr($key, 4),
|
||||
array_keys(
|
||||
array_filter($requirements, function ($key) {
|
||||
return str_starts_with($key, 'ext-');
|
||||
}, ARRAY_FILTER_USE_KEY)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function loadJson(string $file): array|bool
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents($file), true);
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function extractFromInstalledJson(string $file, bool $include_dev = true): ?array
|
||||
{
|
||||
if (!($data = $this->loadJson($file))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$packages = $data['packages'] ?? [];
|
||||
|
||||
if (!$include_dev) {
|
||||
$packages = array_filter($packages, fn ($package) => !in_array($package['name'], $data['dev-package-names'] ?? []));
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
...array_map(fn ($x) => isset($x['require']) ? $this->filterExtensions($x['require']) : [], $packages)
|
||||
);
|
||||
}
|
||||
|
||||
private function extractFromComposerLock(string $file, bool $include_dev = true): ?array
|
||||
{
|
||||
if (!($data = $this->loadJson($file))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get packages ext
|
||||
$packages = $data['packages'] ?? [];
|
||||
$exts = array_merge(
|
||||
...array_map(fn ($package) => $this->filterExtensions($package['require'] ?? []), $packages)
|
||||
);
|
||||
|
||||
// get dev packages ext
|
||||
if ($include_dev) {
|
||||
$packages = $data['packages-dev'] ?? [];
|
||||
$exts = array_merge(
|
||||
$exts,
|
||||
...array_map(fn ($package) => $this->filterExtensions($package['require'] ?? []), $packages)
|
||||
);
|
||||
}
|
||||
|
||||
// get require ext
|
||||
$platform = $data['platform'] ?? [];
|
||||
$exts = array_merge($exts, $this->filterExtensions($platform));
|
||||
|
||||
// get require-dev ext
|
||||
if ($include_dev) {
|
||||
$platform = $data['platform-dev'] ?? [];
|
||||
$exts = array_merge($exts, $this->filterExtensions($platform));
|
||||
}
|
||||
|
||||
return $exts;
|
||||
}
|
||||
|
||||
private function outputExtensions(array $extensions): void
|
||||
{
|
||||
if (!$this->getOption('no-spc-filter')) {
|
||||
$extensions = $this->parseExtensionList($extensions);
|
||||
}
|
||||
switch ($this->getOption('format')) {
|
||||
case 'json':
|
||||
$this->output->writeln(json_encode($extensions, JSON_PRETTY_PRINT));
|
||||
break;
|
||||
case 'text':
|
||||
$this->output->writeln(implode(',', $extensions));
|
||||
break;
|
||||
default:
|
||||
$this->output->writeln('<info>Required PHP extensions' . ($this->getOption('no-dev') ? ' (without dev)' : '') . ':</info>');
|
||||
$this->output->writeln(implode(',', $extensions));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user