refactor(cli): encapsulate compiler runtime context

master
韩天峰 3 weeks ago
parent 9f26e8d06d
commit 159df7dae0
  1. 7
      bin/tpc.php
  2. 2
      phpunit/BinaryEntrypointTest.php
  3. 13
      phpunit/src/Build/CompilerGeneratorFingerprintTest.php
  4. 37
      phpunit/src/Build/CompilerRuntimeTest.php
  5. 24
      phpunit/src/Build/PhpxLocatorTest.php
  6. 8
      phpunit/src/CompilationStatisticsTest.php
  7. 44
      src/Build/CompilerRuntime.php
  8. 49
      src/Build/ComposerNativePackage.php
  9. 9
      src/Build/IncrementalCompilationTrait.php
  10. 12
      src/Build/NanoSourceComposer.php
  11. 10
      src/Build/NativeSourceProjectBuilder.php
  12. 10
      src/Build/PhpxLocator.php
  13. 4
      src/Build/SourcePipelineTrait.php
  14. 6
      src/CompilerBase.php
  15. 6
      src/CompilerTest.php
  16. 14
      src/Translator.php
  17. 40
      src/compiler.php

@ -5,5 +5,8 @@ require TYPEPHP_ROOT_PATH . '/src/polyfills.php';
require TYPEPHP_ROOT_PATH . '/src/gen_stub.php'; require TYPEPHP_ROOT_PATH . '/src/gen_stub.php';
require TYPEPHP_ROOT_PATH . '/src/compiler.php'; require TYPEPHP_ROOT_PATH . '/src/compiler.php';
const TYPEPHP_PHP_SCRIPT_ENTRY = true; runCompiler(
main($argc, $argv); $argc,
$argv,
\TypePhp\Build\CompilerRuntime::source(TYPEPHP_ROOT_PATH, $argv[0]),
);

@ -18,6 +18,8 @@ final class BinaryEntrypointTest extends TestCase
{ {
self::assertFalse(defined('ROOT_PATH')); self::assertFalse(defined('ROOT_PATH'));
self::assertFalse(defined('DEBUG')); self::assertFalse(defined('DEBUG'));
self::assertFalse(defined('TYPEPHP_PHP_SCRIPT_ENTRY'));
self::assertFalse(defined('TYPEPHP_COMPILER_EXECUTABLE'));
self::assertSame(realpath(__DIR__ . '/..'), TYPEPHP_ROOT_PATH); self::assertSame(realpath(__DIR__ . '/..'), TYPEPHP_ROOT_PATH);
} }

@ -7,6 +7,7 @@ use PHPUnit\Framework\Attributes\RunInSeparateProcess;
use ReflectionClass; use ReflectionClass;
use TypePhp\CompilerBase; use TypePhp\CompilerBase;
use TypePhp\CompilerTest; use TypePhp\CompilerTest;
use TypePhp\Build\CompilerRuntime;
use TypePhp\Entity\ClassDef; use TypePhp\Entity\ClassDef;
use TypePhp\Entity\FunctionDef; use TypePhp\Entity\FunctionDef;
use TypePhp\Type; use TypePhp\Type;
@ -17,7 +18,10 @@ final class CompilerGeneratorFingerprintTest extends TestCase
public function testEmbeddedSnapshotIsUsedOnlyByNativeEntry(): void public function testEmbeddedSnapshotIsUsedOnlyByNativeEntry(): void
{ {
define('TYPEPHP_COMPILER_BUILD_FINGERPRINT', str_repeat('a', 64)); define('TYPEPHP_COMPILER_BUILD_FINGERPRINT', str_repeat('a', 64));
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH); $compiler = CompilerTest::create(
TYPEPHP_ROOT_PATH,
CompilerRuntime::nativeAt(TYPEPHP_ROOT_PATH, PHP_BINARY),
);
$method = (new ReflectionClass(\TypePhp\Translator::class))->getMethod('getIncrementalGeneratorFingerprint'); $method = (new ReflectionClass(\TypePhp\Translator::class))->getMethod('getIncrementalGeneratorFingerprint');
$native = $method->invoke($compiler); $native = $method->invoke($compiler);
self::assertMatchesRegularExpression('/^[a-f0-9]{64}$/D', $native); self::assertMatchesRegularExpression('/^[a-f0-9]{64}$/D', $native);
@ -27,8 +31,11 @@ final class CompilerGeneratorFingerprintTest extends TestCase
} }
self::assertFalse($reflection->getProperty('opcodeBuildChecked')->getValue($compiler)); self::assertFalse($reflection->getProperty('opcodeBuildChecked')->getValue($compiler));
self::assertSame($native, $method->invoke($compiler)); self::assertSame($native, $method->invoke($compiler));
define('TYPEPHP_PHP_SCRIPT_ENTRY', true); $sourceCompiler = CompilerTest::create(
self::assertNotSame($native, $method->invoke($compiler)); TYPEPHP_ROOT_PATH,
CompilerRuntime::source(TYPEPHP_ROOT_PATH, PHP_BINARY),
);
self::assertNotSame($native, $method->invoke($sourceCompiler));
} }
public function testCompilerSnapshotIsStableAndChangesWithSourceOrOptions(): void public function testCompilerSnapshotIsStableAndChangesWithSourceOrOptions(): void

@ -0,0 +1,37 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
namespace TypePhpTest\Build;
use PHPUnit\Framework\TestCase;
use TypePhp\Build\CompilerRuntime;
/**
* @internal
* @coversNothing
*/
final class CompilerRuntimeTest extends TestCase
{
public function testSourceRuntimeKeepsInstallationRootSeparateFromEntrypoint(): void
{
$runtime = CompilerRuntime::source(dirname(__DIR__, 3), PHP_BINARY);
self::assertTrue($runtime->sourceEntry);
self::assertSame(realpath(dirname(__DIR__, 3)), $runtime->installationRoot);
self::assertSame(realpath(PHP_BINARY), $runtime->executable);
}
public function testNativeRuntimeUsesExecutableDirectory(): void
{
$runtime = CompilerRuntime::native(PHP_BINARY);
self::assertFalse($runtime->sourceEntry);
self::assertSame(realpath(PHP_BINARY), $runtime->executable);
self::assertSame(realpath(dirname(PHP_BINARY)), $runtime->installationRoot);
}
}

@ -24,7 +24,7 @@ final class PhpxLocatorTest extends TestCase
} else { } else {
putenv('PHPX_HOME=' . $this->originalPhpxHome); putenv('PHPX_HOME=' . $this->originalPhpxHome);
} }
rmdir($this->phpxHome); $this->removeDirectory($this->phpxHome);
} }
public function testPhpxHomeHasPriorityAndReturnsAnAbsolutePath(): void public function testPhpxHomeHasPriorityAndReturnsAnAbsolutePath(): void
@ -44,4 +44,26 @@ final class PhpxLocatorTest extends TestCase
PhpxLocator::resolve($projectRoot), PhpxLocator::resolve($projectRoot),
); );
} }
public function testCompilerLocalInstallationPrecedesComposerMetadata(): void
{
putenv('PHPX_HOME=' . $this->phpxHome . '/missing');
$projectRoot = $this->phpxHome . '/compiler';
$localPhpx = $projectRoot . '/vendor/swoole/phpx';
mkdir($localPhpx, 0777, true);
self::assertSame(realpath($localPhpx), PhpxLocator::resolve($projectRoot));
}
private function removeDirectory(string $directory): void
{
$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);
}
} }

@ -78,7 +78,7 @@ final class CompilationStatisticsTest extends BaseTest
self::assertTrue($selection->completeFallback); self::assertTrue($selection->completeFallback);
self::assertSame(['json', 'standard'], $selection->extensions); self::assertSame(['json', 'standard'], $selection->extensions);
$composition = (new NanoSourceComposer())->compose( $composition = (new NanoSourceComposer(TYPEPHP_ROOT_PATH))->compose(
sys_get_temp_dir() . '/typephp-nano-dynamic-fallback-test', sys_get_temp_dir() . '/typephp-nano-dynamic-fallback-test',
'dynamic_fallback_test', 'dynamic_fallback_test',
false, false,
@ -122,7 +122,7 @@ final class CompilationStatisticsTest extends BaseTest
self::assertSame(['date'], $selection->extensions); self::assertSame(['date'], $selection->extensions);
self::assertSame([], $selection->features); self::assertSame([], $selection->features);
$composition = (new NanoSourceComposer())->compose( $composition = (new NanoSourceComposer(TYPEPHP_ROOT_PATH))->compose(
sys_get_temp_dir() . '/typephp-nano-date-component-test', sys_get_temp_dir() . '/typephp-nano-date-component-test',
'date_component_test', 'date_component_test',
false, false,
@ -156,7 +156,7 @@ final class CompilationStatisticsTest extends BaseTest
$statistics->record(CompilationStatistics::DIRECT_FUNCTIONS, 'array_keys'); $statistics->record(CompilationStatistics::DIRECT_FUNCTIONS, 'array_keys');
$statistics->finish(); $statistics->finish();
$composition = (new NanoSourceComposer())->compose( $composition = (new NanoSourceComposer(TYPEPHP_ROOT_PATH))->compose(
sys_get_temp_dir() . '/typephp-nano-component-test', sys_get_temp_dir() . '/typephp-nano-component-test',
'component_test', 'component_test',
false, false,
@ -186,7 +186,7 @@ final class CompilationStatisticsTest extends BaseTest
$statistics->record(CompilationStatistics::DIRECT_FUNCTIONS, 'json_encode'); $statistics->record(CompilationStatistics::DIRECT_FUNCTIONS, 'json_encode');
$statistics->finish(); $statistics->finish();
$composition = (new NanoSourceComposer())->compose( $composition = (new NanoSourceComposer(TYPEPHP_ROOT_PATH))->compose(
sys_get_temp_dir() . '/typephp-nano-shared-standard-test', sys_get_temp_dir() . '/typephp-nano-shared-standard-test',
'shared_standard_test', 'shared_standard_test',
false, false,

@ -0,0 +1,44 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
namespace TypePhp\Build;
final readonly class CompilerRuntime
{
private function __construct(
public string $installationRoot,
public string $executable,
public bool $sourceEntry,
) {
}
public static function source(string $installationRoot, string $argument): self
{
return new self(
self::directory($installationRoot),
ExecutableLocator::resolve($argument) ?? $argument,
true,
);
}
public static function native(string $argument): self
{
$executable = ExecutableLocator::resolve($argument) ?? $argument;
return self::nativeAt(dirname($executable), $executable);
}
public static function nativeAt(string $installationRoot, string $executable): self
{
return new self(self::directory($installationRoot), $executable, false);
}
private static function directory(string $path): string
{
return realpath($path) ?: $path;
}
}

@ -29,7 +29,7 @@ final readonly class ComposerNativePackage
} }
/** @return list<self> */ /** @return list<self> */
public static function discover(): array public static function discover(?string $compilerRoot = null): array
{ {
$packages = []; $packages = [];
foreach (InstalledVersions::getInstalledPackages() as $package) { foreach (InstalledVersions::getInstalledPackages() as $package) {
@ -43,7 +43,7 @@ final readonly class ComposerNativePackage
|| !is_array($manifest['extra']['typephp-native'] ?? null)) { || !is_array($manifest['extra']['typephp-native'] ?? null)) {
continue; continue;
} }
$packages[] = self::load($package); $packages[] = self::load($package, $compilerRoot);
} }
usort( usort(
@ -57,19 +57,20 @@ final readonly class ComposerNativePackage
return $packages; return $packages;
} }
public static function load(string $package): self public static function load(string $package, ?string $compilerRoot = null): self
{ {
if ($package === 'swoole/php-ext-standard') { if ($package === 'swoole/php-ext-standard') {
throw new RuntimeException( throw new RuntimeException(
'The standard extension is built into swoole/php-nano; remove obsolete package `swoole/php-ext-standard`' 'The standard extension is built into swoole/php-nano; remove obsolete package `swoole/php-ext-standard`'
); );
} }
$installPath = class_exists(InstalledVersions::class) && InstalledVersions::isInstalled($package) $root = self::resolveLocalPackage($package, $compilerRoot);
? InstalledVersions::getInstallPath($package) if ($root === null) {
: null; $installPath = class_exists(InstalledVersions::class) && InstalledVersions::isInstalled($package)
$root = is_string($installPath) ? realpath($installPath) : false; ? InstalledVersions::getInstallPath($package)
if ($root === false) { : null;
$root = self::resolveSiblingPackage($package); $resolvedInstallPath = is_string($installPath) ? realpath($installPath) : false;
$root = $resolvedInstallPath !== false ? $resolvedInstallPath : null;
} }
if ($root === null) { if ($root === null) {
throw new RuntimeException( throw new RuntimeException(
@ -249,21 +250,29 @@ final readonly class ComposerNativePackage
* The same layout is used by the monorepo checkout, where a package may not * The same layout is used by the monorepo checkout, where a package may not
* yet be present in the checkout's generated InstalledVersions metadata. * yet be present in the checkout's generated InstalledVersions metadata.
*/ */
private static function resolveSiblingPackage(string $package): ?string private static function resolveLocalPackage(string $package, ?string $compilerRoot): ?string
{ {
if (!defined('TYPEPHP_ROOT_PATH') || !str_starts_with($package, 'swoole/')) { if ($compilerRoot === null || !str_starts_with($package, 'swoole/')) {
return null; return null;
} }
$candidate = realpath(
dirname(TYPEPHP_ROOT_PATH) . DIRECTORY_SEPARATOR . substr($package, strlen('swoole/')), $packageDirectory = substr($package, strlen('swoole/'));
); $candidates = [
if ($candidate === false || !is_dir($candidate)) { $compilerRoot . '/vendor/swoole/' . $packageDirectory,
return null; $compilerRoot . '/' . $packageDirectory,
dirname($compilerRoot) . '/' . $packageDirectory,
];
foreach ($candidates as $path) {
$candidate = realpath($path);
if ($candidate === false || !is_dir($candidate)) {
continue;
}
$manifest = json_decode((string) @file_get_contents($candidate . '/composer.json'), true);
if (is_array($manifest) && ($manifest['name'] ?? null) === $package) {
return $candidate;
}
} }
$manifest = json_decode((string) @file_get_contents($candidate . '/composer.json'), true); return null;
return is_array($manifest) && ($manifest['name'] ?? null) === $package
? $candidate
: null;
} }
/** @return list<string> */ /** @return list<string> */

@ -371,7 +371,7 @@ trait IncrementalCompilationTrait
// Reading and hashing a 20+ MB executable on every tiny build costs more // Reading and hashing a 20+ MB executable on every tiny build costs more
// than processing the consumer source. Keep the executable fallback for // than processing the consumer source. Keep the executable fallback for
// binaries built before this metadata was introduced. // binaries built before this metadata was introduced.
if (!defined('TYPEPHP_PHP_SCRIPT_ENTRY') if (!$this->compilerRuntime->sourceEntry
&& defined('TYPEPHP_COMPILER_BUILD_FINGERPRINT')) { && defined('TYPEPHP_COMPILER_BUILD_FINGERPRINT')) {
$snapshot = constant('TYPEPHP_COMPILER_BUILD_FINGERPRINT'); $snapshot = constant('TYPEPHP_COMPILER_BUILD_FINGERPRINT');
if (is_string($snapshot) && preg_match('/^[a-f0-9]{64}$/D', $snapshot) === 1) { if (is_string($snapshot) && preg_match('/^[a-f0-9]{64}$/D', $snapshot) === 1) {
@ -386,10 +386,9 @@ trait IncrementalCompilationTrait
// Zend bridge. Fingerprint the executable snapshot with one native hash // Zend bridge. Fingerprint the executable snapshot with one native hash
// operation instead. The interpreted development entry keeps the source // operation instead. The interpreted development entry keeps the source
// walk below so edits invalidate generated-code caches immediately. // walk below so edits invalidate generated-code caches immediately.
if (!defined('TYPEPHP_PHP_SCRIPT_ENTRY') if (!$this->compilerRuntime->sourceEntry) {
&& defined('TYPEPHP_COMPILER_EXECUTABLE')) { $executable = $this->compilerRuntime->executable;
$executable = constant('TYPEPHP_COMPILER_EXECUTABLE'); if (is_file($executable)) {
if (is_string($executable) && is_file($executable)) {
hash_update($context, str_replace('\\', '/', $executable) . "\0"); hash_update($context, str_replace('\\', '/', $executable) . "\0");
if (!hash_update_file($context, $executable)) { if (!hash_update_file($context, $executable)) {
throw new \RuntimeException( throw new \RuntimeException(

@ -8,6 +8,10 @@ use TypePhp\Analysis\CompilationStatistics;
/** Resolves the Composer-provided runtime sources used by a Nano build. */ /** Resolves the Composer-provided runtime sources used by a Nano build. */
final class NanoSourceComposer final class NanoSourceComposer
{ {
public function __construct(private readonly string $compilerRoot)
{
}
/** /**
* @return array{ * @return array{
* packages: list<ComposerNativePackage>, * packages: list<ComposerNativePackage>,
@ -25,8 +29,8 @@ final class NanoSourceComposer
?CompilationStatistics $statistics = null, ?CompilationStatistics $statistics = null,
): array ): array
{ {
$runtime = ComposerNativePackage::load('swoole/php-nano'); $runtime = ComposerNativePackage::load('swoole/php-nano', $this->compilerRoot);
$phpx = ComposerNativePackage::load('swoole/phpx'); $phpx = ComposerNativePackage::load('swoole/phpx', $this->compilerRoot);
if ($runtime->abi !== $phpx->abi) { if ($runtime->abi !== $phpx->abi) {
throw new RuntimeException( throw new RuntimeException(
"Native ABI mismatch: swoole/php-nano={$runtime->abi}, swoole/phpx={$phpx->abi}" "Native ABI mismatch: swoole/php-nano={$runtime->abi}, swoole/phpx={$phpx->abi}"
@ -37,7 +41,7 @@ final class NanoSourceComposer
$runtime->name => $runtime, $runtime->name => $runtime,
$phpx->name => $phpx, $phpx->name => $phpx,
]; ];
foreach (ComposerNativePackage::discover() as $package) { foreach (ComposerNativePackage::discover($this->compilerRoot) as $package) {
$packagesByName[$package->name] = $package; $packagesByName[$package->name] = $package;
} }
$packages = array_values($packagesByName); $packages = array_values($packagesByName);
@ -147,7 +151,7 @@ final class NanoSourceComposer
"Nano component `{$component->name}` requires unknown component `{$requirement}`" "Nano component `{$component->name}` requires unknown component `{$requirement}`"
); );
} }
foreach ($componentsByName[$requirement] ?? [] as [$package, $required]) { foreach ($componentsByName[$requirement] as [$package, $required]) {
if (!isset($active[$package->name][$required->name])) { if (!isset($active[$package->name][$required->name])) {
$active[$package->name][$required->name] = true; $active[$package->name][$required->name] = true;
$pending[] = $required; $pending[] = $required;

@ -16,8 +16,10 @@ final class NativeSourceProjectBuilder
private bool $progressLineActive = false; private bool $progressLineActive = false;
private int $progressLineWidth = 0; private int $progressLineWidth = 0;
public function __construct(private readonly bool $lineProgress = false) public function __construct(
{ private readonly CompilerRuntime $compilerRuntime,
private readonly bool $lineProgress = false,
) {
} }
/** @return array{output: string, sourceCount: int, compiledCount: int} */ /** @return array{output: string, sourceCount: int, compiledCount: int} */
@ -45,7 +47,7 @@ final class NativeSourceProjectBuilder
'Generating C++ from ' . count($project->phpSources) . ' TypePHP source file(s)' 'Generating C++ from ' . count($project->phpSources) . ' TypePHP source file(s)'
); );
$generatedDir = $project->buildDir . DIRECTORY_SEPARATOR . 'generated'; $generatedDir = $project->buildDir . DIRECTORY_SEPARATOR . 'generated';
$translator = Translator::getInstance(); $translator = Translator::getInstance($this->compilerRuntime);
$phpFiles = $translator->prepareNanoSources( $phpFiles = $translator->prepareNanoSources(
$project->phpSources, $project->phpSources,
$this->projectSymbolName($project), $this->projectSymbolName($project),
@ -56,7 +58,7 @@ final class NativeSourceProjectBuilder
$generatedIncludeDir = $generatedDir . DIRECTORY_SEPARATOR . 'include'; $generatedIncludeDir = $generatedDir . DIRECTORY_SEPARATOR . 'include';
} }
$composition = (new NanoSourceComposer())->compose( $composition = (new NanoSourceComposer($this->compilerRuntime->installationRoot))->compose(
$project->buildDir, $project->buildDir,
$this->projectSymbolName($project), $this->projectSymbolName($project),
$project->phpSources !== [], $project->phpSources !== [],

@ -17,6 +17,11 @@ final class PhpxLocator
} }
} }
$resolved = self::existingDirectory(rtrim($rootPath, '/\\') . '/vendor/swoole/phpx');
if ($resolved !== null) {
return $resolved;
}
if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled('swoole/phpx')) { if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled('swoole/phpx')) {
$installPath = InstalledVersions::getInstallPath('swoole/phpx'); $installPath = InstalledVersions::getInstallPath('swoole/phpx');
if (is_string($installPath)) { if (is_string($installPath)) {
@ -27,11 +32,6 @@ final class PhpxLocator
} }
} }
$resolved = self::existingDirectory(rtrim($rootPath, '/\\') . '/vendor/swoole/phpx');
if ($resolved !== null) {
return $resolved;
}
throw new RuntimeException( throw new RuntimeException(
"phpx directory not found. Set PHPX_HOME or install swoole/phpx with Composer.", "phpx directory not found. Set PHPX_HOME or install swoole/phpx with Composer.",
); );

@ -589,7 +589,7 @@ trait SourcePipelineTrait
// file processing/compilation. The compiled tpc executable has libphpx // file processing/compilation. The compiled tpc executable has libphpx
// loaded by the dynamic linker before entering main(), so checking here // loaded by the dynamic linker before entering main(), so checking here
// is neither needed nor possible. // is neither needed nor possible.
if (defined('TYPEPHP_PHP_SCRIPT_ENTRY') && !($platform instanceof Wasi)) { if ($this->compilerRuntime->sourceEntry && !($platform instanceof Wasi)) {
$this->validatePhpxLibrary(); $this->validatePhpxLibrary();
} }
} }
@ -607,7 +607,7 @@ trait SourcePipelineTrait
$this->getPhpDir(), $this->getPhpDir(),
$this->getPhpxDir(), $this->getPhpxDir(),
$this->buildMode, $this->buildMode,
defined('TYPEPHP_PHP_SCRIPT_ENTRY'), $this->compilerRuntime->sourceEntry,
) as $message) { ) as $message) {
if (!empty($message['error'])) { if (!empty($message['error'])) {
$detail = $message['error']; $detail = $message['error'];

@ -10,6 +10,7 @@ namespace TypePhp;
use TypePhp\Analysis\CompilationStatistics; use TypePhp\Analysis\CompilationStatistics;
use TypePhp\Build\AstCache; use TypePhp\Build\AstCache;
use TypePhp\Build\CompilerRuntime;
use TypePhp\Build\PhpxLocator; use TypePhp\Build\PhpxLocator;
use TypePhp\Build\StableIdRegistry; use TypePhp\Build\StableIdRegistry;
@ -630,6 +631,7 @@ class CompilerBase implements PropertyAccessContext
protected bool $decimalTypes = false; protected bool $decimalTypes = false;
protected bool $bigintTypes = false; protected bool $bigintTypes = false;
protected string $rootPath; protected string $rootPath;
protected CompilerRuntime $compilerRuntime;
protected string $buildDir; protected string $buildDir;
protected string $targetName = 'app'; protected string $targetName = 'app';
protected string $outputDir = ''; // Output directory specified by the -o option protected string $outputDir = ''; // Output directory specified by the -o option
@ -684,7 +686,7 @@ class CompilerBase implements PropertyAccessContext
/** @var array<string, true> Fully resolved class names referenced by source code. */ /** @var array<string, true> Fully resolved class names referenced by source code. */
protected array $referencedClasses = []; protected array $referencedClasses = [];
public function __construct(string $rootPath) public function __construct(string $rootPath, ?CompilerRuntime $compilerRuntime = null)
{ {
$this->osType = PHP_OS_FAMILY; $this->osType = PHP_OS_FAMILY;
if (version_compare(PHP_VERSION, '8.4.0', '<')) { if (version_compare(PHP_VERSION, '8.4.0', '<')) {
@ -694,6 +696,8 @@ class CompilerBase implements PropertyAccessContext
$this->error('PHP 8.6.0 or later is not supported'); $this->error('PHP 8.6.0 or later is not supported');
} }
$this->rootPath = $rootPath; $this->rootPath = $rootPath;
$this->compilerRuntime = $compilerRuntime
?? CompilerRuntime::nativeAt($rootPath, PHP_BINARY);
$this->compilationStatistics = new CompilationStatistics(); $this->compilationStatistics = new CompilationStatistics();
$this->symbols = new SymbolRepository(); $this->symbols = new SymbolRepository();
$this->setPhpVersion(self::DEFAULT_PHP_VERSION); $this->setPhpVersion(self::DEFAULT_PHP_VERSION);

@ -8,15 +8,17 @@
namespace TypePhp; namespace TypePhp;
use TypePhp\Build\CompilerRuntime;
/** /**
* @internal * @internal
* @coversNothing * @coversNothing
*/ */
class CompilerTest extends Translator class CompilerTest extends Translator
{ {
public static function create(string $rootPath = ''): CompilerTest public static function create(string $rootPath = '', ?CompilerRuntime $runtime = null): CompilerTest
{ {
$instance = new self($rootPath); $instance = new self($rootPath, $runtime);
$instance->forTest = true; $instance->forTest = true;
return $instance; return $instance;
} }

@ -17,6 +17,7 @@ use TypePhp\Analysis\NativeObjectStackPromotionAnalyzer;
use TypePhp\Analysis\SsaBuilder; use TypePhp\Analysis\SsaBuilder;
use TypePhp\Backend\CompilerFactory; use TypePhp\Backend\CompilerFactory;
use TypePhp\Build\CompileOptions; use TypePhp\Build\CompileOptions;
use TypePhp\Build\CompilerRuntime;
use TypePhp\Build\FileScanner; use TypePhp\Build\FileScanner;
use TypePhp\Build\IncrementalCompilationTrait; use TypePhp\Build\IncrementalCompilationTrait;
use TypePhp\Build\NativeCommandOptionsTrait; use TypePhp\Build\NativeCommandOptionsTrait;
@ -153,9 +154,9 @@ class Translator extends Preprocessor
return $func->method && str_ends_with($func->name, self::NAMESPACE_SEPARATOR . '__construct'); return $func->method && str_ends_with($func->name, self::NAMESPACE_SEPARATOR . '__construct');
} }
public function __construct(string $rootPath) public function __construct(string $rootPath, ?CompilerRuntime $compilerRuntime = null)
{ {
parent::__construct($rootPath); parent::__construct($rootPath, $compilerRuntime);
$this->climate->arguments->add(Constants::COMPILER_OPTIONS); $this->climate->arguments->add(Constants::COMPILER_OPTIONS);
$this->preprocessArgvAdvanced(); $this->preprocessArgvAdvanced();
$this->climate->arguments->parse(); $this->climate->arguments->parse();
@ -199,10 +200,13 @@ class Translator extends Preprocessor
self::$instance = $this; self::$instance = $this;
} }
public static function getInstance(): Translator public static function getInstance(?CompilerRuntime $runtime = null): Translator
{ {
if (self::$instance === null) { if (self::$instance === null) {
self::$instance = new self(TYPEPHP_ROOT_PATH); if ($runtime === null) {
throw new \LogicException('The compiler runtime has not been initialized');
}
self::$instance = new self($runtime->installationRoot, $runtime);
} }
return self::$instance; return self::$instance;
} }
@ -2376,7 +2380,7 @@ CODE;
/** @param list<string> $generatedSources @return list<string> */ /** @param list<string> $generatedSources @return list<string> */
private function composeNanoRuntimeSources(array $generatedSources): array private function composeNanoRuntimeSources(array $generatedSources): array
{ {
$composition = (new NanoSourceComposer())->compose( $composition = (new NanoSourceComposer($this->compilerRuntime->installationRoot))->compose(
$this->getBuildDir(), $this->getBuildDir(),
$this->targetName, $this->targetName,
true, true,

@ -3,7 +3,7 @@ use TypePhp\Translator;
use TypePhp\Build\WasiToolchain; use TypePhp\Build\WasiToolchain;
use TypePhp\Build\WasiProjectConfig; use TypePhp\Build\WasiProjectConfig;
use TypePhp\Build\PhpxLocator; use TypePhp\Build\PhpxLocator;
use TypePhp\Build\ExecutableLocator; use TypePhp\Build\CompilerRuntime;
use TypePhp\Build\NativeSourceProjectBuilder; use TypePhp\Build\NativeSourceProjectBuilder;
use TypePhp\Build\NativeSourceProjectConfig; use TypePhp\Build\NativeSourceProjectConfig;
use TypePhp\Build\ProjectBuildRunner; use TypePhp\Build\ProjectBuildRunner;
@ -11,23 +11,19 @@ use TypePhp\PythonTools\Command as PythonToolsCommand;
use TypePhp\Cli\CompletionCommand; use TypePhp\Cli\CompletionCommand;
function main(int $argc, array $argv): void function main(int $argc, array $argv): void
{
runCompiler($argc, $argv, CompilerRuntime::native($argv[0]));
}
function runCompiler(int $argc, array $argv, CompilerRuntime $runtime): void
{ {
// Compiling a complete project keeps the parsed AST and generated sources in // Compiling a complete project keeps the parsed AST and generated sources in
// memory. The default CLI limit (commonly 128M) is too small for larger builds. // memory. The default CLI limit (commonly 128M) is too small for larger builds.
ini_set('memory_limit', '-1'); ini_set('memory_limit', '-1');
$compilerExecutable = ExecutableLocator::resolve($argv[0]) ?? $argv[0];
if (!defined('TYPEPHP_COMPILER_EXECUTABLE')) {
define('TYPEPHP_COMPILER_EXECUTABLE', $compilerExecutable);
}
if (!defined('TYPEPHP_ROOT_PATH')) {
$compilerRoot = realpath(dirname($compilerExecutable));
define('TYPEPHP_ROOT_PATH', $compilerRoot !== false ? $compilerRoot : dirname($compilerExecutable));
}
// The PHP entrypoint already loaded Composer's project autoloader in // The PHP entrypoint already loaded Composer's project autoloader in
// bin/bootstrap.php. The native binary loads its embedded copy here. // bin/bootstrap.php. The native binary loads its embedded copy here.
if (!defined('TYPEPHP_PHP_SCRIPT_ENTRY')) { if (!$runtime->sourceEntry) {
require_once dirname(__DIR__) . '/vendor/autoload.php'; require_once dirname(__DIR__) . '/vendor/autoload.php';
} }
@ -54,12 +50,12 @@ function main(int $argc, array $argv): void
exit(1); exit(1);
} }
if ($nativeSourceProject) { if ($nativeSourceProject) {
compileNativeSourceProject($argv); compileNativeSourceProject($argv, $runtime);
return; return;
} }
if (getenv('TYPEPHP_WASM_INTERNAL_COMPILE') !== '1' && shouldCompileWasm($argv)) { if (getenv('TYPEPHP_WASM_INTERNAL_COMPILE') !== '1' && shouldCompileWasm($argv)) {
compileWasmProgram($argv, $compilerExecutable); compileWasmProgram($argv, $runtime);
return; return;
} }
@ -69,7 +65,7 @@ function main(int $argc, array $argv): void
return; return;
} }
(new ProjectBuildRunner(Translator::getInstance()))->run($argv); (new ProjectBuildRunner(Translator::getInstance($runtime)))->run($argv);
} }
function shouldCompileNativeSourceProject(array $argv): bool function shouldCompileNativeSourceProject(array $argv): bool
@ -87,7 +83,7 @@ function shouldCompileNativeSourceProject(array $argv): bool
return false; return false;
} }
function compileNativeSourceProject(array $argv): void function compileNativeSourceProject(array $argv, CompilerRuntime $runtime): void
{ {
$input = null; $input = null;
$buildDir = null; $buildDir = null;
@ -133,7 +129,7 @@ function compileNativeSourceProject(array $argv): void
try { try {
$project = NativeSourceProjectConfig::load($input, $buildDir); $project = NativeSourceProjectConfig::load($input, $buildDir);
$builder = new NativeSourceProjectBuilder(); $builder = new NativeSourceProjectBuilder(compilerRuntime: $runtime);
$result = $builder->build($project); $result = $builder->build($project);
fwrite( fwrite(
STDOUT, STDOUT,
@ -155,7 +151,7 @@ function compileNativeSourceProject(array $argv): void
* The lower-level build scripts are implementation details and are not part of * The lower-level build scripts are implementation details and are not part of
* the user-facing workflow. * the user-facing workflow.
*/ */
function compileWasmProgram(array $argv, string $compilerExecutable): void function compileWasmProgram(array $argv, CompilerRuntime $runtime): void
{ {
$input = null; $input = null;
$buildDir = null; $buildDir = null;
@ -222,7 +218,7 @@ function compileWasmProgram(array $argv, string $compilerExecutable): void
$input, $input,
$buildDir, $buildDir,
$workingDirectory, $workingDirectory,
TYPEPHP_ROOT_PATH . DIRECTORY_SEPARATOR . 'build', $runtime->installationRoot . DIRECTORY_SEPARATOR . 'build',
$profile, $profile,
); );
} catch (RuntimeException $exception) { } catch (RuntimeException $exception) {
@ -230,7 +226,7 @@ function compileWasmProgram(array $argv, string $compilerExecutable): void
exit(1); exit(1);
} }
$builder = dirname(__DIR__) . '/wasm/' $builder = $runtime->installationRoot . '/wasm/'
. ($nano ? 'build-nano-program.sh' : 'build-program.sh'); . ($nano ? 'build-nano-program.sh' : 'build-program.sh');
if (!is_executable($builder)) { if (!is_executable($builder)) {
fwrite(STDERR, "TypePHP WASI builder is not executable: {$builder}\n"); fwrite(STDERR, "TypePHP WASI builder is not executable: {$builder}\n");
@ -278,7 +274,7 @@ function compileWasmProgram(array $argv, string $compilerExecutable): void
$environment['TYPEPHP_WASM_PACKAGE'] = $project->package; $environment['TYPEPHP_WASM_PACKAGE'] = $project->package;
$environment['TYPEPHP_WASM_WORLD'] = $project->world; $environment['TYPEPHP_WASM_WORLD'] = $project->world;
$environment['TYPEPHP_WASM_NANO'] = $nano ? '1' : '0'; $environment['TYPEPHP_WASM_NANO'] = $nano ? '1' : '0';
if (!is_file($compilerExecutable) || !is_executable($compilerExecutable)) { if (!is_file($runtime->executable) || !is_executable($runtime->executable)) {
fwrite(STDERR, "Unable to resolve the current TypePHP compiler executable: {$argv[0]}\n"); fwrite(STDERR, "Unable to resolve the current TypePHP compiler executable: {$argv[0]}\n");
exit(1); exit(1);
} }
@ -287,7 +283,7 @@ function compileWasmProgram(array $argv, string $compilerExecutable): void
} }
try { try {
$phpxDir = PhpxLocator::resolve(TYPEPHP_ROOT_PATH); $phpxDir = PhpxLocator::resolve($runtime->installationRoot);
} catch (RuntimeException $exception) { } catch (RuntimeException $exception) {
fwrite(STDERR, "Unable to locate PHPX: {$exception->getMessage()}\n"); fwrite(STDERR, "Unable to locate PHPX: {$exception->getMessage()}\n");
exit(1); exit(1);
@ -295,7 +291,7 @@ function compileWasmProgram(array $argv, string $compilerExecutable): void
if ($project->mode === 'library') { if ($project->mode === 'library') {
$environment['TYPEPHP_WIT_BINDGEN'] = $tools['wit-bindgen']; $environment['TYPEPHP_WIT_BINDGEN'] = $tools['wit-bindgen'];
} }
$command = [$builder, $project->input, $project->output ?? '-', $phpxDir, $compilerExecutable]; $command = [$builder, $project->input, $project->output ?? '-', $phpxDir, $runtime->executable];
$process = proc_open( $process = proc_open(
$command, $command,

Loading…
Cancel
Save