TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
8.5 KiB
268 lines
8.5 KiB
<?php
|
|
|
|
namespace TypePhp\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 TypePHP"
|
|
*
|
|
* # manifest 与 resource 同级(可选,Windows 平台缺省不携带)
|
|
* manifest: path/to/app.manifest
|
|
*/
|
|
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'])
|
|
|| !empty($this->config['manifest']);
|
|
}
|
|
|
|
/**
|
|
* 获取图标文件的绝对路径
|
|
*/
|
|
public function getIconPath(): ?string
|
|
{
|
|
$icon = $this->config['icon'] ?? null;
|
|
if (empty($icon)) {
|
|
return null;
|
|
}
|
|
|
|
return $this->resolvePath($icon);
|
|
}
|
|
|
|
/**
|
|
* 获取 manifest 文件的绝对路径
|
|
*/
|
|
public function getManifestPath(): ?string
|
|
{
|
|
$manifest = $this->config['manifest'] ?? null;
|
|
if (empty($manifest)) {
|
|
return null;
|
|
}
|
|
|
|
return $this->resolvePath($manifest);
|
|
}
|
|
|
|
/**
|
|
* 解析相对/绝对路径为绝对路径
|
|
*/
|
|
private function resolvePath(string $path): string
|
|
{
|
|
// 如果是绝对路径,直接使用
|
|
if (preg_match('/^[A-Za-z]:\\\\|^\//', $path)) {
|
|
return $path;
|
|
}
|
|
|
|
// 相对路径,基于项目目录解析
|
|
return $this->projectDir . DIRECTORY_SEPARATOR . $path;
|
|
}
|
|
|
|
/**
|
|
* 生成 .rc 资源文件内容
|
|
*/
|
|
public function generate(): string
|
|
{
|
|
$content = '';
|
|
$content .= '// Generated by TypePHP - 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;
|
|
|
|
// Manifest 资源(Windows 清单文件,如 UAC、DPI 感知等)
|
|
$manifestPath = $this->getManifestPath();
|
|
if ($manifestPath) {
|
|
$manifestPathRc = str_replace('\\', '/', $manifestPath);
|
|
$content .= '// Manifest Resource' . PHP_EOL;
|
|
$content .= 'CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "' . addslashes($manifestPathRc) . '"' . 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);
|
|
// 确保每个部分都是数字(过滤掉非数字字符)
|
|
foreach ($parts as $i => $part) {
|
|
$parts[$i] = preg_replace('/[^0-9]/', '', $part) ?: '0';
|
|
}
|
|
|
|
// 确保恰好有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 TypePHP - 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;
|
|
}
|
|
}
|
|
|