2020-08-31 10:11:06 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
declare(strict_types=1);
|
2020-08-31 10:11:06 +08:00
|
|
|
|
|
|
|
|
|
|
namespace ZM\Command;
|
|
|
|
|
|
|
2022-12-17 21:53:08 +08:00
|
|
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
2020-08-31 10:11:06 +08:00
|
|
|
|
use Symfony\Component\Console\Input\InputOption;
|
2022-12-31 22:59:03 +08:00
|
|
|
|
use ZM\Store\FileSystem;
|
2020-08-31 10:11:06 +08:00
|
|
|
|
|
2022-12-31 22:59:03 +08:00
|
|
|
|
#[AsCommand(name: 'build', description: '将项目构建一个 Phar 包')]
|
2020-08-31 10:11:06 +08:00
|
|
|
|
class BuildCommand extends Command
|
|
|
|
|
|
{
|
2022-12-17 22:18:50 +08:00
|
|
|
|
use NonPharLoadModeOnly;
|
|
|
|
|
|
|
2020-08-31 10:11:06 +08:00
|
|
|
|
/**
|
2022-08-01 16:31:54 +08:00
|
|
|
|
* 配置
|
2020-08-31 10:11:06 +08:00
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
protected function configure()
|
|
|
|
|
|
{
|
2022-12-31 23:57:56 +08:00
|
|
|
|
$this->setHelp('此功能将会把整个项目打包为 Phar' . PHP_EOL . '默认会启用压缩功能,通过去除文件中的注释和空格,以减小文件大小,但可能增加构建耗时');
|
2022-12-31 23:01:00 +08:00
|
|
|
|
$this->addOption('target', 'D', InputOption::VALUE_REQUIRED, '指定输出文件位置', 'zm.phar');
|
2022-12-31 23:57:56 +08:00
|
|
|
|
$this->addOption('no-compress', null, InputOption::VALUE_NONE, '是否不压缩文件,以减小构建耗时');
|
2020-08-31 10:11:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-31 22:59:03 +08:00
|
|
|
|
protected function handle(): int
|
2022-03-15 18:05:33 +08:00
|
|
|
|
{
|
2022-12-31 22:59:03 +08:00
|
|
|
|
$this->ensurePharWritable();
|
|
|
|
|
|
|
2022-12-31 23:01:00 +08:00
|
|
|
|
$target = $this->input->getOption('target');
|
2022-12-31 22:59:03 +08:00
|
|
|
|
if (FileSystem::isRelativePath($target)) {
|
|
|
|
|
|
$target = SOURCE_ROOT_DIR . '/' . $target;
|
2022-03-15 18:05:33 +08:00
|
|
|
|
}
|
2022-12-31 22:59:03 +08:00
|
|
|
|
$this->ensureTargetWritable($target);
|
|
|
|
|
|
$this->comment("目标文件:{$target}");
|
|
|
|
|
|
|
2022-12-31 23:57:56 +08:00
|
|
|
|
if (file_exists($target)) {
|
|
|
|
|
|
$this->comment('目标文件已存在,正在删除...');
|
|
|
|
|
|
unlink($target);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-31 22:59:03 +08:00
|
|
|
|
$this->info('正在构建 Phar 包');
|
|
|
|
|
|
|
2022-12-31 23:57:56 +08:00
|
|
|
|
$this->build(
|
|
|
|
|
|
$target,
|
|
|
|
|
|
LOAD_MODE === LOAD_MODE_VENDOR ? 'src/entry.php' : 'vendor/zhamao/framework/src/entry.php',
|
|
|
|
|
|
);
|
2022-12-31 22:59:03 +08:00
|
|
|
|
|
|
|
|
|
|
$this->info('Phar 包构建完成');
|
|
|
|
|
|
|
|
|
|
|
|
return self::SUCCESS;
|
2020-08-31 10:11:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-31 22:59:03 +08:00
|
|
|
|
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);
|
2022-03-20 23:26:42 +08:00
|
|
|
|
}
|
2022-12-31 22:59:03 +08:00
|
|
|
|
// 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);
|
2023-01-01 16:04:02 +08:00
|
|
|
|
$args = array_merge(['-d', 'phar.readonly=0'], $_SERVER['argv']);
|
|
|
|
|
|
if (pcntl_exec(PHP_BINARY, $args) === false) {
|
2022-12-31 22:59:03 +08:00
|
|
|
|
$this->error('切换到读写模式失败,请检查环境。');
|
|
|
|
|
|
exit(1);
|
2022-08-01 16:31:54 +08:00
|
|
|
|
}
|
2022-03-20 23:26:42 +08:00
|
|
|
|
}
|
2022-12-31 22:59:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
{
|
2023-01-01 00:18:27 +08:00
|
|
|
|
$phar = new \Phar($target, 0);
|
2022-12-31 22:59:03 +08:00
|
|
|
|
|
|
|
|
|
|
$phar->startBuffering();
|
|
|
|
|
|
$files = FileSystem::scanDirFiles(SOURCE_ROOT_DIR, true, true);
|
2023-02-01 16:27:05 +08:00
|
|
|
|
$separator = '\\' . DIRECTORY_SEPARATOR;
|
2023-01-01 00:18:27 +08:00
|
|
|
|
// 只打包 bin / config / resources / src / vendor 目录以及 composer.json / composer.lock / entry.php
|
2023-02-01 16:27:05 +08:00
|
|
|
|
$files = array_filter($files, function ($file) use ($separator) {
|
|
|
|
|
|
return preg_match('/^(bin|config|resources|src|vendor)' . $separator . '|^(composer\\.json|README\\.md)$/', $file);
|
2022-12-31 22:59:03 +08:00
|
|
|
|
});
|
|
|
|
|
|
sort($files);
|
|
|
|
|
|
|
2022-12-31 23:57:56 +08:00
|
|
|
|
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));
|
|
|
|
|
|
}
|
2022-12-31 22:59:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$phar->setStub(
|
|
|
|
|
|
'#!/usr/bin/env php' . PHP_EOL .
|
|
|
|
|
|
$phar::createDefaultStub($entry)
|
|
|
|
|
|
);
|
|
|
|
|
|
$phar->stopBuffering();
|
|
|
|
|
|
}
|
2020-08-31 10:11:06 +08:00
|
|
|
|
}
|