2023-03-18 17:32:21 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace SPC\command;
|
|
|
|
|
|
|
|
|
|
use SPC\builder\BuilderProvider;
|
|
|
|
|
use SPC\exception\ExceptionHandler;
|
2023-03-29 21:39:36 +08:00
|
|
|
use SPC\exception\WrongUsageException;
|
2023-10-14 11:33:17 +08:00
|
|
|
use SPC\store\FileSystem;
|
2023-08-02 22:14:45 +08:00
|
|
|
use SPC\store\SourcePatcher;
|
2023-03-18 17:32:21 +08:00
|
|
|
use SPC\util\DependencyUtil;
|
2023-04-15 18:46:46 +08:00
|
|
|
use SPC\util\LicenseDumper;
|
2023-04-22 17:45:43 +08:00
|
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
2023-03-18 17:32:21 +08:00
|
|
|
use Symfony\Component\Console\Input\InputArgument;
|
|
|
|
|
use Symfony\Component\Console\Input\InputOption;
|
2023-04-23 20:31:58 +08:00
|
|
|
use ZM\Logger\ConsoleColor;
|
2023-03-18 17:32:21 +08:00
|
|
|
|
2023-12-10 18:28:15 +08:00
|
|
|
#[AsCommand('build', 'build PHP')]
|
2023-03-18 17:32:21 +08:00
|
|
|
class BuildCliCommand extends BuildCommand
|
|
|
|
|
{
|
2023-08-20 19:51:45 +08:00
|
|
|
public function configure(): void
|
2023-03-18 17:32:21 +08:00
|
|
|
{
|
|
|
|
|
$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', '');
|
2024-02-06 16:06:09 +08:00
|
|
|
$this->addOption('build-micro', null, null, 'Build micro SAPI');
|
|
|
|
|
$this->addOption('build-cli', null, null, 'Build cli SAPI');
|
|
|
|
|
$this->addOption('build-fpm', null, null, 'Build fpm SAPI');
|
|
|
|
|
$this->addOption('build-embed', null, null, 'Build embed SAPI');
|
|
|
|
|
$this->addOption('build-all', null, null, 'Build all SAPI');
|
2023-05-10 21:59:33 +08:00
|
|
|
$this->addOption('no-strip', null, null, 'build without strip, in order to debug and load external extensions');
|
2023-05-17 22:19:28 +08:00
|
|
|
$this->addOption('enable-zts', null, null, 'enable ZTS support');
|
2023-10-16 14:05:07 +02:00
|
|
|
$this->addOption('disable-opcache-jit', null, null, 'disable opcache jit');
|
2023-08-02 22:14:45 +08:00
|
|
|
$this->addOption('with-hardcoded-ini', 'I', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Patch PHP source code, inject hardcoded INI');
|
2024-02-16 01:28:10 +08:00
|
|
|
$this->addOption('with-micro-fake-cli', null, null, 'Let phpmicro\'s PHP_SAPI use "cli" instead of "micro"');
|
2023-12-10 18:28:15 +08:00
|
|
|
$this->addOption('with-suggested-libs', 'L', null, 'Build with suggested libs for selected exts and libs');
|
|
|
|
|
$this->addOption('with-suggested-exts', 'E', null, 'Build with suggested extensions for selected exts');
|
2024-01-03 15:57:05 +08:00
|
|
|
$this->addOption('with-added-patch', 'P', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Inject patch script outside');
|
2024-01-29 10:04:21 +08:00
|
|
|
$this->addOption('without-micro-ext-test', null, null, 'Disable phpmicro with extension test code');
|
2024-02-19 12:17:03 +08:00
|
|
|
|
|
|
|
|
$this->addOption('with-upx-pack', null, null, 'Compress / pack binary using UPX tool (linux/windows only)');
|
2024-02-29 15:35:02 +08:00
|
|
|
|
|
|
|
|
if (PHP_OS_FAMILY === 'Windows') {
|
|
|
|
|
$this->addOption('with-micro-logo', null, InputOption::VALUE_REQUIRED, 'Use custom .ico for micro.sfx');
|
|
|
|
|
}
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
|
|
|
|
|
2023-04-22 17:45:43 +08:00
|
|
|
public function handle(): int
|
2023-03-18 17:32:21 +08:00
|
|
|
{
|
2023-08-20 19:51:45 +08:00
|
|
|
// transform string to array
|
2023-04-22 17:45:43 +08:00
|
|
|
$libraries = array_map('trim', array_filter(explode(',', $this->getOption('with-libs'))));
|
2023-08-20 19:51:45 +08:00
|
|
|
// transform string to array
|
2023-04-22 17:45:43 +08:00
|
|
|
$extensions = array_map('trim', array_filter(explode(',', $this->getArgument('extensions'))));
|
2023-03-18 17:32:21 +08:00
|
|
|
|
2023-12-10 18:28:15 +08:00
|
|
|
// parse rule with options
|
|
|
|
|
$rule = $this->parseRules();
|
|
|
|
|
|
2023-04-23 20:31:58 +08:00
|
|
|
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>");
|
2023-08-21 09:30:46 +02:00
|
|
|
$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>");
|
2023-08-06 10:43:20 +08:00
|
|
|
return static::FAILURE;
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
2024-02-18 14:25:42 +08:00
|
|
|
if ($rule === BUILD_TARGET_ALL) {
|
|
|
|
|
logger()->warning('--build-all option makes `--no-strip` always true, be aware!');
|
|
|
|
|
}
|
2024-02-29 15:35:02 +08:00
|
|
|
if (($rule & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO && $this->getOption('with-micro-logo')) {
|
|
|
|
|
$logo = $this->getOption('with-micro-logo');
|
|
|
|
|
if (!file_exists($logo)) {
|
|
|
|
|
logger()->error('Logo file ' . $logo . ' not exist !');
|
|
|
|
|
return static::FAILURE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-19 12:17:03 +08:00
|
|
|
// Check upx
|
|
|
|
|
$suffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : '';
|
|
|
|
|
if ($this->getOption('with-upx-pack')) {
|
|
|
|
|
// only available for linux for now
|
|
|
|
|
if (!in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
|
|
|
|
|
logger()->error('UPX is only available on Linux and Windows!');
|
|
|
|
|
return static::FAILURE;
|
|
|
|
|
}
|
|
|
|
|
// need to install this manually
|
|
|
|
|
if (!file_exists(PKG_ROOT_PATH . '/bin/upx' . $suffix)) {
|
|
|
|
|
global $argv;
|
|
|
|
|
logger()->error('upx does not exist, please install it first:');
|
|
|
|
|
logger()->error('');
|
|
|
|
|
logger()->error("\t" . $argv[0] . ' install-pkg upx');
|
|
|
|
|
logger()->error('');
|
|
|
|
|
return static::FAILURE;
|
|
|
|
|
}
|
|
|
|
|
// exclusive with no-strip
|
|
|
|
|
if ($this->getOption('no-strip')) {
|
|
|
|
|
logger()->warning('--with-upx-pack conflicts with --no-strip, --no-strip won\'t work!');
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-18 17:32:21 +08:00
|
|
|
try {
|
2023-08-20 19:51:45 +08:00
|
|
|
// create builder
|
2023-04-22 17:45:43 +08:00
|
|
|
$builder = BuilderProvider::makeBuilderByInput($this->input);
|
2024-02-06 16:06:09 +08:00
|
|
|
$include_suggest_ext = $this->getOption('with-suggested-exts');
|
|
|
|
|
$include_suggest_lib = $this->getOption('with-suggested-libs');
|
|
|
|
|
[$extensions, $libraries, $not_included] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
|
2023-12-10 18:28:15 +08:00
|
|
|
|
|
|
|
|
// print info
|
|
|
|
|
$indent_texts = [
|
|
|
|
|
'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')',
|
|
|
|
|
'Build SAPI' => $builder->getBuildTypeName($rule),
|
|
|
|
|
'Extensions (' . count($extensions) . ')' => implode(', ', $extensions),
|
|
|
|
|
'Libraries (' . count($libraries) . ')' => implode(', ', $libraries),
|
|
|
|
|
'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes',
|
|
|
|
|
'Enable ZTS' => $builder->getOption('enable-zts') ? 'yes' : 'no',
|
|
|
|
|
];
|
|
|
|
|
if (!empty($this->input->getOption('with-hardcoded-ini'))) {
|
|
|
|
|
$indent_texts['Hardcoded INI'] = $this->input->getOption('with-hardcoded-ini');
|
|
|
|
|
}
|
2024-02-16 18:57:32 +08:00
|
|
|
if ($this->input->getOption('disable-opcache-jit')) {
|
|
|
|
|
$indent_texts['Opcache JIT'] = 'disabled';
|
|
|
|
|
}
|
2024-02-19 12:17:03 +08:00
|
|
|
if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
|
|
|
|
|
$indent_texts['UPX Pack'] = 'enabled';
|
|
|
|
|
$builder->setOption('upx-exec', FileSystem::convertPath(PKG_ROOT_PATH . '/bin/upx' . $suffix));
|
|
|
|
|
}
|
2024-02-16 18:57:32 +08:00
|
|
|
try {
|
|
|
|
|
$ver = $builder->getPHPVersion();
|
|
|
|
|
$indent_texts['PHP Version'] = $ver;
|
|
|
|
|
} catch (\Throwable) {
|
|
|
|
|
if (($ver = $builder->getPHPVersionFromArchive()) !== false) {
|
|
|
|
|
$indent_texts['PHP Version'] = $ver;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-10 18:28:15 +08:00
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
if (!empty($not_included)) {
|
2024-02-16 18:57:32 +08:00
|
|
|
$indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included);
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
2024-02-16 18:57:32 +08:00
|
|
|
$this->printFormatInfo($indent_texts);
|
|
|
|
|
logger()->notice('Build will start after 2s ...');
|
2023-03-18 17:32:21 +08:00
|
|
|
sleep(2);
|
2023-12-10 18:28:15 +08:00
|
|
|
|
2023-10-14 11:33:17 +08:00
|
|
|
if ($this->input->getOption('with-clean')) {
|
|
|
|
|
logger()->info('Cleaning source dir...');
|
|
|
|
|
FileSystem::removeDir(SOURCE_PATH);
|
|
|
|
|
}
|
2023-08-20 19:51:45 +08:00
|
|
|
// compile libraries
|
2023-03-18 17:32:21 +08:00
|
|
|
$builder->buildLibs($libraries);
|
2023-08-20 19:51:45 +08:00
|
|
|
// check extensions
|
2023-03-18 17:32:21 +08:00
|
|
|
$builder->proveExts($extensions);
|
2023-08-20 19:51:45 +08:00
|
|
|
|
2023-08-02 22:14:45 +08:00
|
|
|
// Process -I option
|
|
|
|
|
$custom_ini = [];
|
|
|
|
|
foreach ($this->input->getOption('with-hardcoded-ini') as $value) {
|
|
|
|
|
[$source_name, $ini_value] = explode('=', $value, 2);
|
|
|
|
|
$custom_ini[$source_name] = $ini_value;
|
|
|
|
|
logger()->info('Adding hardcoded INI [' . $source_name . ' = ' . $ini_value . ']');
|
|
|
|
|
}
|
|
|
|
|
if (!empty($custom_ini)) {
|
|
|
|
|
SourcePatcher::patchHardcodedINI($custom_ini);
|
|
|
|
|
}
|
2023-08-20 19:51:45 +08:00
|
|
|
|
2024-02-04 10:56:29 +08:00
|
|
|
// add static-php-cli.version to main.c, in order to debug php failure more easily
|
|
|
|
|
SourcePatcher::patchSPCVersionToPHP($this->getApplication()->getVersion());
|
|
|
|
|
|
2023-08-20 19:51:45 +08:00
|
|
|
// start to build
|
|
|
|
|
$builder->buildPHP($rule);
|
|
|
|
|
|
|
|
|
|
// compile stopwatch :P
|
2023-03-18 17:32:21 +08:00
|
|
|
$time = round(microtime(true) - START_TIME, 3);
|
|
|
|
|
logger()->info('Build complete, used ' . $time . ' s !');
|
2023-08-20 19:51:45 +08:00
|
|
|
|
|
|
|
|
// ---------- When using bin/spc-alpine-docker, the build root path is different from the host system ----------
|
2023-04-30 12:42:19 +08:00
|
|
|
$build_root_path = BUILD_ROOT_PATH;
|
|
|
|
|
$cwd = getcwd();
|
|
|
|
|
$fixed = '';
|
|
|
|
|
if (!empty(getenv('SPC_FIX_DEPLOY_ROOT'))) {
|
|
|
|
|
str_replace($cwd, '', $build_root_path);
|
|
|
|
|
$build_root_path = getenv('SPC_FIX_DEPLOY_ROOT') . $build_root_path;
|
|
|
|
|
$fixed = ' (host system)';
|
|
|
|
|
}
|
2023-04-23 20:31:58 +08:00
|
|
|
if (($rule & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
|
2024-01-10 21:08:25 +08:00
|
|
|
$win_suffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : '';
|
|
|
|
|
$path = FileSystem::convertPath("{$build_root_path}/bin/php{$win_suffix}");
|
|
|
|
|
logger()->info("Static php binary path{$fixed}: {$path}");
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
2023-04-23 20:31:58 +08:00
|
|
|
if (($rule & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
|
2024-01-10 21:08:25 +08:00
|
|
|
$path = FileSystem::convertPath("{$build_root_path}/bin/micro.sfx");
|
|
|
|
|
logger()->info("phpmicro binary path{$fixed}: {$path}");
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
2024-01-10 21:08:25 +08:00
|
|
|
if (($rule & BUILD_TARGET_FPM) === BUILD_TARGET_FPM && PHP_OS_FAMILY !== 'Windows') {
|
|
|
|
|
$path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm");
|
|
|
|
|
logger()->info("Static php-fpm binary path{$fixed}: {$path}");
|
2023-04-23 20:31:58 +08:00
|
|
|
}
|
2023-08-20 19:51:45 +08:00
|
|
|
|
|
|
|
|
// export metadata
|
2023-04-15 18:46:46 +08:00
|
|
|
file_put_contents(BUILD_ROOT_PATH . '/build-extensions.json', json_encode($extensions, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
|
|
|
file_put_contents(BUILD_ROOT_PATH . '/build-libraries.json', json_encode($libraries, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
2023-08-20 19:51:45 +08:00
|
|
|
// export licenses
|
2023-04-15 18:46:46 +08:00
|
|
|
$dumper = new LicenseDumper();
|
|
|
|
|
$dumper->addExts($extensions)->addLibs($libraries)->addSources(['php-src'])->dump(BUILD_ROOT_PATH . '/license');
|
2024-01-10 21:08:25 +08:00
|
|
|
$path = FileSystem::convertPath("{$build_root_path}/license/");
|
|
|
|
|
logger()->info("License path{$fixed}: {$path}");
|
2023-08-06 10:43:20 +08:00
|
|
|
return static::SUCCESS;
|
2023-03-29 21:39:36 +08:00
|
|
|
} catch (WrongUsageException $e) {
|
2023-08-20 19:51:45 +08:00
|
|
|
// WrongUsageException is not an exception, it's a user error, so we just print the error message
|
2023-03-29 21:39:36 +08:00
|
|
|
logger()->critical($e->getMessage());
|
2023-08-06 10:43:20 +08:00
|
|
|
return static::FAILURE;
|
2023-03-18 17:32:21 +08:00
|
|
|
} catch (\Throwable $e) {
|
2023-04-22 17:45:43 +08:00
|
|
|
if ($this->getOption('debug')) {
|
2023-03-18 17:32:21 +08:00
|
|
|
ExceptionHandler::getInstance()->handle($e);
|
|
|
|
|
} else {
|
2023-03-21 00:21:17 +08:00
|
|
|
logger()->critical('Build failed with ' . get_class($e) . ': ' . $e->getMessage());
|
|
|
|
|
logger()->critical('Please check with --debug option to see more details.');
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
2023-08-06 10:43:20 +08:00
|
|
|
return static::FAILURE;
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
|
|
|
|
}
|
2023-12-10 18:28:15 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parse build options to rule int.
|
|
|
|
|
*/
|
|
|
|
|
private function parseRules(): int
|
|
|
|
|
{
|
|
|
|
|
$rule = BUILD_TARGET_NONE;
|
|
|
|
|
$rule |= ($this->getOption('build-cli') ? BUILD_TARGET_CLI : BUILD_TARGET_NONE);
|
|
|
|
|
$rule |= ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE);
|
|
|
|
|
$rule |= ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : BUILD_TARGET_NONE);
|
|
|
|
|
$rule |= ($this->getOption('build-embed') ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE);
|
|
|
|
|
$rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE);
|
|
|
|
|
return $rule;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function printFormatInfo(array $indent_texts): void
|
|
|
|
|
{
|
|
|
|
|
// calculate space count for every line
|
|
|
|
|
$maxlen = 0;
|
|
|
|
|
foreach ($indent_texts as $k => $v) {
|
|
|
|
|
$maxlen = max(strlen($k), $maxlen);
|
|
|
|
|
}
|
|
|
|
|
foreach ($indent_texts as $k => $v) {
|
|
|
|
|
if (is_string($v)) {
|
|
|
|
|
/* @phpstan-ignore-next-line */
|
|
|
|
|
logger()->info($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($v));
|
|
|
|
|
} elseif (is_array($v) && !is_assoc_array($v)) {
|
|
|
|
|
$first = array_shift($v);
|
|
|
|
|
/* @phpstan-ignore-next-line */
|
|
|
|
|
logger()->info($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($first));
|
|
|
|
|
foreach ($v as $vs) {
|
|
|
|
|
/* @phpstan-ignore-next-line */
|
|
|
|
|
logger()->info(str_pad('', $maxlen + 2) . ConsoleColor::yellow($vs));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|