改进打包脚本,3个平台复用一个脚本

pull/43/head
韩天峰 4 weeks ago
parent c86e847c8f
commit cefc3f4f5e
  1. 12
      README.md
  2. 244
      package.php
  3. 221
      package.sh
  4. 2
      version.txt

@ -70,3 +70,15 @@ vim /etc/ld.so.conf.d/swoole.conf
/home/swoole/workspace/projects/phpx/lib
/opt/php-8.4/lib/
```
## Release packaging
Use the same PHP entry point on Windows, Linux, and macOS:
```shell
php package.php
```
Windows packaging requires `PHP_HOME` and `PHPX_HOME`; Linux packaging requires
UPX; macOS uses `strip` when available. TypePHP rejects 32-bit targets and
supports common 64-bit CPU architectures, including x86-64 and ARM64.

@ -1,15 +1,29 @@
#!/usr/bin/env php
<?php
/**
* Windows 打包脚本
* 将构建好的 tpc.exe 及相关文件打包为 zip
* 参考 package.sh,但针对 Windows 特性进行了调整
* TypePHP cross-platform release packager.
*
* Windows produces a self-contained PHP/PHPX SDK package. Linux and macOS
* retain their system-runtime package layout while sharing the same version,
* staging, archive verification, and cleanup rules.
*/
// 检查是否在 Windows 环境下运行
if (!chdir(__DIR__)) {
throw new RuntimeException('Unable to enter the project directory: ' . __DIR__);
}
if (in_array('--help', $argv ?? [], true) || in_array('-h', $argv ?? [], true)) {
echo "Usage: php package.php\n\n";
echo "Windows: requires PHP_HOME and PHPX_HOME; creates a self-contained SDK package.\n";
echo "Linux: requires UPX; packages the native binary and release metadata.\n";
echo "macOS: uses strip when available; packages the native binary and release metadata.\n";
echo "Supported architectures: 64-bit CPUs, including x86_64 and ARM64.\n";
exit(0);
}
if (PHP_OS_FAMILY !== 'Windows') {
echo "警告: 此脚本专为 Windows 设计,当前系统: " . PHP_OS . "\n";
echo "继续使用可能出现问题...\n\n";
packageUnixLike();
exit(0);
}
echo "========================================\n";
@ -59,17 +73,12 @@ echo "当前版本: {$versionId}\n\n";
echo "[2/7] 检测系统架构...\n";
$processorArchitecture = getenv('PROCESSOR_ARCHITEW6432') ?: getenv('PROCESSOR_ARCHITECTURE');
$arch = match (strtoupper((string)$processorArchitecture)) {
'AMD64' => 'x86_64',
'ARM64' => 'arm64',
default => null,
};
if ($arch === null || PHP_INT_SIZE !== 8) {
if (PHP_INT_SIZE !== 8) {
$detectedArchitecture = $processorArchitecture ?: 'unknown';
echo "错误: TypePHP 不支持 32 位或未知 Windows 架构 - {$detectedArchitecture}\n";
echo "仅支持 Windows x86_64 和 ARM64\n";
echo "错误: TypePHP 不支持 32 位 Windows 架构 - {$detectedArchitecture}\n";
exit(1);
}
$arch = normalizeArchitecture((string)$processorArchitecture);
$osType = 'windows';
$outputFile = "tpc_v{$versionId}_{$osType}_{$arch}.zip";
@ -186,7 +195,13 @@ if (is_dir($topLevelDir)) {
mustCreateDirectory($topLevelDir);
$cleanupStage = true;
register_shutdown_function(static function () use (&$cleanupStage, $topLevelDir): void {
$cleanupArchive = true;
register_shutdown_function(static function () use (
&$cleanupStage,
&$cleanupArchive,
$topLevelDir,
$outputFile,
): void {
if ($cleanupStage && is_dir($topLevelDir)) {
try {
removeDirectory($topLevelDir);
@ -194,6 +209,9 @@ register_shutdown_function(static function () use (&$cleanupStage, $topLevelDir)
fwrite(STDERR, "警告: 无法清理临时目录 {$topLevelDir}: {$error->getMessage()}\n");
}
}
if ($cleanupArchive && is_file($outputFile) && !unlink($outputFile)) {
fwrite(STDERR, "警告: 无法清理未提交的压缩包 {$outputFile}\n");
}
});
$packagedCompilerExe = "{$topLevelDir}/{$compilerExe}";
@ -501,6 +519,7 @@ try {
}
throw $error;
}
$cleanupArchive = false;
echo "\n";
@ -532,6 +551,201 @@ echo " 3. set PHPX_HOME=%CD%\\phpx\n";
echo " 4. set PATH=%CD%;%PATH%\n";
echo " 5. 运行: tpc <your_script.php>\n\n";
function normalizeArchitecture(string $architecture): string
{
$architecture = strtolower(trim($architecture));
$normalized = match ($architecture) {
'x86_64', 'x86-64', 'amd64', 'x64' => 'x86_64',
'aarch64', 'arm64', 'arm64b', 'arm64e' => 'arm64',
'powerpc64', 'ppc64' => 'ppc64',
'powerpc64le', 'ppc64le' => 'ppc64le',
'riscv64' => 'riscv64',
's390x' => 's390x',
'loongarch64' => 'loongarch64',
'mips64' => 'mips64',
'mips64el' => 'mips64el',
'sparc64' => 'sparc64',
default => null,
};
if ($normalized !== null) {
return $normalized;
}
// Preserve future 64-bit architecture names while keeping archive names safe.
if (str_contains($architecture, '64')
&& preg_match('/^[a-z0-9][a-z0-9._-]*$/', $architecture) === 1) {
return str_replace('-', '_', $architecture);
}
throw new RuntimeException("Unsupported or non-64-bit architecture: {$architecture}");
}
function packageUnixLike(): void
{
$osType = match (PHP_OS_FAMILY) {
'Linux' => 'linux',
'Darwin' => 'macos',
default => throw new RuntimeException('Unsupported operating system: ' . PHP_OS_FAMILY),
};
if (PHP_INT_SIZE !== 8) {
throw new RuntimeException('TypePHP only supports 64-bit systems');
}
$arch = normalizeArchitecture(php_uname('m'));
$binary = 'tpc';
$versionFile = 'version.txt';
$requiredFiles = [
$binary,
'composer.json',
'README.md',
'LICENSE.md',
'examples/hello.php',
];
foreach ($requiredFiles as $file) {
if (!is_file($file)) {
throw new RuntimeException("Required package file not found: {$file}");
}
}
if (!class_exists('ZipArchive')) {
throw new RuntimeException('The ZipArchive extension is required');
}
$versionId = is_file($versionFile) ? (int)trim((string)file_get_contents($versionFile)) : 1000;
$versionId++;
$topLevelDir = "tpc_v{$versionId}_{$osType}_{$arch}";
$outputFile = $topLevelDir . '.zip';
echo "========================================\n";
echo "TypePHP {$osType} package\n";
echo "========================================\n";
echo "Version: {$versionId}\n";
echo "Architecture: {$arch}\n";
echo "Output: {$outputFile}\n\n";
if (is_dir($topLevelDir)) {
removeDirectory($topLevelDir);
}
mustCreateDirectory($topLevelDir);
$cleanupStage = true;
$cleanupArchive = true;
register_shutdown_function(static function () use (
&$cleanupStage,
&$cleanupArchive,
$topLevelDir,
$outputFile,
): void {
if ($cleanupStage && is_dir($topLevelDir)) {
try {
removeDirectory($topLevelDir);
} catch (Throwable $error) {
fwrite(STDERR, "Warning: unable to clean {$topLevelDir}: {$error->getMessage()}\n");
}
}
if ($cleanupArchive && is_file($outputFile) && !unlink($outputFile)) {
fwrite(STDERR, "Warning: unable to clean uncommitted archive {$outputFile}\n");
}
});
$stagedBinary = "{$topLevelDir}/{$binary}";
mustCopy($binary, $stagedBinary);
if (!chmod($stagedBinary, 0755)) {
throw new RuntimeException("Unable to mark executable: {$stagedBinary}");
}
if ($osType === 'linux') {
exec('command -v upx 2>/dev/null', $upxPath, $upxStatus);
if ($upxStatus !== 0) {
throw new RuntimeException(
'UPX is required for Linux packaging (for example: apt install upx-ucl)',
);
}
exec('upx --best ' . escapeshellarg($stagedBinary) . ' 2>&1', $upxOutput, $upxStatus);
if ($upxStatus !== 0) {
throw new RuntimeException("UPX failed:\n" . implode("\n", $upxOutput));
}
} else {
exec('command -v strip 2>/dev/null', $stripPath, $stripStatus);
if ($stripStatus === 0) {
exec('strip -x ' . escapeshellarg($stagedBinary) . ' 2>&1', $stripOutput, $stripStatus);
if ($stripStatus !== 0) {
throw new RuntimeException("strip failed:\n" . implode("\n", $stripOutput));
}
} else {
echo "Warning: strip was not found; packaging the unstripped binary\n";
}
}
mustCopy('composer.json', "{$topLevelDir}/composer.json");
mustCopy('README.md', "{$topLevelDir}/README.md");
mustCopy('LICENSE.md', "{$topLevelDir}/LICENSE.md");
mustCreateDirectory("{$topLevelDir}/examples");
mustCopy('examples/hello.php', "{$topLevelDir}/examples/hello.php");
if (is_file($outputFile) && !unlink($outputFile)) {
throw new RuntimeException("Unable to remove existing archive: {$outputFile}");
}
$zip = new ZipArchive();
if ($zip->open($outputFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
throw new RuntimeException("Unable to create archive: {$outputFile}");
}
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($topLevelDir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY,
);
foreach ($files as $file) {
if (!$file->isFile()) {
continue;
}
$relativePath = str_replace('\\', '/', substr(
$file->getPathname(),
strlen($topLevelDir) + 1,
));
if (!$zip->addFile($file->getPathname(), "{$topLevelDir}/{$relativePath}")) {
throw new RuntimeException("Unable to add archive entry: {$file->getPathname()}");
}
}
if (!$zip->close()) {
throw new RuntimeException("Unable to finish archive: {$outputFile}");
}
$requiredEntries = [
"{$topLevelDir}/{$binary}",
"{$topLevelDir}/composer.json",
"{$topLevelDir}/README.md",
"{$topLevelDir}/LICENSE.md",
"{$topLevelDir}/examples/hello.php",
];
$verificationZip = new ZipArchive();
if ($verificationZip->open($outputFile) !== true) {
throw new RuntimeException("Unable to verify archive: {$outputFile}");
}
foreach ($requiredEntries as $entry) {
if ($verificationZip->locateName($entry) === false) {
$verificationZip->close();
throw new RuntimeException("Archive is missing required entry: {$entry}");
}
}
$verificationZip->close();
removeDirectory($topLevelDir);
$cleanupStage = false;
try {
mustWriteFile($versionFile, (string)$versionId);
} catch (Throwable $error) {
if (is_file($outputFile)) {
@unlink($outputFile);
}
throw $error;
}
$cleanupArchive = false;
$sizeMb = round(filesize($outputFile) / 1024 / 1024, 2);
echo "Package successful: {$outputFile} ({$sizeMb} MB)\n";
}
/**
* 递归复制目录
* @param string $src 源目录

@ -1,221 +0,0 @@
#!/bin/bash
# 打包脚本:创建包含指定文件的 zip 压缩包
# Linux 系统下使用 UPX 压缩二进制文件,macOS 系统使用 strip 删除调试符号
# 支持版本管理,每次打包版本号自动递增
# 输出文件名格式:tpc_v{版本}_{操作系统}_{架构}.zip
BINARY_FILE="tpc"
VERSION_FILE="version.txt"
BACKUP_FILE="${BINARY_FILE}.backup"
# 读取或初始化版本号
if [ -f "$VERSION_FILE" ]; then
VERSION_ID=$(cat "$VERSION_FILE")
else
VERSION_ID=1000
fi
# 版本号递增
VERSION_ID=$((VERSION_ID + 1))
echo "$VERSION_ID" > "$VERSION_FILE"
echo "当前版本: $VERSION_ID"
# 生成带版本号的二进制文件名
VERSIONED_BINARY="${BINARY_FILE}_v${VERSION_ID}"
# 检查必要文件是否存在
REQUIRED_FILES=(
"$BINARY_FILE"
"composer.json"
"README.md"
"LICENSE.md"
"examples/hello.php"
)
echo "检查文件..."
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -e "$file" ]; then
echo "错误: 文件不存在 - $file"
exit 1
fi
done
echo "所有文件检查通过"
# 复制二进制文件为带版本号的名称
echo "创建版本化二进制文件: $VERSIONED_BINARY"
cp "$BINARY_FILE" "$VERSIONED_BINARY"
# 检测操作系统
detect_os() {
case "$(uname -s)" in
Darwin*)
echo "macos"
;;
Linux*)
echo "linux"
;;
*)
echo "unknown"
;;
esac
}
# 检测硬件架构
detect_arch() {
case "$(uname -m)" in
x86_64|amd64)
echo "x86_64"
;;
aarch64|arm64)
echo "arm64"
;;
armv7l|armv7)
echo "armv7"
;;
i386|i686)
echo "i386"
;;
*)
echo "$(uname -m)"
;;
esac
}
OS_TYPE=$(detect_os)
ARCH_TYPE=$(detect_arch)
echo "检测到操作系统: $OS_TYPE"
echo "检测到硬件架构: $ARCH_TYPE"
# 生成带版本号、操作系统和架构的输出文件名
OUTPUT_FILE="tpc_v${VERSION_ID}_${OS_TYPE}_${ARCH_TYPE}.zip"
echo "输出文件: $OUTPUT_FILE"
# 检查 upx 是否安装(仅 Linux 需要)
if [ "$OS_TYPE" = "linux" ]; then
if ! command -v upx &> /dev/null; then
echo "错误: 未找到 upx 命令,请先安装 upx"
echo "Ubuntu/Debian: sudo apt-get install upx-ucl"
echo "CentOS/RHEL: sudo yum install upx"
# 清理临时文件
rm -f "$VERSIONED_BINARY"
exit 1
fi
echo "检测到 upx: $(upx --version | head -n 1)"
fi
# 根据操作系统决定使用不同的优化方式
if [ "$OS_TYPE" = "linux" ]; then
# 备份原始二进制文件
echo "备份原始二进制文件..."
cp "$BINARY_FILE" "$BACKUP_FILE"
# 使用 UPX 压缩版本化二进制文件
echo "使用 UPX 压缩 $VERSIONED_BINARY ..."
upx --best "$VERSIONED_BINARY"
if [ $? -ne 0 ]; then
echo "✗ UPX 压缩失败!"
# 恢复原始文件并清理临时文件
mv "$BACKUP_FILE" "$BINARY_FILE"
rm -f "$VERSIONED_BINARY"
exit 1
fi
echo "✓ UPX 压缩完成"
echo " 原始大小: $(du -h "$BACKUP_FILE" | cut -f1)"
echo " 压缩后: $(du -h "$VERSIONED_BINARY" | cut -f1)"
# 根据操作系统使用不同的 stat 命令
if [ "$OS_TYPE" = "linux" ]; then
ORIGINAL_SIZE=$(stat -c%s "$BACKUP_FILE")
COMPRESSED_SIZE=$(stat -c%s "$VERSIONED_BINARY")
else
ORIGINAL_SIZE=$(stat -f%z "$BACKUP_FILE")
COMPRESSED_SIZE=$(stat -f%z "$VERSIONED_BINARY")
fi
COMPRESSION_RATIO=$(echo "scale=2; (1 - $COMPRESSED_SIZE / $ORIGINAL_SIZE) * 100" | bc)
echo " 压缩率: ${COMPRESSION_RATIO}%"
else
echo "macOS 系统,使用 strip 删除调试符号..."
# 备份原始二进制文件
echo "备份原始二进制文件..."
cp "$BINARY_FILE" "$BACKUP_FILE"
# 检查 strip 是否可用
if ! command -v strip &> /dev/null; then
echo "警告: 未找到 strip 命令,跳过符号剥离"
else
# 使用 strip 删除调试符号
echo "使用 strip 处理 $VERSIONED_BINARY ..."
strip -x "$VERSIONED_BINARY"
if [ $? -ne 0 ]; then
echo "✗ strip 处理失败!"
# 恢复原始文件并清理临时文件
mv "$BACKUP_FILE" "$BINARY_FILE"
rm -f "$VERSIONED_BINARY"
exit 1
fi
echo "✓ strip 处理完成"
echo " 原始大小: $(du -h "$BACKUP_FILE" | awk '{print $1}')"
echo " 处理后: $(du -h "$VERSIONED_BINARY" | awk '{print $1}')"
# 计算大小变化
ORIGINAL_SIZE=$(stat -f%z "$BACKUP_FILE")
STRIPPED_SIZE=$(stat -f%z "$VERSIONED_BINARY")
if [ $ORIGINAL_SIZE -gt 0 ]; then
REDUCTION_RATIO=$(echo "scale=2; (1 - $STRIPPED_SIZE / $ORIGINAL_SIZE) * 100" | bc)
echo " 减小率: ${REDUCTION_RATIO}%"
fi
fi
fi
# 删除旧的压缩包(如果存在)
if [ -f "$OUTPUT_FILE" ]; then
rm "$OUTPUT_FILE"
echo "已删除旧的压缩包"
fi
# 创建 zip 压缩包(使用带版本号的二进制文件)
echo "创建压缩包: $OUTPUT_FILE"
zip "$OUTPUT_FILE" \
"$VERSIONED_BINARY" \
composer.json \
README.md \
LICENSE.md \
examples/hello.php
PACK_RESULT=$?
# 恢复原始二进制文件并清理临时文件
echo "恢复原始二进制文件..."
mv "$BACKUP_FILE" "$BINARY_FILE"
rm -f "$VERSIONED_BINARY"
if [ $PACK_RESULT -eq 0 ]; then
echo "✓ 打包成功!"
echo "版本号: $VERSION_ID"
# 根据操作系统使用不同的 du 命令
if [ "$OS_TYPE" = "linux" ]; then
PACKAGE_SIZE=$(du -h "$OUTPUT_FILE" | cut -f1)
else
PACKAGE_SIZE=$(du -h "$OUTPUT_FILE" | awk '{print $1}')
fi
echo "压缩包大小: $PACKAGE_SIZE"
echo "包含文件:"
unzip -l "$OUTPUT_FILE" | grep -E "\.php$|compiler|\.json$|\.md$"
else
echo "✗ 打包失败!"
exit 1
fi

@ -1 +1 @@
1094
1096
Loading…
Cancel
Save