diff --git a/composer.json b/composer.json index 457e5aac..388cf62e 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "marcj/topsort": "^2.0", "symfony/var-dumper": "^8.0", "symfony/yaml": "^8.0", - "swoole/phpx": "~2.6.3", + "swoole/phpx": "~2.6.4", "ajaxray/ansikit": "^0.3.1" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 933af660..c450098a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "11856d5d63148d99436c5b7ff7f6c866", + "content-hash": "3262f7344d1d75ad17fdbf19f9f8896d", "packages": [ { "name": "ajaxray/ansikit", @@ -342,16 +342,16 @@ }, { "name": "swoole/phpx", - "version": "v2.6.3", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/swoole/phpx.git", - "reference": "bf24803c07f84780003356f06cb98b713d32d4f7" + "reference": "87a532b30fad8ff7b0a4afac6cd8aacc0fd01cbd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swoole/phpx/zipball/bf24803c07f84780003356f06cb98b713d32d4f7", - "reference": "bf24803c07f84780003356f06cb98b713d32d4f7", + "url": "https://api.github.com/repos/swoole/phpx/zipball/87a532b30fad8ff7b0a4afac6cd8aacc0fd01cbd", + "reference": "87a532b30fad8ff7b0a4afac6cd8aacc0fd01cbd", "shasum": "" }, "require": { @@ -382,9 +382,9 @@ ], "support": { "issues": "https://github.com/swoole/phpx/issues", - "source": "https://github.com/swoole/phpx/tree/v2.6.3" + "source": "https://github.com/swoole/phpx/tree/v2.6.4" }, - "time": "2026-08-24T11:56:43+00:00" + "time": "2026-08-25T08:02:28+00:00" }, { "name": "symfony/polyfill-ctype", diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 4a1a4392..922dd14d 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -1015,7 +1015,7 @@ YAML); $this->assertArrayNotHasKey('cxxflags', $options); } - public function testEmbeddedCompileOptionsPassProjectNameForModuleAccessor(): void + public function testEmbeddedCompileOptionsKeepProjectNameOutOfCommonPchOptions(): void { $this->compiler->setTargetName('module_accessor'); @@ -1023,8 +1023,8 @@ YAML); $this->setPropertyValue('buildMode', $mode); $options = $this->invokeMethod('getCommonCompileCommandOptions'); - $this->assertContains('TYPEPHP_PROJECT_NAME=module_accessor', $options['user_defines'], $mode); - $this->assertContains('TYPEPHP_RUNTIME_EXPORTS=1', $options['user_defines'], $mode); + $this->assertNotContains('TYPEPHP_PROJECT_NAME=module_accessor', $options['user_defines'], $mode); + $this->assertNotContains('TYPEPHP_RUNTIME_EXPORTS=1', $options['user_defines'], $mode); $this->assertSame( [], array_values(array_filter( @@ -1036,6 +1036,59 @@ YAML); } } + public function testProjectRuntimeEntryHasTargetDefineWithoutPchOrObjectCache(): void + { + $this->compiler->setTargetName('module_accessor'); + $this->setPropertyValue('buildMode', CompilerBase::BUILD_MODE_BIN); + $this->setPropertyValue('precompiledHeader', [ + 'header' => '/tmp/typephp_pch.hpp', + 'artifact' => '/tmp/typephp_pch.hpp.gch', + ]); + + $phpxDir = $this->invokeMethod('getPhpxDir'); + $entry = $phpxDir . '/src/misc/typephp_main.cc'; + $options = $this->invokeMethod('getSourceCompileCommandOptions', $entry, null); + + $this->assertContains('TYPEPHP_PROJECT_NAME=module_accessor', $options['user_defines']); + $this->assertContains('TYPEPHP_RUNTIME_EXPORTS=1', $options['user_defines']); + $this->assertArrayNotHasKey('forced_include', $options->toArray()); + $this->assertArrayNotHasKey('precompiled_header', $options->toArray()); + $this->assertFalse($this->compiler->hasMiscObjectFileCache($entry)); + $this->assertStringContainsString( + DIRECTORY_SEPARATOR . 'phpx-misc' . DIRECTORY_SEPARATOR . 'module_accessor' . DIRECTORY_SEPARATOR, + $this->compiler->getObjectFile($entry), + ); + } + + public function testProjectIndependentMiscObjectsUseSharedCacheScope(): void + { + $phpxDir = $this->invokeMethod('getPhpxDir'); + foreach (['typephp_runtime.cc', 'php_cli_process_title.c', 'ps_title.c'] as $sourceName) { + $object = $this->compiler->getObjectFile($phpxDir . '/src/misc/' . $sourceName); + $this->assertStringContainsString( + DIRECTORY_SEPARATOR . 'phpx-misc' . DIRECTORY_SEPARATOR . 'shared' . DIRECTORY_SEPARATOR, + $object, + $sourceName, + ); + } + } + + public function testProjectIndependentMiscObjectCacheSurvivesTargetNameChange(): void + { + $phpxDir = $this->invokeMethod('getPhpxDir'); + $source = $phpxDir . '/src/misc/ps_title.c'; + + $this->compiler->setTargetName('first_project'); + $object = $this->compiler->getObjectFile($source); + file_put_contents($object, 'object'); + touch($object, time() + 10); + $this->invokeMethod('writeMiscObjectCacheMetadata', $source, $object); + + $this->compiler->setTargetName('second_project'); + $this->assertSame($object, $this->compiler->getObjectFile($source)); + $this->assertTrue($this->compiler->hasMiscObjectFileCache($source)); + } + public function testMacosNativeBuildOptionsIncludeHomebrewSearchPaths(): void { $this->setPropertyValue('platform', new Macos()); diff --git a/src/Build/NativeCommandOptionsTrait.php b/src/Build/NativeCommandOptionsTrait.php index b1511c85..18f73071 100644 --- a/src/Build/NativeCommandOptionsTrait.php +++ b/src/Build/NativeCommandOptionsTrait.php @@ -23,10 +23,6 @@ trait NativeCommandOptionsTrait } $userDefines = $this->userDefines; - if ($this->isBuildModeEmbed()) { - $userDefines[] = 'TYPEPHP_PROJECT_NAME=' . $this->targetName; - $userDefines[] = 'TYPEPHP_RUNTIME_EXPORTS=1'; - } if ($this->isBuildModeLib()) { $userDefines[] = 'TYPEPHP_NO_MAIN=1'; $userDefines[] = $this->getLibraryExportsMacroName() . '=1'; @@ -110,6 +106,18 @@ trait NativeCommandOptionsTrait return $options; } + protected function getProjectRuntimeEntryCompileCommandOptions(): CompileOptions + { + $values = $this->getCompileCommandOptions()->toArray(); + // TYPEPHP_PROJECT_NAME is deliberately confined to the small, + // project-specific entry translation unit. Defining it while loading + // the common PCH would make every output target require a distinct PCH. + unset($values['forced_include'], $values['precompiled_header']); + $values['user_defines'][] = 'TYPEPHP_RUNTIME_EXPORTS=1'; + $values['user_defines'][] = 'TYPEPHP_PROJECT_NAME=' . $this->targetName; + return new CompileOptions($values); + } + protected function getLinkCommandOptions(): LinkOptions { $libraryPaths = array_merge($this->getLibraryPaths(), $this->linkPaths); diff --git a/src/Preprocessor.php b/src/Preprocessor.php index 8c6640af..a663b15e 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -240,7 +240,11 @@ class Preprocessor extends CompilerBase $normalizedMiscDir = str_replace('\\', '/', $this->getPhpxDir() . '/src/misc/'); if (str_starts_with($normalizedFile, $normalizedMiscDir)) { $separator = $this->getPlatform()->getPathSeparator(); - $objectDir = $this->buildDir . $separator . 'phpx-misc' . $separator . $this->targetName; + // Only typephp_main.cc contains project-specific symbols. All + // other PHPX misc sources are target-independent and share their + // cached object files within the build directory. + $cacheScope = $this->isProjectRuntimeEntryFile($cppFile) ? $this->targetName : 'shared'; + $objectDir = $this->buildDir . $separator . 'phpx-misc' . $separator . $cacheScope; if (!is_dir($objectDir)) { mkdir($objectDir, 0777, true); } @@ -250,6 +254,13 @@ class Preprocessor extends CompilerBase return $info['dirname'] . $this->getPlatform()->getPathSeparator() . $info['filename'] . $ext; } + protected function isProjectRuntimeEntryFile(string $file): bool + { + $normalizedFile = str_replace('\\', '/', $file); + $runtimeEntry = str_replace('\\', '/', $this->getPhpxDir() . '/src/misc/typephp_main.cc'); + return $normalizedFile === $runtimeEntry; + } + public function prepareFile(string $file): void { $previousPhase = $this->enterCompilerPhase(self::PHASE_PREPARE); diff --git a/src/Translator.php b/src/Translator.php index 4e0ee4ca..ea5b3a73 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -13,6 +13,7 @@ use Ajaxray\AnsiKit\Components\Progressbar; use MJS\TopSort\Implementations\StringSort; use TypePhp\Analysis\SsaBuilder; use TypePhp\Backend\CompilerFactory; +use TypePhp\Build\CompileOptions; use TypePhp\Build\FileScanner; use TypePhp\Build\NativeCommandOptionsTrait; use TypePhp\Build\NativeBuilder; @@ -1310,6 +1311,11 @@ CODE; */ public function hasMiscObjectFileCache(string $cppFile): bool { + // This translation unit emits project-specific runtime symbols and is + // intentionally rebuilt for every target. + if ($this->isProjectRuntimeEntryFile($cppFile)) { + return false; + } if ($this->climate->arguments->defined('force') || $this->enableProfiler) { return false; } @@ -1433,24 +1439,21 @@ CODE; public function compileFile(string $cppFile, string $objectFile, bool $parallel = false): void { - if ($this->isPhpxMiscFile($cppFile) && $this->hasMiscObjectFileCache($cppFile)) { + $isCacheableMiscFile = $this->isPhpxMiscFile($cppFile) + && !$this->isProjectRuntimeEntryFile($cppFile); + if ($isCacheableMiscFile && $this->hasMiscObjectFileCache($cppFile)) { if (!$parallel) { $this->climate->darkGray('[cache] skip: ' . $cppFile); } return; } - $isMiscFile = $this->isPhpxMiscFile($cppFile); - if ($isMiscFile) { + if ($isCacheableMiscFile) { $this->invalidateMiscObjectCache($objectFile); } $language = $this->getLanguageFromExtension($cppFile); - $options = match ($language) { - null => $this->getCompileCommandOptions(), - 'c' => $this->getCCompileCommandOptions(), - default => $this->getNativeCompileCommandOptions($language), - }; + $options = $this->getSourceCompileCommandOptions($cppFile, $language); $result = $this->getNativeBuilder()->compile($cppFile, $objectFile, $options, $language, $parallel); if (!$parallel) { $this->climate->comment($result['command']); @@ -1464,19 +1467,28 @@ CODE; $this->error('compile failed: ' . $cppFile); } - if ($isMiscFile) { + if ($isCacheableMiscFile) { $this->writeMiscObjectCacheMetadata($cppFile, $objectFile); } } - protected function buildCompileFileCommand(string $sourceFile, string $objectFile): string + protected function getSourceCompileCommandOptions(string $sourceFile, ?string $language): CompileOptions { - $language = $this->getLanguageFromExtension($sourceFile); - $options = match ($language) { + if ($this->isProjectRuntimeEntryFile($sourceFile)) { + return $this->getProjectRuntimeEntryCompileCommandOptions(); + } + + return match ($language) { null => $this->getCompileCommandOptions(), 'c' => $this->getCCompileCommandOptions(), default => $this->getNativeCompileCommandOptions($language), }; + } + + protected function buildCompileFileCommand(string $sourceFile, string $objectFile): string + { + $language = $this->getLanguageFromExtension($sourceFile); + $options = $this->getSourceCompileCommandOptions($sourceFile, $language); return $this->getNativeBuilder()->compileCommand($sourceFile, $objectFile, $options, $language); } @@ -1486,6 +1498,13 @@ CODE; // embed 需要 main 函数,以及 cli 的内置函数定义 if ($this->isBuildModeEmbed()) { + $runtimeSource = $this->getPhpxDir() . '/src/misc/typephp_runtime.cc'; + // PHPX 2.6.3 keeps the common runtime in typephp_main.cc. Newer + // PHPX versions split it out so the object can be shared across + // projects. Keep the old layout buildable during release rollout. + if (is_file($runtimeSource)) { + $sourceFiles[] = $runtimeSource; + } $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_main.cc'; }