Skip to content

Instantly share code, notes, and snippets.

@TransparentLC
Created December 10, 2020 03:25
Show Gist options
  • Save TransparentLC/c3044ae50140d3e57b654bfe486bb14a to your computer and use it in GitHub Desktop.
Save TransparentLC/c3044ae50140d3e57b654bfe486bb14a to your computer and use it in GitHub Desktop.
并没有什么用的 PHAR 打包脚本
<?php
/*
PHAR打包脚本
使用方法:
php phar-builder.php /path/to/build/config.json
打包配置文件:
{
// 项目名称,也是打包的文件名称
"name": "package-name",
// 入口文件
// 注意:工作目录是配置文件所在的路径,所以这里的路径都是相对路径
"entry": "main.php",
// 需要包含的目录
"include": [
"src",
"vendor"
],
// 如果路径符合这里的某个正则表达式,则这个文件不会被打包
"exclude": {
"Package meta files": "/^vendor\\/.+\\/(license|readme|upgrading|changelog|authors?|backers?|\\.gitignore|\\.gitattributes|\\.hhconfig|\\.travis\\.yml|appveyor\\.yml|.*\\.md|.*\\.dist|makefile|composer\\.json|composer\\.lock|phpunit\\.xml|psalm\\.xml)$/i",
"Package test files": "/^vendor\\/.+\\/tests?\\/.*$/i"
},
// 是否对代码进行压缩
// 目前支持的压缩:
// 使用php_strip_whitespace对PHP文件进行压缩
// 移除JSON文件中不必要的空格和换行
"minify": {
"php": true,
"json": true
},
// (可选)对PHAR文件进行压缩,可以选择gz或bz2
"compress": "gz",
// (可选)在打包文件名后附加的东西,可以选择timestamp或datetime或hex8或hex16或hex32
"suffix": "datetime"
// 导入PHAR包的PHP文件名,留空则设为index.php,不设定此项则不会创建文件
"loader": "index.php"
}
备注:
可以添加的define:
IS_PHAR 是否在PHAR中运行
PHAR_PATH 在PHAR中运行则返回PHAR文件在硬盘上的目录,否则返回入口文件在硬盘上的目录
APP_PATH 在PHAR中运行则返回入口文件在PHAR文件里的目录,否则返回入口文件在硬盘上的目录
git相关信息:
GIT_COMMIT_HASH、GIT_COMMIT_HASH_SHORT、GIT_COMMIT_HASH_TIMESTAMP均为打包时自动读取(需要允许使用exec并存在.git文件夹)
在代码中可以添加if (!defined(...)) define(..., null);进行占位
*/
if (php_sapi_name() !== 'cli') die;
echo <<< BANNER
= Akarin's Simple Phar Builder =
Usage:
{$argv[0]} /path/to/build/config.json
Define:
define('IS_PHAR', (bool)Phar::running());
define('PHAR_PATH', IS_PHAR ? dirname(Phar::running(false)) : __DIR__);
define('APP_PATH', IS_PHAR ? Phar::running() : __DIR__);
if (!defined('GIT_COMMIT_HASH')) define('GIT_COMMIT_HASH', null);
if (!defined('GIT_COMMIT_HASH_SHORT')) define('GIT_COMMIT_HASH_SHORT', null);
if (!defined('GIT_COMMIT_TIMESTAMP')) define('GIT_COMMIT_TIMESTAMP', null);
BANNER;
if (empty($argv[1])) die;
function prettyFileSize(int $size, int $precision = 2) {
$units = ['KB', 'MB', 'GB', 'GB'];
$unit = 'Bytes';
while ($size > 1024 && count($units)) {
$size /= 1024;
$unit = array_shift($units);
}
$size = round($size, $precision);
return "{$size} {$unit}";
}
function prettyPercentage(float $percentage, int $precision = 2) {
return round($percentage * 100, $precision) . '%';
}
$config = json_decode(file_get_contents($argv[1]), true);
chdir(dirname($argv[1]));
$suffix = '';
if (!empty($config['suffix'])) {
switch ($config['suffix']) {
case 'timestamp':
$suffix = time();
break;
case 'datetime':
$suffix = (new \DateTime)->format('Ymd.His');
break;
case 'hex32':
$suffix = bin2hex(random_bytes(16));
break;
case 'hex16':
$suffix = bin2hex(random_bytes(8));
break;
case 'hex8':
$suffix = bin2hex(random_bytes(4));
break;
default:
echo "Invalid suffix mode: {$config['suffix']}\n";
break;
}
}
if (!empty($suffix)) $config['name'] .= '.' . $suffix;
@unlink("{$config['name']}.phar");
$phar = new Phar("{$config['name']}.phar");
$files = [];
$config['include'][] = $config['entry'];
foreach ($config['include'] as $path) {
$files = array_merge($files, is_file($path) ? [$path] : array_keys(
iterator_to_array(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS)
)
)
));
}
$files = array_map(fn ($file) => str_replace('\\', '/', $file), $files);
$files = array_filter($files, function ($file) use ($config) {
if (is_dir($file)) return false;
foreach ($config['exclude'] as $excludeRule => $excludeRegex) {
if (preg_match($excludeRegex, $file)) {
echo "Ignoring {$file} (Exclude rule: {$excludeRule})\n";
return false;
}
}
return true;
});
$filesCount = count($files);
$currentCount = 0;
foreach ($files as $file) {
$currentCount++;
$minify = false;
if ($config['minify']['php'] && pathinfo($file, PATHINFO_EXTENSION) === 'php') {
$minify = true;
$minified = php_strip_whitespace($file);
} else if ($config['minify']['json'] && pathinfo($file, PATHINFO_EXTENSION) === 'json') {
$minify = true;
$minified = json_encode(json_decode(file_get_contents($file), true), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
if ($minify) {
$phar->addFromString($file, $minified);
} else {
$phar->addFile($file);
}
printf(
"Writing %d/%d (%s): %s (%s)\n",
$currentCount,
$filesCount,
prettyPercentage($currentCount / $filesCount),
$file,
$minify ? (prettyFileSize(filesize($file)) . ' -> ' . prettyFileSize(strlen($minified))) : prettyFileSize(filesize($file))
);
}
$stub = $phar->createDefaultStub($config['entry']);
if (function_exists('exec') && is_dir('.git')) {
$define = [
'define("GIT_COMMIT_HASH","' . trim(exec('git log --pretty="%H" -n1 HEAD')) . '");',
'define("GIT_COMMIT_HASH_SHORT","' . trim(exec('git log --pretty="%h" -n1 HEAD')) . '");',
'define("GIT_COMMIT_TIMESTAMP",' . trim(exec('git log --pretty="%at" -n1 HEAD')) . ');',
];
$stub = preg_replace('/^<\\?php\s/', '<?php ' . join('', $define), $stub, 1);
}
file_put_contents('.stub', $stub);
$phar->setStub(php_strip_whitespace('.stub'));
$phar->stopBuffering();
unlink('.stub');
$pharUncompressedSize = filesize("{$config['name']}.phar");
echo "Phar size: " . prettyFileSize($pharUncompressedSize) . "\n";
if (!empty($config['compress'])) {
switch ($config['compress']) {
case 'gz':
if (extension_loaded('zlib')) {
echo "Creating GZ compressed phar...\n";
$phar->compressFiles(Phar::GZ);
$phar->stopBuffering();
clearstatcache();
printf(
"Phar (GZ compressed) size: %s (%s)\n",
prettyFileSize(filesize("{$config['name']}.phar")),
prettyPercentage(filesize("{$config['name']}.phar") / $pharUncompressedSize)
);
} else {
echo "GZ compression is not enabled.\n";
}
break;
case 'gz':
if (extension_loaded('bz2')) {
echo "Creating BZ2 compressed phar...\n";
$phar->compressFiles(Phar::BZ2);
$phar->stopBuffering();
clearstatcache();
printf(
"Phar (BZ2 compressed) size: %s (%s)\n",
prettyFileSize(filesize("{$config['name']}.phar")),
prettyPercentage(filesize("{$config['name']}.phar") / $pharUncompressedSize)
);
} else {
echo "BZ2 compression is not enabled.\n";
}
break;
}
}
echo "Phar building complete.\n";
echo "Phar file: " . realpath("{$config['name']}.phar") . "\n";
if (isset($config['loader'])) {
file_put_contents(
empty($config['loader']) ? 'index.php' : $config['loader'],
sprintf(
"<?php /* Generated by Akarin's phar builder at %s */ require_once __DIR__ . '/%s.phar';",
date('Y-m-d H:i:s'),
$config['name']
)
);
echo "Loader: " . realpath($config['loader']) . "\n";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment