Created
December 10, 2020 03:25
-
-
Save TransparentLC/c3044ae50140d3e57b654bfe486bb14a to your computer and use it in GitHub Desktop.
并没有什么用的 PHAR 打包脚本
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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