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
韩天峰 10 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",
"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": {

14
composer.lock generated

@ -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",

@ -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());

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

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

@ -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';
}

Loading…
Cancel
Save