TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
482 lines
16 KiB
482 lines
16 KiB
<?php
|
|
|
|
namespace TypePhp\Tests\Platform;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use TypePhp\Platform\Windows;
|
|
use TypePhp\Platform\Linux;
|
|
use TypePhp\Platform\Macos;
|
|
use TypePhp\Platform\Wasi;
|
|
|
|
class PlatformTest extends TestCase
|
|
{
|
|
public function testWasiTargetProperties(): void
|
|
{
|
|
$platform = new Wasi('wasm32-unknown-wasip2');
|
|
|
|
$this->assertSame('WASI SDK (wasm32-unknown-wasip2)', $platform->getName());
|
|
$this->assertSame('.o', $platform->getObjectExtension());
|
|
$this->assertSame('.wasm', $platform->getExecutableExtension());
|
|
$this->assertSame('.a', $platform->getSharedLibraryExtension());
|
|
$this->assertSame('LL', $platform->getIntegerLiteralSuffix());
|
|
$this->assertSame([], $platform->getBuildLibraryWarnings('', '', 'bin'));
|
|
}
|
|
|
|
/**
|
|
* 测试 Windows 平台基本功能
|
|
*/
|
|
public function testWindowsBasic(): void
|
|
{
|
|
$platform = new Windows();
|
|
|
|
$this->assertEquals('Windows', $platform->getName());
|
|
$this->assertEquals('.obj', $platform->getObjectExtension());
|
|
$this->assertEquals('.exe', $platform->getExecutableExtension());
|
|
$this->assertEquals('.dll', $platform->getSharedLibraryExtension());
|
|
$this->assertEquals('\\', $platform->getPathSeparator());
|
|
}
|
|
|
|
/**
|
|
* 测试 Windows 包含路径格式化
|
|
*/
|
|
public function testWindowsIncludeFlags(): void
|
|
{
|
|
$platform = new Windows();
|
|
|
|
$paths = ['C:\PHP\include', 'C:\PHP\SDK\include'];
|
|
$flags = $platform->getIncludeFlags($paths);
|
|
|
|
$this->assertStringContainsString('/I "C:\PHP\include"', $flags);
|
|
$this->assertStringContainsString('/I "C:\PHP\SDK\include"', $flags);
|
|
}
|
|
|
|
/**
|
|
* 测试 Windows 库路径格式化
|
|
*/
|
|
public function testWindowsLibraryPathFlags(): void
|
|
{
|
|
$platform = new Windows();
|
|
|
|
$paths = ['C:\PHP\lib', 'C:\PHP\SDK\lib'];
|
|
$flags = $platform->getLibraryPathFlags($paths);
|
|
|
|
$this->assertStringContainsString('/LIBPATH:"C:\PHP\lib"', $flags);
|
|
$this->assertStringContainsString('/LIBPATH:"C:\PHP\SDK\lib"', $flags);
|
|
}
|
|
|
|
/**
|
|
* 测试 Windows 库文件格式化
|
|
*/
|
|
public function testWindowsLibraryFlags(): void
|
|
{
|
|
$platform = new Windows();
|
|
|
|
$libs = ['php8embed.lib', 'php8ts.lib'];
|
|
$flags = $platform->getLibraryFlags($libs);
|
|
|
|
$this->assertStringContainsString('"php8embed.lib"', $flags);
|
|
$this->assertStringContainsString('"php8ts.lib"', $flags);
|
|
}
|
|
|
|
public function testWindowsMissingPhpxLibrariesAreFatalDiagnostics(): void
|
|
{
|
|
$root = sys_get_temp_dir() . '\\typephp-missing-phpx-' . bin2hex(random_bytes(6));
|
|
mkdir($root);
|
|
|
|
try {
|
|
$diagnostics = (new Windows())->getBuildLibraryWarnings($root, $root, 'bin');
|
|
$errors = array_column($diagnostics, 'error');
|
|
|
|
$this->assertContains(
|
|
'The PHPX import library was not found at: ' . $root . '\lib\phpx.lib',
|
|
$errors,
|
|
);
|
|
$runtimeError = 'The PHPX runtime library was not found at: ' . $root . '\build\phpx.dll';
|
|
$this->assertContains($runtimeError, $errors);
|
|
foreach ($diagnostics as $diagnostic) {
|
|
if (isset($diagnostic['error'])) {
|
|
$this->assertStringContainsString('Build PHPX first', $diagnostic['info']);
|
|
}
|
|
}
|
|
|
|
$nativeExecutableDiagnostics = (new Windows())->getBuildLibraryWarnings(
|
|
$root,
|
|
$root,
|
|
'bin',
|
|
checkPhpxRuntime: false,
|
|
);
|
|
$this->assertNotContains(
|
|
$runtimeError,
|
|
array_column($nativeExecutableDiagnostics, 'error'),
|
|
);
|
|
} finally {
|
|
rmdir($root);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 测试 Windows 路径规范化
|
|
*/
|
|
public function testWindowsNormalizePath(): void
|
|
{
|
|
$platform = new Windows();
|
|
|
|
$this->assertEquals('src\Backend', $platform->normalizePath('src/Backend'));
|
|
$this->assertEquals('C:\PHP\include', $platform->normalizePath('C:/PHP/include'));
|
|
}
|
|
|
|
/**
|
|
* 测试 Windows 路径组合
|
|
*/
|
|
public function testWindowsJoinPath(): void
|
|
{
|
|
$platform = new Windows();
|
|
|
|
$path = $platform->joinPath('src', 'Php', 'Backend');
|
|
$this->assertEquals('src\Php\Backend', $path);
|
|
}
|
|
|
|
public function testTargetExtensions(): void
|
|
{
|
|
$windows = new Windows();
|
|
$linux = new Linux();
|
|
$macos = new Macos();
|
|
|
|
$this->assertSame('.exe', $windows->getTargetExtension('bin'));
|
|
$this->assertSame('.dll', $windows->getTargetExtension('ext'));
|
|
$this->assertSame('.dll', $windows->getTargetExtension('lib'));
|
|
$this->assertSame('', $linux->getTargetExtension('bin'));
|
|
$this->assertSame('.so', $linux->getTargetExtension('ext'));
|
|
$this->assertSame('.so', $linux->getTargetExtension('lib'));
|
|
$this->assertSame('', $macos->getTargetExtension('bin'));
|
|
$this->assertSame('.so', $macos->getTargetExtension('ext'));
|
|
$this->assertSame('.dylib', $macos->getTargetExtension('lib'));
|
|
}
|
|
|
|
public function testPlatformPathPrefixRemoval(): void
|
|
{
|
|
$windows = new Windows();
|
|
$linux = new Linux();
|
|
|
|
$this->assertSame('src\app.php', $windows->removeCommonPrefix('C:\project', 'C:/project/src/app.php'));
|
|
$this->assertSame('src/app.php', $linux->removeCommonPrefix('/project', '/project/src/app.php'));
|
|
}
|
|
|
|
public function testPlatformPathPrefixRemovalUsesPathSegments(): void
|
|
{
|
|
$windows = new Windows();
|
|
$linux = new Linux();
|
|
|
|
$this->assertSame('project2\src\app.php', $windows->removeCommonPrefix('C:\project', 'C:/project2/src/app.php'));
|
|
$this->assertSame('project2/src/app.php', $linux->removeCommonPrefix('/tmp/project', '/tmp/project2/src/app.php'));
|
|
}
|
|
|
|
/**
|
|
* 测试 Windows 子系统选项
|
|
*/
|
|
public function testWindowsSubsystemOptions(): void
|
|
{
|
|
$platform = new Windows();
|
|
|
|
// 无控制台
|
|
$options = $platform->getSubsystemOptions(true);
|
|
$this->assertStringContainsString('/SUBSYSTEM:WINDOWS', $options);
|
|
$this->assertStringContainsString('/ENTRY:mainCRTStartup', $options);
|
|
|
|
// 有控制台
|
|
$options = $platform->getSubsystemOptions(false);
|
|
$this->assertEquals('', $options);
|
|
}
|
|
|
|
/**
|
|
* 测试 Windows CRT 配置
|
|
*/
|
|
public function testWindowsCrtConfig(): void
|
|
{
|
|
$platform = new Windows();
|
|
|
|
$config = $platform->getCrtConfig();
|
|
$this->assertEquals('/NODEFAULTLIB:LIBCMT', $config);
|
|
$this->assertSame('/DLL', $platform->getSharedLinkFlag());
|
|
}
|
|
|
|
/**
|
|
* 测试 Windows 调试选项
|
|
*/
|
|
public function testWindowsDebugOptions(): void
|
|
{
|
|
$platform = new Windows();
|
|
|
|
// 启用调试
|
|
$options = $platform->getDebugOptions(true);
|
|
$this->assertEquals('/DEBUG', $options);
|
|
|
|
// 禁用调试
|
|
$options = $platform->getDebugOptions(false);
|
|
$this->assertEquals('', $options);
|
|
}
|
|
|
|
/**
|
|
* 测试 Linux 平台基本功能
|
|
*/
|
|
public function testLinuxBasic(): void
|
|
{
|
|
$platform = new Linux();
|
|
|
|
$this->assertEquals('Linux', $platform->getName());
|
|
$this->assertEquals('.o', $platform->getObjectExtension());
|
|
$this->assertEquals('', $platform->getExecutableExtension());
|
|
$this->assertEquals('.so', $platform->getSharedLibraryExtension());
|
|
$this->assertEquals('/', $platform->getPathSeparator());
|
|
}
|
|
|
|
/**
|
|
* 测试 Linux 包含路径格式化
|
|
*/
|
|
public function testLinuxIncludeFlags(): void
|
|
{
|
|
$platform = new Linux();
|
|
|
|
$paths = ['/usr/include/php', '/usr/local/include'];
|
|
$flags = $platform->getIncludeFlags($paths);
|
|
|
|
$this->assertStringContainsString('-I' . escapeshellarg('/usr/include/php'), $flags);
|
|
$this->assertStringContainsString('-I' . escapeshellarg('/usr/local/include'), $flags);
|
|
}
|
|
|
|
/**
|
|
* 测试 Linux 库路径格式化
|
|
*/
|
|
public function testLinuxLibraryPathFlags(): void
|
|
{
|
|
$platform = new Linux();
|
|
|
|
$paths = ['/usr/lib', '/usr/local/lib'];
|
|
$flags = $platform->getLibraryPathFlags($paths);
|
|
|
|
$this->assertStringContainsString('-L' . escapeshellarg('/usr/lib'), $flags);
|
|
$this->assertStringContainsString('-L' . escapeshellarg('/usr/local/lib'), $flags);
|
|
}
|
|
|
|
/**
|
|
* 测试 Linux 库文件格式化
|
|
*/
|
|
public function testLinuxLibraryFlags(): void
|
|
{
|
|
$platform = new Linux();
|
|
|
|
$libs = ['/usr/lib/libphp.so', '/usr/lib/libphpx.a'];
|
|
$flags = $platform->getLibraryFlags($libs);
|
|
|
|
$this->assertStringContainsString('-lphp', $flags);
|
|
$this->assertStringContainsString('-lphpx', $flags);
|
|
}
|
|
|
|
/**
|
|
* 测试 Linux RPATH 选项
|
|
*/
|
|
public function testLinuxRpathOptions(): void
|
|
{
|
|
$platform = new Linux();
|
|
|
|
$paths = ['/usr/lib', '/usr/local/lib'];
|
|
$options = $platform->getRpathOptions($paths);
|
|
|
|
$this->assertStringContainsString('-Wl,-rpath,' . escapeshellarg('/usr/lib'), $options);
|
|
$this->assertStringContainsString('-Wl,-rpath,' . escapeshellarg('/usr/local/lib'), $options);
|
|
}
|
|
|
|
/**
|
|
* 测试 Linux PIC 标志
|
|
*/
|
|
public function testLinuxPicFlag(): void
|
|
{
|
|
$platform = new Linux();
|
|
|
|
$this->assertEquals('-fPIC', $platform->getPicFlag());
|
|
}
|
|
|
|
public function testIntegerLiteralSuffixes(): void
|
|
{
|
|
$this->assertSame('L', (new Linux())->getIntegerLiteralSuffix());
|
|
$this->assertSame('LL', (new Macos())->getIntegerLiteralSuffix());
|
|
$this->assertSame('LL', (new Windows())->getIntegerLiteralSuffix());
|
|
}
|
|
|
|
/**
|
|
* 测试 Linux 共享库链接标志
|
|
*/
|
|
public function testLinuxSharedLinkFlag(): void
|
|
{
|
|
$platform = new Linux();
|
|
|
|
$this->assertEquals('-shared', $platform->getSharedLinkFlag());
|
|
$this->assertSame('', $platform->getSubsystemOptions(true));
|
|
$this->assertSame('', $platform->getCrtConfig());
|
|
}
|
|
|
|
public function testLinuxPhpHomeSelectsMatchingVersionedPhpConfig(): void
|
|
{
|
|
if (PHP_OS_FAMILY === 'Windows') {
|
|
$this->markTestSkipped('Unix php-config lookup test');
|
|
}
|
|
|
|
$root = sys_get_temp_dir() . '/typephp-php-home-' . bin2hex(random_bytes(6));
|
|
$phpHome = $root . '/php';
|
|
$rightInclude = $phpHome . '/include/right';
|
|
$wrongInclude = $phpHome . '/include/wrong';
|
|
mkdir($phpHome . '/bin', 0755, true);
|
|
mkdir($rightInclude, 0755, true);
|
|
mkdir($wrongInclude, 0755, true);
|
|
|
|
$versionedConfig = $phpHome . '/bin/php-config' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
|
|
$wrongMinor = PHP_MINOR_VERSION === 4 ? 5 : 4;
|
|
$this->writePhpConfig($versionedConfig, PHP_VERSION, $phpHome, $rightInclude);
|
|
$this->writePhpConfig(
|
|
$phpHome . '/bin/php-config',
|
|
PHP_MAJOR_VERSION . '.' . $wrongMinor . '.0',
|
|
$phpHome,
|
|
$wrongInclude,
|
|
);
|
|
|
|
$previousPhpHome = getenv('PHP_HOME');
|
|
try {
|
|
putenv('PHP_HOME=' . $phpHome);
|
|
$platform = new Linux();
|
|
|
|
$this->assertSame($phpHome, $platform->getPhpDir());
|
|
$this->assertSame([$rightInclude], $platform->buildPhpIncludePaths($phpHome));
|
|
} finally {
|
|
$previousPhpHome === false
|
|
? putenv('PHP_HOME')
|
|
: putenv('PHP_HOME=' . $previousPhpHome);
|
|
unlink($versionedConfig);
|
|
unlink($phpHome . '/bin/php-config');
|
|
rmdir($rightInclude);
|
|
rmdir($wrongInclude);
|
|
rmdir($phpHome . '/include');
|
|
rmdir($phpHome . '/bin');
|
|
rmdir($phpHome);
|
|
rmdir($root);
|
|
}
|
|
}
|
|
|
|
public function testLinuxPhpHomeDoesNotFallBackToPathPhpConfig(): void
|
|
{
|
|
if (PHP_OS_FAMILY === 'Windows') {
|
|
$this->markTestSkipped('Unix php-config lookup test');
|
|
}
|
|
|
|
$root = sys_get_temp_dir() . '/typephp-php-home-missing-' . bin2hex(random_bytes(6));
|
|
$phpHome = $root . '/php';
|
|
mkdir($phpHome . '/bin', 0755, true);
|
|
$previousPhpHome = getenv('PHP_HOME');
|
|
|
|
try {
|
|
putenv('PHP_HOME=' . $phpHome);
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('PHP_HOME does not provide an executable bin/php-config');
|
|
(new Linux())->buildPhpIncludePaths($phpHome);
|
|
} finally {
|
|
$previousPhpHome === false
|
|
? putenv('PHP_HOME')
|
|
: putenv('PHP_HOME=' . $previousPhpHome);
|
|
rmdir($phpHome . '/bin');
|
|
rmdir($phpHome);
|
|
rmdir($root);
|
|
}
|
|
}
|
|
|
|
private function writePhpConfig(string $path, string $version, string $prefix, string $include): void
|
|
{
|
|
$script = sprintf(
|
|
"#!/bin/sh\ncase \"\$1\" in\n --version) printf '%%s\\n' %s ;;\n --prefix) printf '%%s\\n' %s ;;\n --includes) printf '%%s\\n' %s ;;\nesac\n",
|
|
escapeshellarg($version),
|
|
escapeshellarg($prefix),
|
|
escapeshellarg('-I' . $include),
|
|
);
|
|
file_put_contents($path, $script);
|
|
chmod($path, 0755);
|
|
}
|
|
|
|
/**
|
|
* 测试 macOS 平台基本功能
|
|
*/
|
|
public function testMacosBasic(): void
|
|
{
|
|
$platform = new Macos();
|
|
|
|
$this->assertEquals('macOS', $platform->getName());
|
|
$this->assertEquals('.o', $platform->getObjectExtension());
|
|
$this->assertEquals('', $platform->getExecutableExtension());
|
|
$this->assertEquals('.dylib', $platform->getSharedLibraryExtension());
|
|
$this->assertEquals('/', $platform->getPathSeparator());
|
|
}
|
|
|
|
/**
|
|
* 测试 macOS install_name 选项
|
|
*/
|
|
public function testMacosInstallName(): void
|
|
{
|
|
$platform = new Macos();
|
|
|
|
$option = $platform->getCurrentInstallNameOption('/usr/lib/libtest.dylib');
|
|
$this->assertStringContainsString('-install_name', $option);
|
|
$this->assertStringContainsString('/usr/lib/libtest.dylib', $option);
|
|
}
|
|
|
|
/**
|
|
* 测试 macOS 共享库链接标志
|
|
*/
|
|
public function testMacosSharedLinkFlag(): void
|
|
{
|
|
$platform = new Macos();
|
|
|
|
$this->assertEquals('-dynamiclib', $platform->getSharedLinkFlag());
|
|
}
|
|
|
|
public function testMacosHomebrewSearchPaths(): void
|
|
{
|
|
$platform = new Macos();
|
|
|
|
$includePaths = $platform->buildPhpIncludePaths('/path/that/does/not/exist');
|
|
$this->assertContains('/opt/homebrew/include', $includePaths);
|
|
$this->assertContains('/usr/local/include', $includePaths);
|
|
|
|
$libraryPaths = $platform->buildPhpLibPaths('/path/that/does/not/exist');
|
|
$this->assertContains('/opt/homebrew/lib', $libraryPaths);
|
|
$this->assertContains('/usr/local/lib', $libraryPaths);
|
|
|
|
$this->assertStringContainsString(
|
|
'-I' . escapeshellarg('/opt/homebrew/include'),
|
|
$platform->getIncludeFlags($includePaths),
|
|
);
|
|
$this->assertStringContainsString(
|
|
'-L' . escapeshellarg('/opt/homebrew/lib'),
|
|
$platform->getLibraryPathFlags($libraryPaths),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 测试空数组处理
|
|
*/
|
|
public function testEmptyArrays(): void
|
|
{
|
|
$windows = new Windows();
|
|
$linux = new Linux();
|
|
$macos = new Macos();
|
|
|
|
// 所有平台应该正确处理空数组
|
|
$this->assertEquals('', $windows->getIncludeFlags([]));
|
|
$this->assertEquals('', $windows->getLibraryPathFlags([]));
|
|
$this->assertEquals('', $windows->getLibraryFlags([]));
|
|
|
|
$this->assertEquals('', $linux->getIncludeFlags([]));
|
|
$this->assertEquals('', $linux->getLibraryPathFlags([]));
|
|
$this->assertEquals('', $linux->getLibraryFlags([]));
|
|
|
|
$this->assertEquals('', $macos->getIncludeFlags([]));
|
|
$this->assertEquals('', $macos->getLibraryPathFlags([]));
|
|
$this->assertEquals('', $macos->getLibraryFlags([]));
|
|
}
|
|
}
|
|
|