diff --git a/docs/COMPILER_CLI.md b/docs/COMPILER_CLI.md index f6d6b464..a8fb6574 100644 --- a/docs/COMPILER_CLI.md +++ b/docs/COMPILER_CLI.md @@ -117,6 +117,18 @@ TypePHP 和 PHPX 的最低运行时版本均为 PHP 8.4。`--php-version` 与实 传入 `project.yml` 时,命令行参数优先于 YAML 中的同名配置。项目文件格式参见用户文档及代码中的项目配置解析器。 +### PHP 扩展依赖 + +程序依赖其他 PHP 扩展时,可以将必需模块写入 Zend 模块依赖表: + +```yaml +extension-dependencies: + - pdo_mysql + - curl +``` + +编译器会为每一项生成 `ZEND_MOD_REQUIRED`。Zend 在加载 TypePHP 模块时检查这些扩展是否已加载。该配置不表示原生链接库;C/C++ 链接依赖仍使用 `link-libs`。 + ## 查看权威帮助 命令行实现可能继续演进,发布版本的实际参数以以下命令为准: diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 34f4bbbe..58115e74 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -431,6 +431,10 @@ link-libs: link-paths: - /usr/local/lib - /opt/custom/lib +extension-dependencies: + - pdo_mysql + - curl + - curl YAML); $this->invokeMethod('parseProjectYaml', $projectFile); @@ -449,10 +453,62 @@ YAML); $this->assertTrue($this->compiler->isLtoEnabled()); $this->assertSame(['curl', 'ssl'], $this->compiler->getLinkLibs()); $this->assertSame(['/usr/local/lib', '/opt/custom/lib'], $this->compiler->getLinkPaths()); + $this->assertSame(['pdo_mysql', 'curl'], $this->compiler->getExtensionDependencies()); $this->assertSame('/tmp/project-build', $this->compiler->getBuildDir()); $this->assertTrue($this->getPropertyValue('formatCode')); } + public function testParseProjectYamlRejectsInvalidExtensionDependencies(): void + { + $projectFile = $this->createProjectFile(<<<'YAML' +sources: + - main.php +extension-dependencies: curl +YAML); + + $this->expectException(TestError::class); + $this->expectExceptionMessage('`extension-dependencies` must be array'); + $this->invokeMethod('parseProjectYaml', $projectFile); + } + + public function testExtensionDependenciesAreWrittenToZendModuleEntry(): void + { + global $translator; + $translator = $this->compiler; + $this->compiler->setBuildMode(CompilerBase::BUILD_MODE_EXT); + $projectFile = $this->createProjectFile(<<<'YAML' +sources: + - main.php +extension-dependencies: + - pdo_mysql + - curl +YAML); + $files = $this->invokeMethod('parseProjectYaml', $projectFile); + $this->compiler->addFiles($files); + foreach ($files as $file) { + $this->compiler->prepareFile($file); + $this->compiler->convertFile($file); + } + + $extension = file_get_contents($this->compiler->genExtension()); + + $this->assertStringContainsString( + "static const zend_module_dep typephp_app_module_deps[] = {\n" + . " ZEND_MOD_REQUIRED(\"pdo_mysql\")\n" + . " ZEND_MOD_REQUIRED(\"curl\")\n" + . " ZEND_MOD_END\n" + . '};', + $extension, + ); + $this->assertStringContainsString( + "zend_module_entry typephp_app_module_entry = {\n" + . " STANDARD_MODULE_HEADER_EX,\n" + . " nullptr,\n" + . " typephp_app_module_deps,", + $extension, + ); + } + public function testParseProjectYamlSupportsCustomFilenameAndRelativeBuildDir(): void { $projectFile = $this->createProjectFile(<<<'YAML' diff --git a/src/CompilerBase.php b/src/CompilerBase.php index e14c2d45..06d3bc14 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -373,6 +373,8 @@ class CompilerBase implements PropertyAccessContext 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 + /** @var list Required PHP modules recorded in zend_module_entry.deps. */ + protected array $extensionDependencies = []; protected int $floatPrecision = 17; protected bool $debug = false; protected bool $formatCode = false; // --format: enable clang-format (disabled by default) @@ -997,6 +999,12 @@ class CompilerBase implements PropertyAccessContext return $this->linkPaths; } + /** @return list */ + public function getExtensionDependencies(): array + { + return $this->extensionDependencies; + } + public function getMarch(): string { return $this->march; diff --git a/src/Translator.php b/src/Translator.php index 1237fb16..c5f37c2b 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -1243,9 +1243,24 @@ PHP_RSHUTDOWN_FUNCTION({$moduleName}) { module_clean(); return SUCCESS; } +CODE; + + if ($this->extensionDependencies === []) { + $moduleHeader = ' STANDARD_MODULE_HEADER,'; + } else { + $dependencyArray = $moduleName . '_module_deps'; + $code .= PHP_EOL . 'static const zend_module_dep ' . $dependencyArray . '[] = {' . PHP_EOL; + foreach ($this->extensionDependencies as $dependency) { + $code .= ' ZEND_MOD_REQUIRED(' . $this->genCharPtr($dependency, true) . ')' . PHP_EOL; + } + $code .= ' ZEND_MOD_END' . PHP_EOL . '};' . PHP_EOL; + $moduleHeader = " STANDARD_MODULE_HEADER_EX,\n nullptr,\n {$dependencyArray},"; + } + + $code .= <<error('`extension-dependencies` must be array'); + } + foreach ($dependencies as $dependency) { + if (!is_string($dependency) || trim($dependency) === '') { + $this->error('Each `extension-dependencies` entry must be a non-empty string'); + } + $dependency = trim($dependency); + if (str_contains($dependency, "\0")) { + $this->error('Extension dependency names must not contain NUL bytes'); + } + if (!in_array($dependency, $this->extensionDependencies, true)) { + $this->extensionDependencies[] = $dependency; + } + } + } + // 读取 output/name。name 只表示目标名,不能按 YAML 目录解析成输出路径。 $output = $cfg['output'] ?? null; if (!empty($output)) {