From b1aea186efef5a3cf4541ff44bb4999e91d8939c Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 1 Feb 2023 16:32:09 +0800 Subject: [PATCH] add plugin:pack command and pack events --- src/ZM/Command/Plugin/PluginPackCommand.php | 8 +- src/ZM/Plugin/PluginManager.php | 83 ++++++++++++++++++--- src/ZM/Plugin/PluginMeta.php | 4 +- src/ZM/Plugin/Traits/PluginPackTrait.php | 51 +++++++++++++ src/ZM/Plugin/ZMPlugin.php | 1 + 5 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 src/ZM/Plugin/Traits/PluginPackTrait.php diff --git a/src/ZM/Command/Plugin/PluginPackCommand.php b/src/ZM/Command/Plugin/PluginPackCommand.php index f5e342c9..d28b8fec 100644 --- a/src/ZM/Command/Plugin/PluginPackCommand.php +++ b/src/ZM/Command/Plugin/PluginPackCommand.php @@ -16,6 +16,7 @@ class PluginPackCommand extends PluginCommand protected function configure() { $this->addArgument('name', InputArgument::REQUIRED, '要打包的插件名称'); + $this->addOption('build-dir', 'D', InputOption::VALUE_REQUIRED, '指定输出文件位置', WORKING_DIR . '/build'); // 下面是辅助用的,和 server:start 一样 $this->addOption('config-dir', null, InputOption::VALUE_REQUIRED, '指定其他配置文件目录'); @@ -27,7 +28,12 @@ class PluginPackCommand extends PluginCommand protected function handle(): int { try { - PluginManager::packPlugin($this->input->getArgument('name')); + $outpupt = PluginManager::packPlugin( + plugin_name: $this->input->getArgument('name'), + build_dir: $this->input->getOption('build-dir'), + command_context: $this + ); + $this->info("插件打包完成,输出文件:{$outpupt}"); } catch (PluginException $e) { $this->error($e->getMessage()); } diff --git a/src/ZM/Plugin/PluginManager.php b/src/ZM/Plugin/PluginManager.php index ae5d575c..7534b8be 100644 --- a/src/ZM/Plugin/PluginManager.php +++ b/src/ZM/Plugin/PluginManager.php @@ -8,8 +8,10 @@ use Jelix\Version\VersionComparator; use ZM\Annotation\AnnotationMap; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Framework\BindEvent; +use ZM\Command\Command; use ZM\Exception\PluginException; use ZM\Store\FileSystem; +use ZM\Store\PharHelper; class PluginManager { @@ -270,19 +272,80 @@ class PluginManager * * @throws PluginException */ - public static function packPlugin(string $name): string + public static function packPlugin(string $plugin_name, string $build_dir, ?Command $command_context = null): string { // 必须是源码模式才行 - if (!isset(self::$plugins[$name]) || self::$plugins[$name]->getPluginType() !== ZM_PLUGIN_TYPE_SOURCE) { - throw new PluginException("没有找到名字为 {$name} 的插件(要打包的插件必须是源码模式)。"); + if (!isset(self::$plugins[$plugin_name]) || self::$plugins[$plugin_name]->getPluginType() !== ZM_PLUGIN_TYPE_SOURCE) { + throw new PluginException("没有找到名字为 {$plugin_name} 的插件(要打包的插件必须是源码模式)。"); + } + try { + // 创建目录 + FileSystem::createDir($build_dir); + $plugin = self::$plugins[$plugin_name]; + // 插件目录 + $dir = $plugin->getRootDir(); + // 先判断是不是可写的 + PharHelper::ensurePharWritable(); + // 拼接 phar 名称,通过插件名和版本号(如果没有版本号则使用 1.0-dev 作为版本号) + $phar_name = $plugin->getName() . '_' . $plugin->getVersion() . '.phar'; + $phar_name = zm_dir($build_dir . '/' . $phar_name); + // 判断文件如果存在的话是否是可写的 + PharHelper::ensurePharFileWritable($phar_name); + // 文件存在先删除 + if (file_exists($phar_name)) { + $command_context?->info('Phar 文件 ' . $phar_name . ' 已存在,删除中...'); + unlink($phar_name); + } + // 先执行一些打包前检查的 bootstrap + // 1. 检查插件是否引用了 require-dev 的内容(检查 --no-dev) + if (file_exists($dir . '/composer.json') && file_exists($dir . '/vendor/composer/installed.json')) { + $json = json_decode(file_get_contents($dir . '/vendor/composer/installed.json'), true); + if (!isset($json['dev'])) { + $command_context?->error('插件的 Composer 未正确配置,忽略检查 dev 模式!'); + } elseif ($json['dev'] === true) { + throw new PluginException( + "插件的 Composer 配置了 dev 模式,但是打包时没有使用 --no-dev 选项,无法打包\n" . + '请先进入插件目录,执行 composer update --no-dev' + ); + } + } + // 创建 Phar 对象 + $phar = new \Phar($phar_name, 0); + // 调用插件的打包的用户自定义前置方法 + $plugin->getEntity()?->emitPack(); + // 扫描插件目录 + $dir_list = FileSystem::scanDirFiles($dir, true, true); + if ($command_context instanceof Command) { + $dir_list = $command_context->progress()->iterate($dir_list); + } + $file_added = 0; + $file_ignored = 0; + foreach ($dir_list as $v) { + // 过滤文件 + if ($plugin->getEntity()?->emitFilterPack($v) === false) { + ++$file_ignored; + continue; + } + // 添加文件 + $phar->addFromString($v, php_strip_whitespace(zm_dir($dir . '/' . $v))); + ++$file_added; + } + // 找有没有 main,没有 main 就不添加 stub + $main = (json_decode(file_get_contents($dir . '/zmplugin.json'), true)['main'] ?? 'main.php'); + if (file_exists(zm_dir($dir . '/' . $main)) && $phar->offsetExists($main)) { + $command_context?->info('设置插件默认入口文件 ' . $main); + $phar->setStub($phar->setDefaultStub($main)); + } else { + $phar->setStub('stopBuffering(); + // 输出结果 + $command_context?->info("共添加 {$file_added} 个文件" . ($file_ignored > 0 ? ",忽略 {$file_ignored} 个文件" : '')); + return $phar_name; + } catch (\PharException $e) { + throw new PluginException("插件 {$plugin_name} 打包失败,原因为 Phar 异常:\n" . $e->getMessage(), previous: $e); } - - $plugin = self::$plugins[$name]; - // 插件目录 - $dir = $plugin->getRootDir(); - // TODO: 写到这了 - // 插件加载方式判断 - return ''; } /** diff --git a/src/ZM/Plugin/PluginMeta.php b/src/ZM/Plugin/PluginMeta.php index f282fa39..381a3eec 100644 --- a/src/ZM/Plugin/PluginMeta.php +++ b/src/ZM/Plugin/PluginMeta.php @@ -44,7 +44,7 @@ class PluginMeta implements \JsonSerializable // 设置名称 $this->name = $meta['name'] ?? ''; // 设置版本 - $this->version = $meta['version'] ?? '1.0.0'; + $this->version = $meta['version'] ?? '1.0-dev'; // 设置描述 $this->description = $meta['description'] ?? ''; // 设置依赖 @@ -52,7 +52,7 @@ class PluginMeta implements \JsonSerializable $this->metas = $meta; // 设置插件根目录 $this->plugin_type = $plugin_type; - // 设置插件类型 + // 设置插件根目录 $this->root_dir = $root_dir; } diff --git a/src/ZM/Plugin/Traits/PluginPackTrait.php b/src/ZM/Plugin/Traits/PluginPackTrait.php new file mode 100644 index 00000000..582c8ac2 --- /dev/null +++ b/src/ZM/Plugin/Traits/PluginPackTrait.php @@ -0,0 +1,51 @@ +on_pack = $callback; + } + + public function filterPack(callable $callback): void + { + $this->on_pack_filter = $callback; + } + + /** + * @internal + */ + public function emitPack(): void + { + if (is_callable($this->on_pack)) { + ($this->on_pack)(); + } + } + + /** + * @internal + * @param string $file 文件名称,由 PluginManager 传入 + * @return bool 返回 False 代表该文件不需要打包 + */ + public function emitFilterPack(string $file): bool + { + if (is_callable($this->on_pack_filter)) { + return (bool) ($this->on_pack_filter)($file); + } + return true; + } +} diff --git a/src/ZM/Plugin/ZMPlugin.php b/src/ZM/Plugin/ZMPlugin.php index 3ab14f7d..f487d273 100644 --- a/src/ZM/Plugin/ZMPlugin.php +++ b/src/ZM/Plugin/ZMPlugin.php @@ -17,4 +17,5 @@ class ZMPlugin use Traits\InitTrait; use Traits\PluginLoadTrait; use Traits\RouteTrait; + use Traits\PluginPackTrait; }