diff --git a/docs/COMPILER_CLI.md b/docs/COMPILER_CLI.md index ac278033..1d412863 100644 --- a/docs/COMPILER_CLI.md +++ b/docs/COMPILER_CLI.md @@ -319,6 +319,14 @@ OPTIONS: -m, --mode Compilation mode, -m bin(binary) or -m ext(extension), default: bin -j, --job Number of parallel compilation jobs (default: 4) --no-literal-strings Disable literal strings optimization + -I, --include-path Add an additional C++ include directory (repeatable) + -D, --define Define a preprocessor macro (repeatable, e.g. -D FOO=bar) + --dry Dry run: only generate C++ code, skip compilation and linking + --lto Enable Link Time Optimization (-flto) + --format Enable clang-format code formatting (disabled by default) + -l, --link-lib Link against a library (repeatable, e.g. -lcurl) + -L, --link-path Add a library search path (repeatable, e.g. -L/usr/local/lib) + --build-dir Specify build directory for generated C++ code EXAMPLES: ./bin/compiler.php examples/hello.php @@ -367,6 +375,194 @@ EXAMPLES: ./bin/compiler.php project.yml -O2 -j 16 -v ``` +### 场景六:使用外部 C++ 库 + +```bash +# 添加自定义头文件路径和预处理器宏 +./bin/compiler.php app.php -I /opt/mylib/include -I ../shared/include -D MY_DEBUG=1 -O2 +``` + +--- + +### 12. `-I ` / `--include-path ` - 添加头文件搜索路径 + +**别名**: `--include-path` +**类型**: 可重复参数 + +添加额外的 C++ 头文件(`.h` / `.hpp`)搜索目录。编译器的 include 路径由两部分组成: +1. 系统默认路径(PHP 头文件、PHPX 头文件等) +2. 用户通过 `-I` 指定的自定义路径 + +**适用场景**: +- ✅ 引用外部 C/C++ 库的头文件 +- ✅ 项目有自定义的 C++ 扩展代码 +- ✅ 多个项目共享的头文件目录 + +**可重复使用**: 可以多次指定 `-I` 添加多个目录: +```bash +./bin/compiler.php app.php \ + -I /opt/openssl/include \ + -I /opt/mylib/include \ + -I ../shared/headers +``` + +**与 C++ 编译器等价**: `-I ` 直接传递给 GCC/Clang/MSVC 的 `-I` 选项。 + +--- + +### 13. `-D ` / `--define ` - 定义预处理器宏 + +**别名**: `--define` +**类型**: 可重复参数 + +定义 C++ 预处理器宏,等价于在 C++ 代码中使用 `#define`。使用 `name=value` 格式。 + +**适用场景**: +- ✅ 条件编译(`#ifdef MY_FEATURE` / `#ifndef MY_FEATURE`) +- ✅ 功能开关(`-D ENABLE_LOGGING=1`) +- ✅ 调试标志(`-D DEBUG_LEVEL=3`) +- ✅ 版本号定义(`-D APP_VERSION=\"2.0\"`) + +**格式说明**: +| 格式 | 等价 C++ 代码 | 说明 | +|------|--------------|------| +| `-D FOO` | `#define FOO` | 无值宏(值为空) | +| `-D FOO=1` | `#define FOO 1` | 整数值宏 | +| `-D FOO=bar` | `#define FOO bar` | 字符串值宏 | +| `-D FOO=\"bar\"` | `#define FOO "bar"` | 引号字符串宏 | + +**可重复使用**: 可以多次指定 `-D` 定义多个宏: +```bash +./bin/compiler.php app.php \ + -D MY_DEBUG=1 \ + -D LOG_LEVEL=3 \ + -D APP_NAME=\\"MyApp\" +``` + +**与 C++ 编译器等价**: +| 编译器 | 产生的编译标志 | +|--------|-------------| +| GCC / Clang | `-D` | +| MSVC | `/D` | + +**示例: 条件编译控制功能开关**: +```bash +# 启用调试日志 +./bin/compiler.php app.php -D ENABLE_LOGGING=1 -O2 + +# 生产环境(关闭调试) +./bin/compiler.php app.php -O2 +``` + +--- + +### 14. `--lto` - 链接时优化 + +**类型**: 开关(无参数) + +启用链接时优化(Link Time Optimization, LTO),允许编译器在链接阶段跨编译单元进行优化,可显著提升运行时性能和减小二进制体积。 + +**编译器适配**: +| 编译器 | 编译阶段标志 | 链接阶段标志 | +|--------|-----------|-----------| +| GCC | `-flto` | `-flto` | +| Clang | `-flto` | `-flto` | +| MSVC | `/GL` | `/LTCG` | + +**适用场景**: +- ✅ 生产环境部署(配合 `-O2` 或 `-O3`) +- ✅ 对性能有极致要求的应用 +- ✅ 需要减小二进制体积的场景 +- ⚠️ 会增加链接时间 + +**示例**: +```bash +# 启用 LTO 的生产环境编译 +./bin/compiler.php app.php -O2 --lto + +# 与自定义 include 和 define 组合使用 +./bin/compiler.php app.php -O3 --lto -I /opt/lib/include -D NDEBUG=1 +``` + +--- + +### 15. `--format` - 代码格式化 + +**类型**: 开关(无参数) +**默认**: 关闭 + +启用 `clang-format` 对生成的 C++ 代码进行自动格式化。由于格式化会增加编译时间,默认关闭。需要系统中安装了 `clang-format` 才能生效。 + +**适用场景**: +- ✅ 需要审查生成的 C++ 代码 +- ✅ 团队开发需要统一的代码风格 +- ⚠️ 会增加编译时间 + +**示例**: +```bash +# 编译时启用代码格式化 +./bin/compiler.php app.php --format + +# 结合优化使用 +./bin/compiler.php app.php -O2 --format +``` + +如果系统未安装 `clang-format`,使用 `--format` 时会显示警告并跳过格式化。 + +--- + +### 16. `-l ` / `--link-lib ` - 链接库 + +**别名**: `--link-lib` +**类型**: 可重复参数 + +指定要链接的库,等价于 GCC/Clang 的 `-l` 选项。实际产生的标志为 `-l`。 + +**适用场景**: +- ✅ 链接第三方 C/C++ 库(如 `-lcurl`、`-lssl`) +- ✅ 链接自定义编译的静态库/动态库 +- ✅ 多库依赖的项目 + +**可重复使用**: 可以多次指定 `-l` 链接多个库: +```bash +./bin/compiler.php app.php \ + -lcurl \ + -lssl \ + -lcrypto + +# 等价的长格式 +./bin/compiler.php app.php --link-lib curl --link-lib ssl --link-lib crypto +``` + +**与 GCC/Clang 等价**: `-l` 直接传递给链接器的 `-l` 选项,链接 `lib.so` 或 `lib.a`。 + +--- + +### 17. `-L ` / `--link-path ` - 库搜索路径 + +**别名**: `--link-path` +**类型**: 可重复参数 + +添加库文件搜索路径,等价于 GCC/Clang 的 `-L` 选项。实际产生的标志为 `-L`。 + +**适用场景**: +- ✅ 链接非标准路径下的库文件 +- ✅ 使用自定义编译的本地库 +- ✅ 链接项目内部的私有库 + +**可重复使用**: 可以多次指定 `-L` 添加多个搜索路径: +```bash +./bin/compiler.php app.php \ + -L/usr/local/lib \ + -L/opt/custom/lib \ + -lmycustom + +# 等价的长格式 +./bin/compiler.php app.php --link-path /usr/local/lib --link-path /opt/custom/lib --link-lib mycustom +``` + +**与 GCC/Clang 等价**: `-L` 直接传递给链接器的 `-L` 选项。 + --- ## 🔧 编译器选择 @@ -509,13 +705,14 @@ convert: /path/to/file.php - ✅ 生成对应的 `.cpp` 文件 - ✅ 处理类型映射 -### 阶段四:格式化 (Format) +### 阶段四:格式化 (Format)(需 `--format` 开启) ``` format: /path/to/build/file.cpp cd /path && clang-format -i /path/to/build/file.cpp ``` +- 🔘 需 `--format` 参数显式开启 - ✅ 使用 clang-format 格式化 C++ 代码 - ✅ 确保代码风格一致 @@ -650,6 +847,9 @@ skip: /path/to/file.php # 高性能构建 ./bin/compiler.php app.php -O3 -j 16 -p +# 外部库集成构建 +./bin/compiler.php app.php -I /opt/mylib/include -D ENABLE_FEATURE=1 -O2 + # 调试构建 ./bin/compiler.php app.php -O0 -v --debug diff --git a/src/Php/Backend/Clang.php b/src/Php/Backend/Clang.php index d8f9ce4c..a7717ca0 100644 --- a/src/Php/Backend/Clang.php +++ b/src/Php/Backend/Clang.php @@ -420,6 +420,18 @@ class Clang extends CompilerBackend $cmd .= ' ' . $config['cxxflags']; } + // 用户自定义预处理器宏 + if (!empty($config['user_defines'])) { + foreach ($config['user_defines'] as $define) { + $cmd .= ' -D' . $define; + } + } + + // LTO(链接时优化) + if (!empty($config['lto'])) { + $cmd .= ' -flto'; + } + return $cmd; } @@ -472,7 +484,12 @@ class Clang extends CompilerBackend if (!empty($config['sanitize'])) { $cmd .= ' -fsanitize=' . $config['sanitize']; } - + + // LTO(链接时优化) + if (!empty($config['lto'])) { + $cmd .= ' -flto'; + } + return $cmd; } } diff --git a/src/Php/Backend/Gcc.php b/src/Php/Backend/Gcc.php index f50c4e87..452ab1b4 100644 --- a/src/Php/Backend/Gcc.php +++ b/src/Php/Backend/Gcc.php @@ -302,6 +302,18 @@ class Gcc extends CompilerBackend $cmd .= ' ' . $config['cxxflags']; } + // 用户自定义预处理器宏 + if (!empty($config['user_defines'])) { + foreach ($config['user_defines'] as $define) { + $cmd .= ' -D' . $define; + } + } + + // LTO(链接时优化) + if (!empty($config['lto'])) { + $cmd .= ' -flto'; + } + return $cmd; } @@ -336,7 +348,12 @@ class Gcc extends CompilerBackend $cmd .= ' -fsanitize=undefined'; } } - + + // LTO(链接时优化) + if (!empty($config['lto'])) { + $cmd .= ' -flto'; + } + return $cmd; } } diff --git a/src/Php/Backend/Msvc.php b/src/Php/Backend/Msvc.php index 751995b8..2da7e8bf 100644 --- a/src/Php/Backend/Msvc.php +++ b/src/Php/Backend/Msvc.php @@ -389,16 +389,28 @@ class Msvc extends CompilerBackend $cmd .= ' ' . $config['cxxflags']; } + // 用户自定义预处理器宏 + if (!empty($config['user_defines'])) { + foreach ($config['user_defines'] as $define) { + $cmd .= ' /D' . $define; + } + } + + // LTO(全程序优化 /GL + 链接时代码生成 /LTCG) + if (!empty($config['lto'])) { + $cmd .= ' /GL'; + } + return $cmd; } - + /** * 构建链接选项(实现抽象方法) */ public function buildLinkOptions(array $config = []): string { $cmd = ''; - + // 调试 if (!empty($config['debug'])) { $cmd .= ' /DEBUG'; @@ -420,6 +432,11 @@ class Msvc extends CompilerBackend // nologo $cmd .= ' /nologo'; + // LTO(链接时代码生成) + if (!empty($config['lto'])) { + $cmd .= ' /LTCG'; + } + return $cmd; } diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index e0ea530c..6e7cb76b 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -242,14 +242,19 @@ class CompilerBase extends \PhpAot\Core\Translator protected string $cxxFlags = ''; protected string $cxxStd = 'c++17'; protected string $ldflags = ''; + protected array $linkLibs = []; // --link-lib / -l: user-specified libraries to link + protected array $linkPaths = []; // --link-path / -L: user-specified library search paths protected int $floatPrecision = 17; protected bool $debug = false; - protected bool $formatCode = true; + protected bool $formatCode = false; // --format: enable clang-format (disabled by default) protected bool $printBacktraceOnError = true; protected bool $noLiteralStrings = false; protected bool $noConsole = false; // Windows: hide console window protected string $sanitize = ''; // Sanitizer type (address, undefined, etc.) protected bool $dryRun = false; // Dry run: only generate C++ code, skip compile & link + protected array $userIncludePaths = []; // --include-path / -I: user-provided C++ include dirs + protected array $userDefines = []; // --define / -D: user-provided preprocessor macros + protected bool $enableLto = false; // --lto: enable Link Time Optimization (-flto) protected string $file; protected string $dir; @@ -743,6 +748,31 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->buildDir; } + public function getUserIncludePaths(): array + { + return $this->userIncludePaths; + } + + public function getUserDefines(): array + { + return $this->userDefines; + } + + public function isLtoEnabled(): bool + { + return $this->enableLto; + } + + public function getLinkLibs(): array + { + return $this->linkLibs; + } + + public function getLinkPaths(): array + { + return $this->linkPaths; + } + public function getRelativePath($path, $cwd = ''): string { $cwd = $cwd ?: getcwd(); diff --git a/src/Php/Constants.php b/src/Php/Constants.php index f2f4e23a..decf8b04 100644 --- a/src/Php/Constants.php +++ b/src/Php/Constants.php @@ -175,6 +175,46 @@ class Constants 'required' => false, 'noValue' => true, ], + 'include-path' => [ + 'prefix' => 'I', + 'longPrefix' => 'include-path', + 'description' => 'Add an additional C++ include directory (repeatable)', + 'required' => false, + 'multiple' => true, + ], + 'define' => [ + 'prefix' => 'D', + 'longPrefix' => 'define', + 'description' => 'Define a preprocessor macro (repeatable, e.g. -D FOO=bar)', + 'required' => false, + 'multiple' => true, + ], + 'lto' => [ + 'longPrefix' => 'lto', + 'description' => 'Enable Link Time Optimization (-flto)', + 'required' => false, + 'noValue' => true, + ], + 'format' => [ + 'longPrefix' => 'format', + 'description' => 'Enable clang-format code formatting (disabled by default)', + 'required' => false, + 'noValue' => true, + ], + 'link-lib' => [ + 'prefix' => 'l', + 'longPrefix' => 'link-lib', + 'description' => 'Link against a library (repeatable, e.g. -lcurl)', + 'required' => false, + 'multiple' => true, + ], + 'link-path' => [ + 'prefix' => 'L', + 'longPrefix' => 'link-path', + 'description' => 'Add a library search path (repeatable, e.g. -L/usr/local/lib)', + 'required' => false, + 'multiple' => true, + ], ]; /** diff --git a/src/Php/Translator.php b/src/Php/Translator.php index b776fea0..9f0d7ef9 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -223,6 +223,12 @@ class Translator extends Preprocessor $climate->tab()->out('--sanitize Enable sanitizers (address, undefined, etc.)'); $climate->tab()->out('--build-dir Specify build directory for generated C++ code (default: /build)'); $climate->tab()->out('--dry Dry run: only generate C++ code, skip compilation and linking'); + $climate->tab()->out('-I, --include-path Add an additional C++ include directory (repeatable)'); + $climate->tab()->out('-D, --define Define a preprocessor macro (repeatable, e.g. -D FOO=bar)'); + $climate->tab()->out('--lto Enable Link Time Optimization (-flto)'); + $climate->tab()->out('--format Enable clang-format code formatting (disabled by default)'); + $climate->tab()->out('-l, --link-lib Link against a library (repeatable, e.g. -lcurl)'); + $climate->tab()->out('-L, --link-path Add a library search path (repeatable, e.g. -L/usr/local/lib)'); $climate->br(); $climate->bold('EXAMPLES:'); @@ -238,6 +244,7 @@ class Translator extends Preprocessor $climate->tab()->out($cmd . ' app.php -r -O2 -- --flag1 value1'); $climate->tab()->out($cmd . ' hello.php --dry (only generate C++ code, skip compilation)'); $climate->tab()->out($cmd . ' app.php --build-dir /tmp/mybuild -O2 (specify build directory)'); + $climate->tab()->out($cmd . ' app.php -I /opt/mylib/include -D MY_DEBUG=1 -O2 (custom includes and defines)'); $climate->br(); } @@ -312,6 +319,78 @@ class Translator extends Preprocessor if ($this->climate->arguments->defined('dry')) { $this->dryRun = true; } + + // 用户自定义 C++ include 路径(直接从 argv 解析以支持多值) + $this->userIncludePaths = $this->parseRepeatableArgv(['-I', '--include-path']); + // 用户自定义预处理器宏(直接从 argv 解析以支持多值) + $this->userDefines = $this->parseRepeatableArgv(['-D', '--define']); + + // 链接时优化 + if ($this->climate->arguments->defined('lto')) { + $this->enableLto = true; + } + + // clang-format 代码格式化(默认关闭,需显式 --format 开启) + if ($this->climate->arguments->defined('format')) { + $clangFormatVersion = shell_exec('clang-format --version'); + if (!empty($clangFormatVersion)) { + $this->formatCode = true; + } else { + $this->climate->warning('--format requested but clang-format not found, skipping formatting'); + } + } + + // 用户自定义链接库(直接从 argv 解析以支持多值) + $this->linkLibs = $this->parseRepeatableArgv(['-l', '--link-lib']); + // 用户自定义库搜索路径(直接从 argv 解析以支持多值) + $this->linkPaths = $this->parseRepeatableArgv(['-L', '--link-path']); + } + + /** + * 从原始 $argv 中解析可重复参数,支持 -X val 和 --long val 两种形式。 + * CLImate 的 multiple 选项只能保留最后一个值,因此需要手动解析。 + * + * @param string[] $flags 要匹配的标志列表,如 ['-I', '--include-path'] + * @return string[] 收集到的所有值 + */ + protected function parseRepeatableArgv(array $flags): array + { + global $argv; + $values = []; + for ($i = 1; $i < count($argv); $i++) { + // 精确匹配标志(如 -I, --include-path) + if (in_array($argv[$i], $flags, true) && isset($argv[$i + 1]) && $argv[$i + 1] !== '' && $argv[$i + 1][0] !== '-') { + $values[] = $argv[$i + 1]; + $i++; // 跳过值 + } + // 合并形式:-I/path 或 --include-path=/path + elseif (!$this->isLongFlagWithEquals($argv[$i], $flags, $values)) { + // 检查短标志合并:-I/path + foreach ($flags as $flag) { + if (strlen($flag) === 2 && $flag[0] === '-') { + $short = substr($flag, 1); + if (preg_match('/^-' . preg_quote($short, '/') . '(.+)$/', $argv[$i], $m)) { + $values[] = $m[1]; + } + } + } + } + } + return $values; + } + + /** + * 处理 --flag=value 格式的长标志 + */ + private function isLongFlagWithEquals(string $arg, array $flags, array &$values): bool + { + foreach ($flags as $flag) { + if (str_starts_with($flag, '--') && preg_match('/^' . preg_quote($flag, '/') . '=(.+)$/', $arg, $m)) { + $values[] = $m[1]; + return true; + } + } + return false; } private function showVersion(): void @@ -489,11 +568,6 @@ class Translator extends Preprocessor } } - $clangFormatVersion = shell_exec('clang-format --version'); - if (empty($clangFormatVersion)) { - $this->formatCode = false; - } - $files = $this->getFiles($path); // 应用 ignorePaths 过滤 if (!empty($this->ignorePaths)) { @@ -1312,8 +1386,14 @@ CODE; protected function getCompileCommandOptions(): array { + // 包含路径:系统路径 + 用户自定义路径 + $includePaths = $this->getIncludePaths(); + if (!empty($this->userIncludePaths)) { + $includePaths = array_merge($includePaths, $this->userIncludePaths); + } + return [ - 'include_paths' => $this->getIncludePaths(), + 'include_paths' => $includePaths, 'optimize' => $this->optimizeLevel, 'debug' => $this->debug, 'sanitize' => $this->sanitize, @@ -1324,6 +1404,8 @@ CODE; 'prof_output' => $this->targetName . '.prof', 'suppressed_warnings' => Constants::MSVC_SUPPRESSED_WARNINGS ?? [], 'cxxflags' => $this->cxxFlags, + 'user_defines' => $this->userDefines, + 'lto' => $this->enableLto, ]; } @@ -1365,6 +1447,16 @@ CODE; $ldflags .= ' -lprofiler'; } + // 用户通过 --link-lib / -l 指定的链接库 + foreach ($this->linkLibs as $lib) { + $ldflags .= ' -l' . $lib; + } + + // 用户通过 --link-path / -L 指定的库搜索路径 + foreach ($this->linkPaths as $path) { + $ldflags .= ' -L' . escapeshellarg($path); + } + $options = [ 'library_paths' => $this->getLibraryPaths(), 'libraries' => $this->getLibraries(), @@ -1373,6 +1465,7 @@ CODE; 'no_console' => $this->noConsole, 'build_mode' => $this->buildMode, 'sanitize' => $this->sanitize, + 'lto' => $this->enableLto, ]; $rpaths = $this->getPlatform()->getDefaultRpaths( @@ -1798,6 +1891,22 @@ CODE; } } + // 读取 link-libs(支持中横线和下划线) + $linkLibs = $cfg['link-libs'] ?? $cfg['link_libs'] ?? null; + if (!empty($linkLibs) && is_array($linkLibs)) { + foreach ($linkLibs as $lib) { + $this->linkLibs[] = (string)$lib; + } + } + + // 读取 link-paths(支持中横线和下划线) + $linkPaths = $cfg['link-paths'] ?? $cfg['link_paths'] ?? null; + if (!empty($linkPaths) && is_array($linkPaths)) { + foreach ($linkPaths as $path) { + $this->linkPaths[] = (string)$path; + } + } + // 读取 name if (!empty($cfg['name'])) { $this->setTargetName($cfg['name']);