From 85240c753b345f4282819420f38f145fc7157369 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sat, 31 Dec 2022 22:59:03 +0800 Subject: [PATCH 1/5] add build command --- bin/zhamao | 6 -- bin/zhamao.bat | 2 +- src/ZM/Command/BuildCommand.php | 143 ++++++++++++++++---------------- src/ZM/Command/Command.php | 18 ++++ zhamao | 6 -- 5 files changed, 90 insertions(+), 85 deletions(-) diff --git a/bin/zhamao b/bin/zhamao index c3eb7aaf..3f3c7659 100755 --- a/bin/zhamao +++ b/bin/zhamao @@ -21,12 +21,6 @@ else fi fi -result=$(echo "$1" | grep -E "module|build") - -if [ "$result" != "" ]; then - executable="$executable -d phar.readonly=off" -fi - if [ -f "$(pwd)/src/entry.php" ]; then $executable "$(pwd)/src/entry.php" $@ elif [ -f "$(pwd)/vendor/zhamao/framework/src/entry.php" ]; then diff --git a/bin/zhamao.bat b/bin/zhamao.bat index 1afe4b41..f6c3dbb0 100644 --- a/bin/zhamao.bat +++ b/bin/zhamao.bat @@ -13,7 +13,7 @@ IF /i "%ZM_CUSTOM_PHP_PATH%" neq "" ( echo "* Using system PHP executable" SET executable=php ) -@REM TODO: Phar write support is missing + IF exist src/entry.php ( @REM Run the PHP entry point %executable% src/entry.php %* diff --git a/src/ZM/Command/BuildCommand.php b/src/ZM/Command/BuildCommand.php index b3cb65f3..1834e5c2 100644 --- a/src/ZM/Command/BuildCommand.php +++ b/src/ZM/Command/BuildCommand.php @@ -5,12 +5,10 @@ declare(strict_types=1); namespace ZM\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; +use ZM\Store\FileSystem; -#[AsCommand(name: 'build', description: '将项目构建一个phar包')] +#[AsCommand(name: 'build', description: '将项目构建一个 Phar 包')] class BuildCommand extends Command { use NonPharLoadModeOnly; @@ -20,81 +18,82 @@ class BuildCommand extends Command */ protected function configure() { - $this->setHelp('此功能将会把整个项目打包为phar'); - $this->addOption('target', 'D', InputOption::VALUE_REQUIRED, 'Output Directory | 指定输出目录'); - // ... + $this->setHelp('此功能将会把整个项目打包为 Phar'); + $this->addOption('target', 'D', InputOption::VALUE_REQUIRED, '指定输出文件位置'); } - protected function execute(InputInterface $input, OutputInterface $output): int + protected function handle(): int { - /* TODO - $this->output = $output; - $target_dir = $input->getOption('target') ?? WORKING_DIR; - if (mb_strpos($target_dir, '../')) { - $target_dir = realpath($target_dir); + $this->ensurePharWritable(); + + $target = $this->input->getOption('target') ?? 'zm.phar'; + if (FileSystem::isRelativePath($target)) { + $target = SOURCE_ROOT_DIR . '/' . $target; } - if ($target_dir === false) { - $output->writeln(TermColor::color8(31) . zm_internal_errcode('E00039') . 'Error: No such file or directory (' . $target_dir . ')' . TermColor::RESET); - return 1; - } - $output->writeln('Target: ' . $target_dir); - if (mb_substr($target_dir, -1, 1) !== '/') { - $target_dir .= '/'; - } - if (ini_get('phar.readonly') == 1) { - $output->writeln(TermColor::color8(31) . zm_internal_errcode('E00040') . 'You need to set "phar.readonly" to "Off"!'); - $output->writeln(TermColor::color8(31) . 'See: https://stackoverflow.com/questions/34667606/cant-enable-phar-writing'); - return 1; - } - if (!is_dir($target_dir)) { - $output->writeln(TermColor::color8(31) . zm_internal_errcode('E00039') . "Error: No such file or directory ({$target_dir})" . TermColor::RESET); - return 1; - } - $filename = 'server.phar'; - $this->build($target_dir, $filename); - */ - $output->writeln('Not implemented.'); - return 1; + $this->ensureTargetWritable($target); + $this->comment("目标文件:{$target}"); + + $this->info('正在构建 Phar 包'); + + $this->build($target, LOAD_MODE === LOAD_MODE_VENDOR ? 'src/entry.php' : 'vendor/zhamao/framework/src/entry.php'); + + $this->info('Phar 包构建完成'); + + return self::SUCCESS; } - /* - private function build($target_dir, $filename) - { - @unlink($target_dir . $filename); - $phar = new Phar($target_dir . $filename); - $phar->startBuffering(); - $all = DataProvider::scanDirFiles(DataProvider::getSourceRootDir(), true, true); - - $all = array_filter($all, function ($x) { - $dirs = preg_match('/(^(bin|config|resources|src|vendor)\\/|^(composer\\.json|README\\.md)$)/', $x); - return !($dirs !== 1); - }); - - sort($all); - - $archive_dir = DataProvider::getSourceRootDir(); - $map = []; - - if (class_exists('\\League\\CLImate\\CLImate')) { - $climate = new CLImate(); - $progress = $climate->progress()->total(count($all)); + private function ensurePharWritable(): void + { + if (ini_get('phar.readonly') === '1') { + if (!function_exists('pcntl_exec')) { + $this->error('Phar 处于只读模式,且 pcntl 扩展未加载,无法自动切换到读写模式。'); + $this->error('请修改 php.ini 中的 phar.readonly 为 0,或执行 php -d phar.readonly=0 ' . $_SERVER['PHP_SELF'] . ' build'); + exit(1); } - foreach ($all as $k => $v) { - $map[$v] = $archive_dir . '/' . $v; - if (isset($progress)) { - $progress->current($k + 1, 'Adding ' . $v); - } + // Windows 下无法使用 pcntl_exec + if (DIRECTORY_SEPARATOR === '\\') { + $this->error('Phar 处于只读模式,且当前运行环境为 Windows,无法自动切换到读写模式。'); + $this->error('请修改 php.ini 中的 phar.readonly 为 0,或执行 php -d phar.readonly=0 ' . $_SERVER['PHP_SELF'] . ' build'); + exit(1); + } + $this->info('Phar 处于只读模式,正在尝试切换到读写模式...'); + sleep(1); + $args = array_merge(['php', '-d', 'phar.readonly=0'], $_SERVER['argv']); + if (pcntl_exec('/usr/bin/env', $args) === false) { + $this->error('切换到读写模式失败,请检查环境。'); + exit(1); } - $this->output->write('Building...'); - $phar->buildFromIterator(new ArrayIterator($map)); - $phar->setStub( - "#!/usr/bin/env php\n" . - $phar->createDefaultStub(LOAD_MODE == 0 ? 'src/entry.php' : 'vendor/zhamao/framework/src/entry.php') - ); - $phar->stopBuffering(); - $this->output->writeln(''); - $this->output->writeln('Successfully built. Location: ' . $target_dir . "{$filename}"); - $this->output->writeln('You may use `chmod +x server.phar` to let phar executable with `./` command'); } - */ + } + + private function ensureTargetWritable(string $target): void + { + if (file_exists($target) && !is_writable($target)) { + $this->error('目标文件不可写:' . $target); + exit(1); + } + } + + private function build(string $target, string $entry): void + { + $phar = new \Phar($target, 0, $target); + + $phar->startBuffering(); + $files = FileSystem::scanDirFiles(SOURCE_ROOT_DIR, true, true); + $files = array_filter($files, function ($x) { + $dirs = preg_match('/(^(bin|config|resources|src|vendor)\\/|^(composer\\.json|README\\.md)$)/', $x); + return !($dirs !== 1); + }); + sort($files); + + foreach ($this->progress()->iterate($files) as $file) { + $phar->addFile($file, $file); + } + + $phar->setStub( + '#!/usr/bin/env php' . PHP_EOL . + $phar::createDefaultStub($entry) + ); + $phar->stopBuffering(); + } } diff --git a/src/ZM/Command/Command.php b/src/ZM/Command/Command.php index ebb87910..93144cb8 100644 --- a/src/ZM/Command/Command.php +++ b/src/ZM/Command/Command.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace ZM\Command; +use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\ConsoleSectionOutput; @@ -151,4 +152,21 @@ abstract class Command extends \Symfony\Component\Console\Command\Command exit(self::FAILURE); } } + + /** + * 获取一个进度条实例 + * + * @param int $max 最大进度值,可以稍后再设置 + */ + protected function progress(int $max = 0): ProgressBar + { + $progress = new ProgressBar($this->output, $max); + $progress->setBarCharacter('⚬'); + $progress->setEmptyBarCharacter('⚬'); + $progress->setProgressCharacter('➤'); + $progress->setFormat( + "%current%/%max% [%bar%] %percent:3s%%\n🪅 %estimated:-20s% %memory:20s%" + ); + return $progress; + } } diff --git a/zhamao b/zhamao index c3eb7aaf..3f3c7659 100755 --- a/zhamao +++ b/zhamao @@ -21,12 +21,6 @@ else fi fi -result=$(echo "$1" | grep -E "module|build") - -if [ "$result" != "" ]; then - executable="$executable -d phar.readonly=off" -fi - if [ -f "$(pwd)/src/entry.php" ]; then $executable "$(pwd)/src/entry.php" $@ elif [ -f "$(pwd)/vendor/zhamao/framework/src/entry.php" ]; then From 413d783d53dff02143f6f80562588b3c66a1c7d5 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sat, 31 Dec 2022 23:01:00 +0800 Subject: [PATCH 2/5] use built-in default value --- src/ZM/Command/BuildCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZM/Command/BuildCommand.php b/src/ZM/Command/BuildCommand.php index 1834e5c2..d338e7fe 100644 --- a/src/ZM/Command/BuildCommand.php +++ b/src/ZM/Command/BuildCommand.php @@ -19,14 +19,14 @@ class BuildCommand extends Command protected function configure() { $this->setHelp('此功能将会把整个项目打包为 Phar'); - $this->addOption('target', 'D', InputOption::VALUE_REQUIRED, '指定输出文件位置'); + $this->addOption('target', 'D', InputOption::VALUE_REQUIRED, '指定输出文件位置', 'zm.phar'); } protected function handle(): int { $this->ensurePharWritable(); - $target = $this->input->getOption('target') ?? 'zm.phar'; + $target = $this->input->getOption('target'); if (FileSystem::isRelativePath($target)) { $target = SOURCE_ROOT_DIR . '/' . $target; } From 50d6e75e7109669f72d81067816f74b898338c3d Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sat, 31 Dec 2022 23:57:56 +0800 Subject: [PATCH 3/5] add phar compression --- src/ZM/Command/BuildCommand.php | 29 ++++++++++++++++++++++------- src/ZM/Command/Command.php | 2 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/ZM/Command/BuildCommand.php b/src/ZM/Command/BuildCommand.php index d338e7fe..fa5456a5 100644 --- a/src/ZM/Command/BuildCommand.php +++ b/src/ZM/Command/BuildCommand.php @@ -18,8 +18,9 @@ class BuildCommand extends Command */ protected function configure() { - $this->setHelp('此功能将会把整个项目打包为 Phar'); + $this->setHelp('此功能将会把整个项目打包为 Phar' . PHP_EOL . '默认会启用压缩功能,通过去除文件中的注释和空格,以减小文件大小,但可能增加构建耗时'); $this->addOption('target', 'D', InputOption::VALUE_REQUIRED, '指定输出文件位置', 'zm.phar'); + $this->addOption('no-compress', null, InputOption::VALUE_NONE, '是否不压缩文件,以减小构建耗时'); } protected function handle(): int @@ -33,9 +34,17 @@ class BuildCommand extends Command $this->ensureTargetWritable($target); $this->comment("目标文件:{$target}"); + if (file_exists($target)) { + $this->comment('目标文件已存在,正在删除...'); + unlink($target); + } + $this->info('正在构建 Phar 包'); - $this->build($target, LOAD_MODE === LOAD_MODE_VENDOR ? 'src/entry.php' : 'vendor/zhamao/framework/src/entry.php'); + $this->build( + $target, + LOAD_MODE === LOAD_MODE_VENDOR ? 'src/entry.php' : 'vendor/zhamao/framework/src/entry.php', + ); $this->info('Phar 包构建完成'); @@ -80,14 +89,20 @@ class BuildCommand extends Command $phar->startBuffering(); $files = FileSystem::scanDirFiles(SOURCE_ROOT_DIR, true, true); - $files = array_filter($files, function ($x) { - $dirs = preg_match('/(^(bin|config|resources|src|vendor)\\/|^(composer\\.json|README\\.md)$)/', $x); - return !($dirs !== 1); + // 只打包 bin / config / resources / src / vendor + $files = array_filter($files, function ($file) { + return preg_match('#^(bin|config|resources|src|vendor)/#', $file); }); sort($files); - foreach ($this->progress()->iterate($files) as $file) { - $phar->addFile($file, $file); + if ($this->input->getOption('no-compress')) { + foreach ($this->progress()->iterate($files) as $file) { + $phar->addFile($file, $file); + } + } else { + foreach ($this->progress()->iterate($files) as $file) { + $phar->addFromString($file, php_strip_whitespace($file)); + } } $phar->setStub( diff --git a/src/ZM/Command/Command.php b/src/ZM/Command/Command.php index 93144cb8..df329755 100644 --- a/src/ZM/Command/Command.php +++ b/src/ZM/Command/Command.php @@ -165,7 +165,7 @@ abstract class Command extends \Symfony\Component\Console\Command\Command $progress->setEmptyBarCharacter('⚬'); $progress->setProgressCharacter('➤'); $progress->setFormat( - "%current%/%max% [%bar%] %percent:3s%%\n🪅 %estimated:-20s% %memory:20s%" + "%current%/%max% [%bar%] %percent:3s%%\n🪅 %estimated:-20s% %memory:20s%" . PHP_EOL ); return $progress; } From 3086bcdf6d11f1deda7ff55e89c5d5641c29530d Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sun, 1 Jan 2023 00:18:27 +0800 Subject: [PATCH 4/5] simplify file filtering --- src/ZM/Command/BuildCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ZM/Command/BuildCommand.php b/src/ZM/Command/BuildCommand.php index fa5456a5..4483f381 100644 --- a/src/ZM/Command/BuildCommand.php +++ b/src/ZM/Command/BuildCommand.php @@ -85,13 +85,13 @@ class BuildCommand extends Command private function build(string $target, string $entry): void { - $phar = new \Phar($target, 0, $target); + $phar = new \Phar($target, 0); $phar->startBuffering(); $files = FileSystem::scanDirFiles(SOURCE_ROOT_DIR, true, true); - // 只打包 bin / config / resources / src / vendor + // 只打包 bin / config / resources / src / vendor 目录以及 composer.json / composer.lock / entry.php $files = array_filter($files, function ($file) { - return preg_match('#^(bin|config|resources|src|vendor)/#', $file); + return preg_match('/(^(bin|config|resources|src|vendor)\\/|^(composer\\.json|README\\.md)$)/', $file); }); sort($files); From da1bbee24c9bb536571bf8b8b1a30e572b4ce683 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sun, 1 Jan 2023 16:04:02 +0800 Subject: [PATCH 5/5] add built-in php support --- src/ZM/Command/BuildCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZM/Command/BuildCommand.php b/src/ZM/Command/BuildCommand.php index 4483f381..c296d39f 100644 --- a/src/ZM/Command/BuildCommand.php +++ b/src/ZM/Command/BuildCommand.php @@ -67,8 +67,8 @@ class BuildCommand extends Command } $this->info('Phar 处于只读模式,正在尝试切换到读写模式...'); sleep(1); - $args = array_merge(['php', '-d', 'phar.readonly=0'], $_SERVER['argv']); - if (pcntl_exec('/usr/bin/env', $args) === false) { + $args = array_merge(['-d', 'phar.readonly=0'], $_SERVER['argv']); + if (pcntl_exec(PHP_BINARY, $args) === false) { $this->error('切换到读写模式失败,请检查环境。'); exit(1); }