parent
8ddd04e1b1
commit
af2b03c111
62 changed files with 451 additions and 237 deletions
@ -1,6 +1,17 @@ |
||||
<?php |
||||
define("ROOT_PATH", dirname(__DIR__)); |
||||
define('DEBUG', true); |
||||
/** |
||||
* This file is part of TypePHP(AOT). |
||||
* |
||||
* @link https://www.swoole.com/aot/ |
||||
* @contact service@swoole.com |
||||
*/ |
||||
|
||||
require ROOT_PATH . '/vendor/autoload.php'; |
||||
require ROOT_PATH . '/src/functions.php'; |
||||
define('TYPEPHP_ROOT_PATH', dirname(__DIR__)); |
||||
define('TYPEPHP_DEBUG', true); |
||||
|
||||
// Composer bin proxies provide the consuming project's autoloader. A source |
||||
// checkout and a packaged compiler keep their own autoloader below TYPEPHP_ROOT_PATH. |
||||
$autoloadPath = $GLOBALS['_composer_autoload_path'] ?? TYPEPHP_ROOT_PATH . '/vendor/autoload.php'; |
||||
unset($GLOBALS['_composer_autoload_path']); |
||||
require $autoloadPath; |
||||
unset($autoloadPath); |
||||
|
||||
@ -1,9 +1,9 @@ |
||||
#!/usr/bin/env php |
||||
<?php |
||||
require __DIR__ . '/bootstrap.php'; |
||||
require __DIR__ . '/../src/polyfills.php'; |
||||
require __DIR__ . '/../src/gen_stub.php'; |
||||
require __DIR__ . '/../src/compiler.php'; |
||||
require TYPEPHP_ROOT_PATH . '/src/polyfills.php'; |
||||
require TYPEPHP_ROOT_PATH . '/src/gen_stub.php'; |
||||
require TYPEPHP_ROOT_PATH . '/src/compiler.php'; |
||||
|
||||
const TYPEPHP_PHP_SCRIPT_ENTRY = true; |
||||
main($argc, $argv); |
||||
|
||||
@ -0,0 +1,188 @@ |
||||
<?php |
||||
/** |
||||
* This file is part of TypePHP(AOT). |
||||
* |
||||
* @link https://www.swoole.com/aot/ |
||||
* @contact service@swoole.com |
||||
*/ |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
|
||||
/** |
||||
* @internal |
||||
* @coversNothing |
||||
*/ |
||||
final class BinaryEntrypointTest extends TestCase |
||||
{ |
||||
public function testBootstrapConstantsUseTypePhpPrefix(): void |
||||
{ |
||||
self::assertFalse(defined('ROOT_PATH')); |
||||
self::assertFalse(defined('DEBUG')); |
||||
self::assertSame(realpath(__DIR__ . '/..'), TYPEPHP_ROOT_PATH); |
||||
self::assertTrue(TYPEPHP_DEBUG); |
||||
} |
||||
|
||||
public function testStubGeneratorDoesNotPolluteTheGlobalSymbolTable(): void |
||||
{ |
||||
self::assertTrue(function_exists('TypePhp\\StubGenerator\\generateStubFile')); |
||||
self::assertTrue(class_exists('TypePhp\\StubGenerator\\ClassInfo')); |
||||
self::assertFalse(function_exists('generateStubFile')); |
||||
self::assertFalse(class_exists('ClassInfo')); |
||||
} |
||||
|
||||
public function testSourceCheckoutEntrypointUsesPackageAutoloader(): void |
||||
{ |
||||
$temporaryRoot = sys_get_temp_dir() . '/typephp-source-bin-' . bin2hex(random_bytes(6)); |
||||
self::assertTrue(mkdir($temporaryRoot, 0777, true)); |
||||
|
||||
try { |
||||
$result = $this->runCompiler(TYPEPHP_ROOT_PATH . '/bin/tpc.php', $temporaryRoot); |
||||
|
||||
self::assertSame(0, $result['status'], $result['stderr']); |
||||
self::assertStringContainsString('TypePHP Compiler (AOT)', $result['stdout']); |
||||
} finally { |
||||
$this->removeDirectory($temporaryRoot); |
||||
} |
||||
} |
||||
|
||||
public function testComposerVendorEntrypointUsesProjectAutoloader(): void |
||||
{ |
||||
$temporaryRoot = sys_get_temp_dir() . '/typephp-composer-bin-' . bin2hex(random_bytes(6)); |
||||
$packageRoot = $temporaryRoot . '/vendor/swoole/typephp'; |
||||
|
||||
try { |
||||
$proxy = $this->createComposerEntrypoint($temporaryRoot, $packageRoot); |
||||
self::assertDirectoryDoesNotExist($packageRoot . '/vendor'); |
||||
|
||||
$result = $this->runCompiler($proxy, $temporaryRoot); |
||||
|
||||
self::assertSame(0, $result['status'], $result['stderr']); |
||||
self::assertStringContainsString('TypePHP Compiler (AOT)', $result['stdout']); |
||||
} finally { |
||||
$this->removeDirectory($temporaryRoot); |
||||
} |
||||
} |
||||
|
||||
public function testComposerVendorEntrypointDryCompilesProject(): void |
||||
{ |
||||
if (!$this->hasPhpxLibrary()) { |
||||
self::markTestSkipped('A built PHPX library is required for the Zend PHP compiler integration test'); |
||||
} |
||||
|
||||
$temporaryRoot = sys_get_temp_dir() . '/typephp-composer-project-' . bin2hex(random_bytes(6)); |
||||
$packageRoot = $temporaryRoot . '/vendor/swoole/typephp'; |
||||
|
||||
try { |
||||
$proxy = $this->createComposerEntrypoint($temporaryRoot, $packageRoot); |
||||
self::assertNotFalse(file_put_contents( |
||||
$temporaryRoot . '/main.php', |
||||
"<?php\nfunction main(): void {}\n",
|
||||
)); |
||||
self::assertNotFalse(file_put_contents( |
||||
$temporaryRoot . '/project.yml', |
||||
"name: composer-entrypoint-test\nbuild-dir: build\nsources:\n - main.php\n", |
||||
)); |
||||
|
||||
$result = $this->runCompiler( |
||||
$proxy, |
||||
$temporaryRoot, |
||||
['project.yml', '--dry', '--no-color'], |
||||
); |
||||
|
||||
self::assertSame(0, $result['status'], $result['stderr']); |
||||
self::assertStringContainsString('Dry run completed:', $result['stdout']); |
||||
} finally { |
||||
$this->removeDirectory($temporaryRoot); |
||||
} |
||||
} |
||||
|
||||
/** @return array{status: int, stdout: string, stderr: string} */ |
||||
private function runCompiler( |
||||
string $entrypoint, |
||||
string $workingDirectory, |
||||
array $arguments = ['--version', '--no-color'], |
||||
): array { |
||||
$process = proc_open([PHP_BINARY, $entrypoint, ...$arguments], [ |
||||
0 => ['pipe', 'r'], |
||||
1 => ['pipe', 'w'], |
||||
2 => ['pipe', 'w'], |
||||
], $pipes, $workingDirectory, null, ['suppress_errors' => true]); |
||||
self::assertIsResource($process); |
||||
fclose($pipes[0]); |
||||
$stdout = stream_get_contents($pipes[1]); |
||||
fclose($pipes[1]); |
||||
$stderr = stream_get_contents($pipes[2]); |
||||
fclose($pipes[2]); |
||||
|
||||
return [ |
||||
'status' => proc_close($process), |
||||
'stdout' => (string) $stdout, |
||||
'stderr' => (string) $stderr, |
||||
]; |
||||
} |
||||
|
||||
private function createComposerEntrypoint(string $temporaryRoot, string $packageRoot): string |
||||
{ |
||||
$this->copyPackageEntrypoint($packageRoot); |
||||
$proxy = $temporaryRoot . '/vendor/bin/tpc.php'; |
||||
$proxySource = sprintf( |
||||
"<?php\n\$GLOBALS['_composer_autoload_path'] = %s;\nrequire %s;\n",
|
||||
var_export(TYPEPHP_ROOT_PATH . '/vendor/autoload.php', true), |
||||
var_export($packageRoot . '/bin/tpc.php', true), |
||||
); |
||||
self::assertNotFalse(file_put_contents($proxy, $proxySource)); |
||||
return $proxy; |
||||
} |
||||
|
||||
private function hasPhpxLibrary(): bool |
||||
{ |
||||
$phpxHome = getenv('PHPX_HOME'); |
||||
$roots = [TYPEPHP_ROOT_PATH . '/vendor/swoole/phpx']; |
||||
if (is_string($phpxHome) && $phpxHome !== '') { |
||||
array_unshift($roots, $phpxHome); |
||||
} |
||||
foreach ($roots as $root) { |
||||
foreach (['lib/libphpx.so', 'lib/libphpx.a', 'lib/phpx.lib', 'bin/phpx.dll'] as $library) { |
||||
if (is_file($root . '/' . $library)) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private function copyPackageEntrypoint(string $packageRoot): void |
||||
{ |
||||
$files = [ |
||||
'bin/bootstrap.php', |
||||
'bin/tpc.php', |
||||
'src/compiler.php', |
||||
'src/gen_stub.php', |
||||
'src/polyfills.php', |
||||
]; |
||||
foreach ($files as $file) { |
||||
$destination = $packageRoot . '/' . $file; |
||||
$directory = dirname($destination); |
||||
if (!is_dir($directory)) { |
||||
self::assertTrue(mkdir($directory, 0777, true)); |
||||
} |
||||
self::assertTrue(copy(TYPEPHP_ROOT_PATH . '/' . $file, $destination)); |
||||
} |
||||
self::assertTrue(mkdir(dirname($packageRoot, 2) . '/bin', 0777, true)); |
||||
} |
||||
|
||||
private function removeDirectory(string $directory): void |
||||
{ |
||||
if (!is_dir($directory)) { |
||||
return; |
||||
} |
||||
$items = new RecursiveIteratorIterator( |
||||
new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS), |
||||
RecursiveIteratorIterator::CHILD_FIRST, |
||||
); |
||||
foreach ($items as $item) { |
||||
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname()); |
||||
} |
||||
rmdir($directory); |
||||
} |
||||
} |
||||
@ -1,54 +0,0 @@ |
||||
<?php |
||||
/** |
||||
* This file is part of TypePHP. |
||||
* |
||||
* @link https://www.swoole.com/ |
||||
* @contact service@swoole.com |
||||
*/ |
||||
|
||||
use TypePhp\CompilerBase; |
||||
use TypePhp\Exception\Unsupported; |
||||
|
||||
function abort($v) |
||||
{ |
||||
/* |
||||
* @var $translator Translator |
||||
*/ |
||||
global $translator; |
||||
$lang = $translator->getLang(); |
||||
$msg = 'Error: Unsupported ' . $lang . ' Syntax,'; |
||||
$msg .= ' Line: ' . $translator->getLine($v) . ', Type: ' . $translator->getType($v) . PHP_EOL; |
||||
if ($translator->mode == 'cli') { |
||||
if (DEBUG) { |
||||
var_dump($v); |
||||
debug_print_backtrace(); |
||||
} |
||||
} else { |
||||
header('Content-Type: application/json'); |
||||
echo json_encode($v, JSON_PRETTY_PRINT); |
||||
} |
||||
throw new Unsupported($msg); |
||||
} |
||||
|
||||
function if_empty_debug($if_expr, $v) |
||||
{ |
||||
if (empty($if_expr)) { |
||||
abort($v); |
||||
} |
||||
} |
||||
|
||||
function if_not_empty_debug($if_expr, $v) |
||||
{ |
||||
if (!empty($if_expr)) { |
||||
abort($v); |
||||
} |
||||
} |
||||
|
||||
function debug() |
||||
{ |
||||
foreach (func_get_args() as $arg) { |
||||
var_dump($arg); |
||||
} |
||||
debug_print_backtrace(); |
||||
exit; |
||||
} |
||||
Loading…
Reference in new issue