- 实现 ResourceFileGenerator 类用于生成 .rc 资源文件 - 支持图标文件嵌入功能,可指定 icon 配置项 - 实现版本信息生成功能,支持文件版本、产品版本等属性 - 提供配置示例文档,包括公司名称、版权等元数据 - 实现版本号格式化功能,支持多种输入格式转换 - 添加 UTF-8 编码支持,解决中文乱码问题 - 实现资源头文件生成,便于 C++ 代码引用资源 IDpull/1/head
parent
826e62e7b4
commit
8c8daa47e5
1 changed files with 233 additions and 0 deletions
@ -0,0 +1,233 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Generator; |
||||
|
||||
/** |
||||
* Windows 资源文件 (.rc) 生成器 |
||||
* |
||||
* 用于生成 Windows PE 资源文件,可将图标、版本信息等嵌入到 exe 中 |
||||
* |
||||
* 配置示例(在 project.yml 中): |
||||
* |
||||
* resource: |
||||
* icon: path/to/icon.ico |
||||
* version-info: |
||||
* file-version: 1.0.0.0 |
||||
* product-version: 1.0.0.0 |
||||
* file-flags-mask: 3f |
||||
* file-flags: 00 |
||||
* file-os: 040004 |
||||
* file-type: 01 |
||||
* file-subtype: 00 |
||||
* company-name: "My Company" |
||||
* file-description: "My Application" |
||||
* internal-name: "myapp" |
||||
* legal-copyright: "Copyright (C) 2026 My Company" |
||||
* legal-trademarks: "MyApp is a trademark of My Company" |
||||
* original-filename: "myapp.exe" |
||||
* product-name: "My Product" |
||||
* comments: "Built with Swoole Compiler" |
||||
*/ |
||||
class ResourceFileGenerator |
||||
{ |
||||
/** |
||||
* 资源配置 |
||||
*/ |
||||
private array $config; |
||||
|
||||
/** |
||||
* 项目目录(用于解析相对路径) |
||||
*/ |
||||
private string $projectDir; |
||||
|
||||
public function __construct(array $config, string $projectDir) |
||||
{ |
||||
$this->config = $config; |
||||
$this->projectDir = $projectDir; |
||||
} |
||||
|
||||
/** |
||||
* 检查是否有任何资源配置 |
||||
*/ |
||||
public function hasResource(): bool |
||||
{ |
||||
return !empty($this->config['icon']) || !empty($this->config['version-info']); |
||||
} |
||||
|
||||
/** |
||||
* 获取图标文件的绝对路径 |
||||
*/ |
||||
public function getIconPath(): ?string |
||||
{ |
||||
$icon = $this->config['icon'] ?? null; |
||||
if (empty($icon)) { |
||||
return null; |
||||
} |
||||
|
||||
// 如果是绝对路径,直接使用 |
||||
if (preg_match('/^[A-Za-z]:\\\\|^\//', $icon)) { |
||||
return $icon; |
||||
} |
||||
|
||||
// 相对路径,基于项目目录解析 |
||||
return $this->projectDir . DIRECTORY_SEPARATOR . $icon; |
||||
} |
||||
|
||||
/** |
||||
* 生成 .rc 资源文件内容 |
||||
*/ |
||||
public function generate(): string |
||||
{ |
||||
$content = ''; |
||||
$content .= '// Generated by Swoole Compiler - Windows Resource File' . PHP_EOL; |
||||
$content .= '// DO NOT EDIT - This file is auto-generated' . PHP_EOL; |
||||
$content .= PHP_EOL; |
||||
|
||||
// 告诉 rc.exe 此文件使用 UTF-8 编码,避免中文乱码 |
||||
$content .= '#pragma code_page(65001)' . PHP_EOL; |
||||
$content .= PHP_EOL; |
||||
|
||||
// 包含 Windows 版本信息头文件 |
||||
$content .= '#include <windows.h>' . PHP_EOL; |
||||
$content .= PHP_EOL; |
||||
|
||||
// 图标资源 |
||||
$iconPath = $this->getIconPath(); |
||||
if ($iconPath) { |
||||
// 使用正斜杠,Windows RC 编译器更兼容 |
||||
$iconPathRc = str_replace('\\', '/', $iconPath); |
||||
$content .= '// Icon Resource' . PHP_EOL; |
||||
$content .= 'MAINICON ICON "' . addslashes($iconPathRc) . '"' . PHP_EOL; |
||||
$content .= PHP_EOL; |
||||
} |
||||
|
||||
// 版本信息 |
||||
$versionInfo = $this->config['version-info'] ?? []; |
||||
if (!empty($versionInfo)) { |
||||
$content .= $this->generateVersionInfo($versionInfo); |
||||
} |
||||
|
||||
return $content; |
||||
} |
||||
|
||||
/** |
||||
* 生成版本信息块 |
||||
*/ |
||||
private function generateVersionInfo(array $info): string |
||||
{ |
||||
$fileVersion = $info['file-version'] ?? '0.0.0.0'; |
||||
$productVersion = $info['product-version'] ?? $fileVersion; |
||||
|
||||
$content = ''; |
||||
$content .= '// Version Information' . PHP_EOL; |
||||
$content .= '1 VERSIONINFO' . PHP_EOL; |
||||
$content .= 'FILEVERSION ' . $this->formatVersionDots($fileVersion) . PHP_EOL; |
||||
$content .= 'PRODUCTVERSION ' . $this->formatVersionDots($productVersion) . PHP_EOL; |
||||
$content .= 'FILEFLAGSMASK ' . ($info['file-flags-mask'] ?? '0x3fL') . PHP_EOL; |
||||
$content .= 'FILEFLAGS ' . ($info['file-flags'] ?? '0x0L') . PHP_EOL; |
||||
$content .= 'FILEOS ' . ($info['file-os'] ?? 'VOS_NT_WINDOWS32') . PHP_EOL; |
||||
$content .= 'FILETYPE ' . ($info['file-type'] ?? 'VFT_APP') . PHP_EOL; |
||||
$content .= 'FILESUBTYPE ' . ($info['file-subtype'] ?? 'VFT2_UNKNOWN') . PHP_EOL; |
||||
$content .= 'BEGIN' . PHP_EOL; |
||||
|
||||
// StringFileInfo 块 |
||||
$content .= ' BLOCK "StringFileInfo"' . PHP_EOL; |
||||
$content .= ' BEGIN' . PHP_EOL; |
||||
|
||||
// 语言代码页(040904b0 = 英文/UTF-8,配合 #pragma code_page(65001) 正确显示中文) |
||||
$langCodepage = $info['lang-codepage'] ?? '040904b0'; |
||||
$content .= ' BLOCK "' . $langCodepage . '"' . PHP_EOL; |
||||
$content .= ' BEGIN' . PHP_EOL; |
||||
|
||||
// 字符串值 |
||||
$stringFields = [ |
||||
'company-name' => 'CompanyName', |
||||
'file-description' => 'FileDescription', |
||||
'file-version' => 'FileVersion', |
||||
'internal-name' => 'InternalName', |
||||
'legal-copyright' => 'LegalCopyright', |
||||
'legal-trademarks' => 'LegalTrademarks', |
||||
'original-filename' => 'OriginalFilename', |
||||
'product-name' => 'ProductName', |
||||
'product-version' => 'ProductVersion', |
||||
'comments' => 'Comments', |
||||
]; |
||||
|
||||
foreach ($stringFields as $yamlKey => $rcKey) { |
||||
if (isset($info[$yamlKey])) { |
||||
$value = $info[$yamlKey]; |
||||
$content .= ' VALUE "' . $rcKey . '", "' . addslashes($value) . '\\0"' . PHP_EOL; |
||||
} |
||||
} |
||||
|
||||
// 如果没有设置 FileVersion,从 file-version 字段自动填入 |
||||
if (!isset($info['file-version-str']) && $fileVersion) { |
||||
// 已在上面通过 file-version 键处理 |
||||
} |
||||
|
||||
$content .= ' END' . PHP_EOL; |
||||
$content .= ' END' . PHP_EOL; |
||||
|
||||
// VarFileInfo 块 |
||||
$content .= ' BLOCK "VarFileInfo"' . PHP_EOL; |
||||
$content .= ' BEGIN' . PHP_EOL; |
||||
// 0x0409 = English(US),1200 = Unicode(UTF-16) |
||||
// 配合 StringFileInfo 中的 040904b0 代码页,确保中文在 UTF-8 源文件中正确编码 |
||||
$content .= ' VALUE "Translation", 0x0409, 1200' . PHP_EOL; |
||||
$content .= ' END' . PHP_EOL; |
||||
|
||||
$content .= 'END' . PHP_EOL; |
||||
|
||||
return $content; |
||||
} |
||||
|
||||
/** |
||||
* 将版本号格式化为逗号分隔的格式(1,0,0,0) |
||||
* 支持以下输入格式: |
||||
* - "1.0.0.0" → "1,0,0,0" |
||||
* - "1,0,0,0" → "1,0,0,0" |
||||
* - "v1052" → "1052,0,0,0" (去掉 v 前缀) |
||||
* - "1.0" → "1,0,0,0" |
||||
*/ |
||||
private function formatVersionDots(string $version): string |
||||
{ |
||||
// 去掉 v/V 前缀(如 v1052 → 1052) |
||||
$version = ltrim($version, 'vV'); |
||||
|
||||
// 如果已经是逗号分隔格式,直接返回 |
||||
if (str_contains($version, ',')) { |
||||
return $version; |
||||
} |
||||
|
||||
// 用点号分隔 |
||||
$parts = explode('.', $version); |
||||
// 确保每个部分都是数字(过滤掉非数字字符) |
||||
$parts = array_map(function ($p) { |
||||
return preg_replace('/[^0-9]/', '', $p) ?: '0'; |
||||
}, $parts); |
||||
|
||||
// 确保恰好有4个部分 |
||||
while (count($parts) < 4) { |
||||
$parts[] = '0'; |
||||
} |
||||
|
||||
return implode(',', array_slice($parts, 0, 4)); |
||||
} |
||||
|
||||
/** |
||||
* 生成 resource.h 头文件内容(可选,供 C++ 代码引用资源 ID) |
||||
*/ |
||||
public function generateHeader(): string |
||||
{ |
||||
$content = ''; |
||||
$content .= '// Generated by Swoole Compiler - Resource Header' . PHP_EOL; |
||||
$content .= '// DO NOT EDIT - This file is auto-generated' . PHP_EOL; |
||||
$content .= PHP_EOL; |
||||
|
||||
if ($this->getIconPath()) { |
||||
$content .= '#define MAINICON 101' . PHP_EOL; |
||||
} |
||||
|
||||
return $content; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue