refactor(build): separate project-specific compile options from common pch

- Remove TYPEPHP_PROJECT_NAME and TYPEPHP_RUNTIME_EXPORTS from common compile options
- Add dedicated getProjectRuntimeEntryCompileCommandOptions method for project-specific defines
- Update testEmbeddedCompileOptionsPassProjectNameForModuleAccessor to reflect new behavior
- Create new testProjectRuntimeEntryHasTargetDefineWithoutPchOrObjectCache
- Add testProjectIndependentMiscObjectsUseSharedCacheScope and testProjectIndependentMiscObjectCacheSurvivesTargetNameChange
- Update composer.json to use swoole/phpx ~2.6.4
- Modify object file caching logic to handle project-specific vs shared misc objects
- Add isProjectRuntimeEntryFile helper method for determining cache scope
- Update compileFile method to use source-specific compile options
- Handle different PHPX runtime source file layouts during embed mode builds
master
韩天峰 11 hours ago
parent 305d0d09d9
commit e4bb7b2605
  1. 2
      composer.json
  2. 14
      composer.lock
  3. 59
      phpunit/src/CompilerBaseApiTest.php
  4. 16
      src/Build/NativeCommandOptionsTrait.php
  5. 13
      src/Preprocessor.php
  6. 43
      src/Translator.php

@ -12,7 +12,7 @@
"marcj/topsort": "^2.0", "marcj/topsort": "^2.0",
"symfony/var-dumper": "^8.0", "symfony/var-dumper": "^8.0",
"symfony/yaml": "^8.0", "symfony/yaml": "^8.0",
"swoole/phpx": "~2.6.3", "swoole/phpx": "~2.6.4",
"ajaxray/ansikit": "^0.3.1" "ajaxray/ansikit": "^0.3.1"
}, },
"require-dev": { "require-dev": {

14
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "11856d5d63148d99436c5b7ff7f6c866", "content-hash": "3262f7344d1d75ad17fdbf19f9f8896d",
"packages": [ "packages": [
{ {
"name": "ajaxray/ansikit", "name": "ajaxray/ansikit",
@ -342,16 +342,16 @@
}, },
{ {
"name": "swoole/phpx", "name": "swoole/phpx",
"version": "v2.6.3", "version": "v2.6.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/swoole/phpx.git", "url": "https://github.com/swoole/phpx.git",
"reference": "bf24803c07f84780003356f06cb98b713d32d4f7" "reference": "87a532b30fad8ff7b0a4afac6cd8aacc0fd01cbd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/swoole/phpx/zipball/bf24803c07f84780003356f06cb98b713d32d4f7", "url": "https://api.github.com/repos/swoole/phpx/zipball/87a532b30fad8ff7b0a4afac6cd8aacc0fd01cbd",
"reference": "bf24803c07f84780003356f06cb98b713d32d4f7", "reference": "87a532b30fad8ff7b0a4afac6cd8aacc0fd01cbd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -382,9 +382,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/swoole/phpx/issues", "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", "name": "symfony/polyfill-ctype",

@ -1015,7 +1015,7 @@ YAML);
$this->assertArrayNotHasKey('cxxflags', $options); $this->assertArrayNotHasKey('cxxflags', $options);
} }
public function testEmbeddedCompileOptionsPassProjectNameForModuleAccessor(): void public function testEmbeddedCompileOptionsKeepProjectNameOutOfCommonPchOptions(): void
{ {
$this->compiler->setTargetName('module_accessor'); $this->compiler->setTargetName('module_accessor');
@ -1023,8 +1023,8 @@ YAML);
$this->setPropertyValue('buildMode', $mode); $this->setPropertyValue('buildMode', $mode);
$options = $this->invokeMethod('getCommonCompileCommandOptions'); $options = $this->invokeMethod('getCommonCompileCommandOptions');
$this->assertContains('TYPEPHP_PROJECT_NAME=module_accessor', $options['user_defines'], $mode); $this->assertNotContains('TYPEPHP_PROJECT_NAME=module_accessor', $options['user_defines'], $mode);
$this->assertContains('TYPEPHP_RUNTIME_EXPORTS=1', $options['user_defines'], $mode); $this->assertNotContains('TYPEPHP_RUNTIME_EXPORTS=1', $options['user_defines'], $mode);
$this->assertSame( $this->assertSame(
[], [],
array_values(array_filter( 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 public function testMacosNativeBuildOptionsIncludeHomebrewSearchPaths(): void
{ {
$this->setPropertyValue('platform', new Macos()); $this->setPropertyValue('platform', new Macos());

@ -23,10 +23,6 @@ trait NativeCommandOptionsTrait
} }
$userDefines = $this->userDefines; $userDefines = $this->userDefines;
if ($this->isBuildModeEmbed()) {
$userDefines[] = 'TYPEPHP_PROJECT_NAME=' . $this->targetName;
$userDefines[] = 'TYPEPHP_RUNTIME_EXPORTS=1';
}
if ($this->isBuildModeLib()) { if ($this->isBuildModeLib()) {
$userDefines[] = 'TYPEPHP_NO_MAIN=1'; $userDefines[] = 'TYPEPHP_NO_MAIN=1';
$userDefines[] = $this->getLibraryExportsMacroName() . '=1'; $userDefines[] = $this->getLibraryExportsMacroName() . '=1';
@ -110,6 +106,18 @@ trait NativeCommandOptionsTrait
return $options; 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 protected function getLinkCommandOptions(): LinkOptions
{ {
$libraryPaths = array_merge($this->getLibraryPaths(), $this->linkPaths); $libraryPaths = array_merge($this->getLibraryPaths(), $this->linkPaths);

@ -240,7 +240,11 @@ class Preprocessor extends CompilerBase
$normalizedMiscDir = str_replace('\\', '/', $this->getPhpxDir() . '/src/misc/'); $normalizedMiscDir = str_replace('\\', '/', $this->getPhpxDir() . '/src/misc/');
if (str_starts_with($normalizedFile, $normalizedMiscDir)) { if (str_starts_with($normalizedFile, $normalizedMiscDir)) {
$separator = $this->getPlatform()->getPathSeparator(); $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)) { if (!is_dir($objectDir)) {
mkdir($objectDir, 0777, true); mkdir($objectDir, 0777, true);
} }
@ -250,6 +254,13 @@ class Preprocessor extends CompilerBase
return $info['dirname'] . $this->getPlatform()->getPathSeparator() . $info['filename'] . $ext; 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 public function prepareFile(string $file): void
{ {
$previousPhase = $this->enterCompilerPhase(self::PHASE_PREPARE); $previousPhase = $this->enterCompilerPhase(self::PHASE_PREPARE);

@ -13,6 +13,7 @@ use Ajaxray\AnsiKit\Components\Progressbar;
use MJS\TopSort\Implementations\StringSort; use MJS\TopSort\Implementations\StringSort;
use TypePhp\Analysis\SsaBuilder; use TypePhp\Analysis\SsaBuilder;
use TypePhp\Backend\CompilerFactory; use TypePhp\Backend\CompilerFactory;
use TypePhp\Build\CompileOptions;
use TypePhp\Build\FileScanner; use TypePhp\Build\FileScanner;
use TypePhp\Build\NativeCommandOptionsTrait; use TypePhp\Build\NativeCommandOptionsTrait;
use TypePhp\Build\NativeBuilder; use TypePhp\Build\NativeBuilder;
@ -1310,6 +1311,11 @@ CODE;
*/ */
public function hasMiscObjectFileCache(string $cppFile): bool 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) { if ($this->climate->arguments->defined('force') || $this->enableProfiler) {
return false; return false;
} }
@ -1433,24 +1439,21 @@ CODE;
public function compileFile(string $cppFile, string $objectFile, bool $parallel = false): void 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) { if (!$parallel) {
$this->climate->darkGray('[cache] skip: ' . $cppFile); $this->climate->darkGray('[cache] skip: ' . $cppFile);
} }
return; return;
} }
$isMiscFile = $this->isPhpxMiscFile($cppFile); if ($isCacheableMiscFile) {
if ($isMiscFile) {
$this->invalidateMiscObjectCache($objectFile); $this->invalidateMiscObjectCache($objectFile);
} }
$language = $this->getLanguageFromExtension($cppFile); $language = $this->getLanguageFromExtension($cppFile);
$options = match ($language) { $options = $this->getSourceCompileCommandOptions($cppFile, $language);
null => $this->getCompileCommandOptions(),
'c' => $this->getCCompileCommandOptions(),
default => $this->getNativeCompileCommandOptions($language),
};
$result = $this->getNativeBuilder()->compile($cppFile, $objectFile, $options, $language, $parallel); $result = $this->getNativeBuilder()->compile($cppFile, $objectFile, $options, $language, $parallel);
if (!$parallel) { if (!$parallel) {
$this->climate->comment($result['command']); $this->climate->comment($result['command']);
@ -1464,19 +1467,28 @@ CODE;
$this->error('compile failed: ' . $cppFile); $this->error('compile failed: ' . $cppFile);
} }
if ($isMiscFile) { if ($isCacheableMiscFile) {
$this->writeMiscObjectCacheMetadata($cppFile, $objectFile); $this->writeMiscObjectCacheMetadata($cppFile, $objectFile);
} }
} }
protected function buildCompileFileCommand(string $sourceFile, string $objectFile): string protected function getSourceCompileCommandOptions(string $sourceFile, ?string $language): CompileOptions
{ {
$language = $this->getLanguageFromExtension($sourceFile); if ($this->isProjectRuntimeEntryFile($sourceFile)) {
$options = match ($language) { return $this->getProjectRuntimeEntryCompileCommandOptions();
}
return match ($language) {
null => $this->getCompileCommandOptions(), null => $this->getCompileCommandOptions(),
'c' => $this->getCCompileCommandOptions(), 'c' => $this->getCCompileCommandOptions(),
default => $this->getNativeCompileCommandOptions($language), 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); return $this->getNativeBuilder()->compileCommand($sourceFile, $objectFile, $options, $language);
} }
@ -1486,6 +1498,13 @@ CODE;
// embed 需要 main 函数,以及 cli 的内置函数定义 // embed 需要 main 函数,以及 cli 的内置函数定义
if ($this->isBuildModeEmbed()) { 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'; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_main.cc';
} }

Loading…
Cancel
Save