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); }