From b17b4acbac64e41370eb283a5a2846f91a471f92 Mon Sep 17 00:00:00 2001 From: rango Date: Sat, 9 May 2026 20:33:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(build):=20=E6=B7=BB=E5=8A=A0=20Windows=20?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=96=87=E4=BB=B6=E6=94=AF=E6=8C=81=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 .gitignore 中添加 node_modules 目录 - 在 Msvc.php 中添加 compileResourceFile 方法用于编译 .rc 资源文件 - 新增 package.json 配置文件及 svg-to-ico.js 转换脚本 - 在 project.yml 中添加 Windows 资源文件配置示例 - 添加 swoole-logo.svg 图标文件 - 在 Translator.php 中集成资源文件生成功能 - 实现资源文件编译和链接到最终可执行文件的流程 - 更新版本号从 1049 到 1051 --- .gitignore | 1 + examples/win32-hello/project.yml | 13 +++ package.json | 25 ++++++ project.yml | 18 ++++ src/Php/Backend/Msvc.php | 34 +++++-- src/Php/Translator.php | 133 ++++++++++++++++++++++++++++ svg-to-ico.js | 147 +++++++++++++++++++++++++++++++ swoole-logo.ico | Bin 0 -> 12229 bytes swoole-logo.svg | 15 ++++ version.txt | 2 +- 10 files changed, 381 insertions(+), 7 deletions(-) create mode 100644 package.json create mode 100644 svg-to-ico.js create mode 100644 swoole-logo.ico create mode 100644 swoole-logo.svg diff --git a/.gitignore b/.gitignore index 975d041b..f3d4ca74 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /logs /build /vendor +/node_modules /tmp /projects/wordpress /projects/workerman diff --git a/examples/win32-hello/project.yml b/examples/win32-hello/project.yml index 1babb902..bea763e5 100644 --- a/examples/win32-hello/project.yml +++ b/examples/win32-hello/project.yml @@ -1,6 +1,19 @@ name: win32-hello version: 0.0.1 mode: bin + +# Windows 资源文件配置示例 +# resource: +# icon: icon.ico +# version-info: +# file-version: 0.0.1.0 +# product-version: 0.0.1.0 +# company-name: "My Company" +# file-description: "Win32 Hello World Application" +# legal-copyright: "Copyright (C) 2026 My Company" +# product-name: "Win32 Hello" +# original-filename: "win32-hello.exe" + sources: - hello-win.php - ./cpp-src diff --git a/package.json b/package.json new file mode 100644 index 00000000..a7fbc824 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "compiler", + "version": "1.0.0", + "description": "- 需要 PHP-8.2 以上版本 - 需要 GCC-9 以上版本,支持 C++17 标准 - 需要 CMake-3.24 以上版本", + "main": "index.js", + "directories": { + "doc": "docs", + "example": "examples", + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git@git.code-galaxy.net:aot/compiler.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "sharp": "^0.34.5" + } +} diff --git a/project.yml b/project.yml index ed70c4cc..26533e7d 100644 --- a/project.yml +++ b/project.yml @@ -5,6 +5,24 @@ cxx-std: c++17 cxx-flags: - -Wall +# Windows 资源文件配置(图标、版本信息等) +# 仅在 Windows 平台编译 bin 模式时生效 +resource: + # 图标文件路径(相对于 project.yml 所在目录,也支持绝对路径) + icon: swoole-logo.ico + # 版本信息 + version-info: + file-version: 0.1.0.1052 + product-version: 0.1.0 + company-name: "上海识沃网络科技有限公司" + file-description: "Swoole AOT Compiler" + internal-name: "swoole-compiler" + legal-copyright: "Copyright (C) 2026 上海识沃网络科技有限公司" + legal-trademarks: "Swoole is a trademark of 上海识沃网络科技有限公司" + original-filename: "swoole_compiler.exe" + product-name: "Swoole Compiler" + comments: "PHP 原生编译器,可将 PHP 项目编译为 Windows/Linux/macOS 平台原生的可执行文件" + sources: - ./src/Php - ./src/Core diff --git a/src/Php/Backend/Msvc.php b/src/Php/Backend/Msvc.php index 4a53a6b4..93769f26 100644 --- a/src/Php/Backend/Msvc.php +++ b/src/Php/Backend/Msvc.php @@ -375,28 +375,50 @@ class Msvc extends CompilerBackend public function buildLinkOptions(array $config = []): string { $cmd = ''; - + // 调试 if (!empty($config['debug'])) { $cmd .= ' /DEBUG'; } - + // Windows 子系统 if (!empty($config['no_console'])) { $cmd .= ' ' . $this->platform->getSubsystemOptions(true); } - + // CRT 配置 $cmd .= ' ' . $this->platform->getCrtConfig(); - + // 扩展模块选项 if (!empty($config['build_mode']) && $config['build_mode'] === 'ext') { $cmd .= ' /DLL'; } - + // nologo $cmd .= ' /nologo'; - + + return $cmd; + } + + /** + * 编译 Windows 资源文件 (.rc) 为目标文件 (.res) + * + * 使用 rc.exe(MSVC 资源编译器)将 .rc 文件编译为 .res 文件 + * .res 文件可以直接传给 link.exe 作为输入 + * + * @param string $rcFile 资源文件路径 (.rc) + * @param string $resFile 输出资源文件路径 (.res) + * @return string 编译命令 + */ + public function compileResourceFile(string $rcFile, string $resFile): string + { + // rc.exe 是 MSVC 自带的资源编译器 + // /nologo: 不显示版权信息 + // /fo: 指定输出文件 + $cmd = 'rc.exe /nologo'; + $cmd .= ' /fo ' . escapeshellarg($resFile); + $cmd .= ' ' . escapeshellarg($rcFile); + return $cmd; } } diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 0a25b5dd..720ecbe7 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -19,6 +19,7 @@ use PhpAot\Php\Entity\PropertyDef; use PhpAot\Php\Exception\Redo; use PhpAot\Php\Exception\SyntaxError; use PhpAot\Php\Exception\Unsupported; +use PhpAot\Php\Generator\ResourceFileGenerator; use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\Node\Stmt\Foreach_; @@ -39,6 +40,9 @@ class Translator extends Preprocessor protected array $argInfoHeaderFiles = []; protected array $registerSymbols = []; + // Windows 资源文件配置(图标、版本信息等) + protected array $resourceConfig = []; + // 类静态属性初始值 protected array $defaultStaticPropertyList = []; @@ -753,6 +757,9 @@ CODE; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/ps_title.c'; } + // Windows 平台:编译资源文件(图标、版本信息等) + $this->compileResourceFile(); + if (!$this->getPlatform()->supportsPcntlParallelCompile() or $job <= 1) { return $this->compileSourceFile($sourceFiles); } @@ -892,9 +899,115 @@ CODE; $this->climate->{$style}($message); } + // ======================================================================== + // Windows 资源文件支持 + // ======================================================================== + + /** + * 检查是否配置了 Windows 资源信息 + */ + public function hasResourceFile(): bool + { + if (!$this->isWindows()) { + return false; + } + $generator = $this->createResourceGenerator(); + return $generator !== null && $generator->hasResource(); + } + + /** + * 获取 .rc 资源文件路径 + */ + public function getResourceRcFile(): string + { + return $this->getBuildDir() . DIRECTORY_SEPARATOR . 'app_resource.rc'; + } + + /** + * 获取 .res 编译后的资源文件路径 + */ + public function getResourceResFile(): string + { + return $this->getBuildDir() . DIRECTORY_SEPARATOR . 'app_resource.res'; + } + + /** + * 创建资源文件生成器 + */ + protected function createResourceGenerator(): ?ResourceFileGenerator + { + if (empty($this->resourceConfig)) { + return null; + } + $projectDir = $this->resourceConfig['_projectDir'] ?? getcwd(); + return new ResourceFileGenerator($this->resourceConfig, $projectDir); + } + + /** + * 编译 Windows 资源文件 + * + * 如果配置了 resource 选项,生成 .rc 文件并使用 rc.exe 编译为 .res + * .res 文件会在 build 阶段被链接到最终的 exe 中 + */ + protected function compileResourceFile(): void + { + if (!$this->isWindows()) { + return; + } + + $generator = $this->createResourceGenerator(); + if ($generator === null || !$generator->hasResource()) { + return; + } + + // 生成 .rc 文件 + $rcFile = $this->getResourceRcFile(); + $rcContent = $generator->generate(); + // 写入 UTF-8 BOM,确保 rc.exe 正确识别编码,避免中文乱码 + $this->writeFile($rcFile, "\xEF\xBB\xBF" . $rcContent); + $this->climate->info('Generated resource file: ' . $rcFile); + + // 使用 MSVC 的 rc.exe 编译 .rc -> .res + $backend = $this->getCompilerBackend(); + if ($backend instanceof \PhpAot\Php\Backend\Msvc) { + $resFile = $this->getResourceResFile(); + $cmd = $backend->compileResourceFile($rcFile, $resFile); + $this->climate->comment($cmd); + + exec($cmd . ' 2>&1', $output, $ret); + + if (!empty($output)) { + foreach ($output as $line) { + $this->climate->out($line); + } + } + + if ($ret !== 0) { + $this->error('Resource compilation failed: ' . $rcFile); + } + + if (!file_exists($resFile)) { + $this->error('Resource file not generated: ' . $resFile); + } + + $this->climate->green('Resource compiled: ' . $resFile); + } else { + $this->climate->warning('Resource files are only supported with MSVC backend on Windows'); + } + } + public function build(array $objectFiles): void { $targetFile = $this->getTargetFileName(); + + // Windows 平台:将 .res 资源文件加入链接 + if ($this->isWindows() && $this->hasResourceFile()) { + $resFile = $this->getResourceResFile(); + if (file_exists($resFile)) { + $objectFiles[] = $resFile; + } + } + $linkCmd = $this->buildLinkCommand($objectFiles, $targetFile); $this->climate->comment($linkCmd); @@ -1241,6 +1354,26 @@ CODE; } } + // 读取 resource(Windows 资源配置:图标、版本信息等) + $resource = $cfg['resource'] ?? null; + if (!empty($resource)) { + if (!is_array($resource)) { + $this->error('`resource` must be array'); + } + // 验证图标文件是否存在 + if (!empty($resource['icon'])) { + $iconPath = $resource['icon']; + if (!preg_match('/^[A-Za-z]:\\|^\//', $iconPath)) { + $iconPath = $projectDir . DIRECTORY_SEPARATOR . $iconPath; + } + if (!file_exists($iconPath)) { + $this->error('Icon file not exists: `' . $resource['icon'] . '`'); + } + } + $this->resourceConfig = $resource; + $this->resourceConfig['_projectDir'] = $projectDir; + } + return $list; } diff --git a/svg-to-ico.js b/svg-to-ico.js new file mode 100644 index 00000000..4dfa28b0 --- /dev/null +++ b/svg-to-ico.js @@ -0,0 +1,147 @@ +/** + * SVG → ICO 转换脚本 (Node.js) + * + * 使用 sharp 库将 SVG 渲染为多尺寸 PNG,再合并为 ICO + * + * 用法: node svg-to-ico.js [output.ico] + */ + +const sharp = require('sharp'); +const fs = require('fs'); +const path = require('path'); + +const sizes = [16, 32, 48, 64, 128, 256]; + +async function main() { + const args = process.argv.slice(2); + if (args.length < 1) { + console.log('用法: node svg-to-ico.js [output.ico]'); + process.exit(1); + } + + const inputSvg = path.resolve(args[0]); + const outputIco = args.length >= 2 + ? path.resolve(args[1]) + : path.join(path.dirname(inputSvg), path.basename(inputSvg, '.svg') + '.ico'); + + if (!fs.existsSync(inputSvg)) { + console.log(`错误: SVG 文件不存在: ${inputSvg}`); + process.exit(1); + } + + console.log('=== SVG → ICO 转换工具 ===\n'); + console.log(`输入: ${inputSvg}`); + console.log(`输出: ${outputIco}\n`); + + const svgBuffer = fs.readFileSync(inputSvg); + + // 步骤1: 渲染 SVG 为多个尺寸的 PNG + const pngBuffers = {}; + for (const size of sizes) { + console.log(`[1/2] 渲染 SVG → PNG (${size}x${size})...`); + try { + const pngBuffer = await sharp(svgBuffer, { density: 300 }) + .resize(size, size, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 1 } }) + .png() + .toBuffer(); + pngBuffers[size] = pngBuffer; + console.log(` 完成: ${size}x${size} (${pngBuffer.length} bytes)`); + } catch (err) { + console.log(` 警告: ${size}x${size} PNG 生成失败: ${err.message},跳过`); + } + } + + const validSizes = Object.keys(pngBuffers).map(Number).sort((a, b) => a - b); + if (validSizes.length === 0) { + console.log('\n错误: 所有尺寸的 PNG 都生成失败'); + process.exit(1); + } + + // 步骤2: 合并为 ICO + console.log('\n[2/2] 合并 PNG → ICO...'); + const icoBuffer = createIcoFromPngs(pngBuffers, validSizes); + fs.writeFileSync(outputIco, icoBuffer); + + console.log(`\n成功! ICO 文件已生成: ${outputIco}`); + console.log(`包含尺寸: ${validSizes.join(', ')}`); + console.log(`文件大小: ${icoBuffer.length} bytes`); +} + +/** + * 将多个 PNG buffer 合并为 ICO 格式的 Buffer + */ +function createIcoFromPngs(pngBuffers, sizes) { + const imageCount = sizes.length; + + // 计算各部分大小 + const headerSize = 6; // ICO 头部 + const dirEntrySize = 16; // 每个目录条目 + const dirSize = dirEntrySize * imageCount; + + let dataOffset = headerSize + dirSize; + + // 构建目录条目 + const dirEntries = []; + for (const size of sizes) { + const pngData = pngBuffers[size]; + const dataSize = pngData.length; + + // Width/Height: 0 表示 256 + const w = size >= 256 ? 0 : size; + const h = size >= 256 ? 0 : size; + + dirEntries.push({ + width: w, + height: h, + colorCount: 0, + reserved: 0, + planes: 1, + bitCount: 32, + dataSize: dataSize, + offset: dataOffset + }); + + dataOffset += dataSize; + } + + // 计算总大小 + let totalSize = headerSize + dirSize; + for (const size of sizes) { + totalSize += pngBuffers[size].length; + } + + // 构建 ICO 二进制数据 + const buffer = Buffer.alloc(totalSize); + let pos = 0; + + // ICO 头部 (6 bytes) + buffer.writeUInt16LE(0, pos); pos += 2; // Reserved + buffer.writeUInt16LE(1, pos); pos += 2; // Type: 1 = ICO + buffer.writeUInt16LE(imageCount, pos); pos += 2; // Image count + + // 目录条目 + for (const entry of dirEntries) { + buffer.writeUInt8(entry.width, pos); pos += 1; + buffer.writeUInt8(entry.height, pos); pos += 1; + buffer.writeUInt8(entry.colorCount, pos); pos += 1; + buffer.writeUInt8(entry.reserved, pos); pos += 1; + buffer.writeUInt16LE(entry.planes, pos); pos += 2; + buffer.writeUInt16LE(entry.bitCount, pos); pos += 2; + buffer.writeUInt32LE(entry.dataSize, pos); pos += 4; + buffer.writeUInt32LE(entry.offset, pos); pos += 4; + } + + // 图像数据 + for (const size of sizes) { + const pngData = pngBuffers[size]; + pngData.copy(buffer, pos); + pos += pngData.length; + } + + return buffer; +} + +main().catch(err => { + console.error('错误:', err); + process.exit(1); +}); diff --git a/swoole-logo.ico b/swoole-logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..73e563cee7d1efa2ae1b8eab7f4127129f0cccb7 GIT binary patch literal 12229 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 M_YYnE_aou|4eqh$jsO4v literal 0 HcmV?d00001 diff --git a/swoole-logo.svg b/swoole-logo.svg new file mode 100644 index 00000000..6297cc67 --- /dev/null +++ b/swoole-logo.svg @@ -0,0 +1,15 @@ + + + + Transfon SWOOLE - Registered + + + + \ No newline at end of file diff --git a/version.txt b/version.txt index 6409db25..01ff2b71 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1049 \ No newline at end of file +1051 \ No newline at end of file