From 0d8f99f6cfe3367eeb57764347537a707b49a9a1 Mon Sep 17 00:00:00 2001 From: Yurun Date: Fri, 3 Jul 2026 21:14:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(build):=20=E6=94=AF=E6=8C=81=20Windows=20?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E7=A8=8B=E5=BA=8F=E6=B8=85=E5=8D=95=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.manifest | 52 +++++++++++++++++++++ project.yml | 6 +++ src/Php/Generator/ResourceFileGenerator.php | 43 +++++++++++++++-- src/Php/Translator.php | 21 ++++++++- 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 app.manifest diff --git a/app.manifest b/app.manifest new file mode 100644 index 00000000..2c598dbb --- /dev/null +++ b/app.manifest @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + PerMonitorV2 + + + + diff --git a/project.yml b/project.yml index 6f49c330..7c8d1a63 100644 --- a/project.yml +++ b/project.yml @@ -23,6 +23,12 @@ resource: product-name: "Swoole Compiler" comments: "PHP 原生编译器,可将 PHP 项目编译为 Windows/Linux/macOS 平台原生的可执行文件" +# Windows 应用程序清单文件(缺省不携带 manifest) +# 仅在 Windows 平台编译 bin 模式时生效 +# 清单文件为 XML 格式,可配置 UAC 权限级别、DPI 感知、运行时依赖等 +# 示例:如需启用,取消注释下一行并指定 manifest 文件路径 +# manifest: app.manifest + sources: - ./src/Php - ./src/Core diff --git a/src/Php/Generator/ResourceFileGenerator.php b/src/Php/Generator/ResourceFileGenerator.php index 9e44749b..f16e069c 100644 --- a/src/Php/Generator/ResourceFileGenerator.php +++ b/src/Php/Generator/ResourceFileGenerator.php @@ -27,6 +27,9 @@ namespace PhpAot\Php\Generator; * original-filename: "myapp.exe" * product-name: "My Product" * comments: "Built with Swoole Compiler" + * + * # manifest 与 resource 同级(可选,Windows 平台缺省不携带) + * manifest: path/to/app.manifest */ class ResourceFileGenerator { @@ -51,7 +54,9 @@ class ResourceFileGenerator */ public function hasResource(): bool { - return !empty($this->config['icon']) || !empty($this->config['version-info']); + return !empty($this->config['icon']) + || !empty($this->config['version-info']) + || !empty($this->config['manifest']); } /** @@ -64,13 +69,34 @@ class ResourceFileGenerator 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]:\\\\|^\//', $icon)) { - return $icon; + if (preg_match('/^[A-Za-z]:\\\\|^\//', $path)) { + return $path; } // 相对路径,基于项目目录解析 - return $this->projectDir . DIRECTORY_SEPARATOR . $icon; + return $this->projectDir . DIRECTORY_SEPARATOR . $path; } /** @@ -91,6 +117,15 @@ class ResourceFileGenerator $content .= '#include ' . 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) { diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 0e5bac32..63270a84 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -2223,7 +2223,7 @@ CODE; } } - // 读取 resource(Windows 资源配置:图标、版本信息等) + // 读取 resource(Windows 资源配置:图标、版本信息) $resource = $cfg['resource'] ?? null; if (!empty($resource)) { if (!is_array($resource)) { @@ -2243,6 +2243,25 @@ CODE; $this->resourceConfig['_projectDir'] = $projectDir; } + // 读取 manifest(Windows 清单文件,与 resource 同级,缺省不携带) + $manifest = $cfg['manifest'] ?? null; + if (!empty($manifest)) { + if (!is_string($manifest)) { + $this->error('`manifest` must be a string (path to manifest file)'); + } + $manifestPath = $manifest; + if (!preg_match('/^[A-Za-z]:\\|^\//', $manifestPath)) { + $manifestPath = $projectDir . DIRECTORY_SEPARATOR . $manifestPath; + } + if (!file_exists($manifestPath)) { + $this->error('Manifest file not exists: `' . $manifest . '`'); + } + if (empty($this->resourceConfig)) { + $this->resourceConfig = ['_projectDir' => $projectDir]; + } + $this->resourceConfig['manifest'] = $manifest; + } + return $this->filterIgnoredFiles($list); }