From 9f26e8d06ddf97e6855979aa96f77109e7524b6c Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 22 Sep 2026 12:28:36 +0800 Subject: [PATCH] refactor(cli): remove legacy runtime path switches --- bin/bootstrap.php | 1 - phpunit/BinaryEntrypointTest.php | 1 - phpunit/src/Build/ExecutableLocatorTest.php | 73 +++++++++++++++++++ src/Backend/CompilerFactory.php | 47 ++----------- src/Build/ExecutableLocator.php | 77 +++++++++++++++++++++ src/CompilerBase.php | 6 +- src/compiler.php | 25 +++---- 7 files changed, 167 insertions(+), 63 deletions(-) create mode 100644 phpunit/src/Build/ExecutableLocatorTest.php create mode 100644 src/Build/ExecutableLocator.php diff --git a/bin/bootstrap.php b/bin/bootstrap.php index 8d897dda..b6ad6ff6 100644 --- a/bin/bootstrap.php +++ b/bin/bootstrap.php @@ -7,7 +7,6 @@ */ 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. diff --git a/phpunit/BinaryEntrypointTest.php b/phpunit/BinaryEntrypointTest.php index 2d2ade82..33f3bf81 100644 --- a/phpunit/BinaryEntrypointTest.php +++ b/phpunit/BinaryEntrypointTest.php @@ -19,7 +19,6 @@ final class BinaryEntrypointTest extends TestCase self::assertFalse(defined('ROOT_PATH')); self::assertFalse(defined('DEBUG')); self::assertSame(realpath(__DIR__ . '/..'), TYPEPHP_ROOT_PATH); - self::assertTrue(TYPEPHP_DEBUG); } public function testStubGeneratorDoesNotPolluteTheGlobalSymbolTable(): void diff --git a/phpunit/src/Build/ExecutableLocatorTest.php b/phpunit/src/Build/ExecutableLocatorTest.php new file mode 100644 index 00000000..70378308 --- /dev/null +++ b/phpunit/src/Build/ExecutableLocatorTest.php @@ -0,0 +1,73 @@ +originalPath = getenv('PATH'); + $this->directory = sys_get_temp_dir() . '/typephp-executable-' . bin2hex(random_bytes(6)); + mkdir($this->directory, 0777, true); + } + + protected function tearDown(): void + { + if ($this->originalPath === false) { + putenv('PATH'); + } else { + putenv('PATH=' . $this->originalPath); + } + + foreach (glob($this->directory . '/*') ?: [] as $file) { + unlink($file); + } + rmdir($this->directory); + } + + public function testResolvesDirectExecutablePath(): void + { + $executable = $this->createExecutable('tpc'); + + self::assertSame(realpath($executable), ExecutableLocator::resolve($executable)); + } + + public function testResolvesExecutableFromPath(): void + { + $executable = $this->createExecutable('tpc'); + putenv('PATH=' . $this->directory); + + self::assertSame(realpath($executable), ExecutableLocator::resolve('tpc')); + } + + public function testReturnsNullForUnknownExecutable(): void + { + putenv('PATH=' . $this->directory); + + self::assertNull(ExecutableLocator::resolve('missing-tpc')); + } + + private function createExecutable(string $name): string + { + $path = $this->directory . DIRECTORY_SEPARATOR . $name; + file_put_contents($path, '#!/bin/sh'); + chmod($path, 0755); + return $path; + } +} diff --git a/src/Backend/CompilerFactory.php b/src/Backend/CompilerFactory.php index 0c472ba3..09e5f41b 100644 --- a/src/Backend/CompilerFactory.php +++ b/src/Backend/CompilerFactory.php @@ -2,10 +2,11 @@ namespace TypePhp\Backend; -use TypePhp\Platform\PlatformBase; -use TypePhp\Platform\Windows; +use TypePhp\Build\ExecutableLocator; use TypePhp\Platform\Linux; use TypePhp\Platform\Macos; +use TypePhp\Platform\PlatformBase; +use TypePhp\Platform\Windows; /** * Compiler factory. @@ -101,37 +102,7 @@ class CompilerFactory return false; } - if (self::isPathLikeCommand($program)) { - return is_file($program) && is_executable($program); - } - - $path = getenv('PATH'); - if ($path === false || $path === '') { - return false; - } - - $extensions = ['']; - if (DIRECTORY_SEPARATOR === '\\') { - $pathext = getenv('PATHEXT') ?: '.COM;.EXE;.BAT;.CMD'; - $extensions = array_filter(array_map('strtolower', explode(';', $pathext))); - if (preg_match('/\.[A-Za-z0-9]+$/', $program)) { - array_unshift($extensions, ''); - } - } - - foreach (explode(PATH_SEPARATOR, $path) as $dir) { - if ($dir === '') { - continue; - } - foreach ($extensions as $extension) { - $candidate = rtrim($dir, DIRECTORY_SEPARATOR . '/\\') . DIRECTORY_SEPARATOR . $program . $extension; - if (is_file($candidate) && is_executable($candidate)) { - return true; - } - } - } - - return false; + return ExecutableLocator::resolve($program) !== null; } /** @@ -190,8 +161,7 @@ class CompilerFactory } } - $firstToken = strtok($command, " \t\r\n"); - return $firstToken === false ? '' : $firstToken; + return strtok($command, " \t\r\n"); } private static function normalizeCompilerName(string $compilerName): string @@ -206,11 +176,4 @@ class CompilerFactory return preg_replace('/\.exe$/', '', $name); } - - private static function isPathLikeCommand(string $program): bool - { - return str_contains($program, '/') - || str_contains($program, '\\') - || preg_match('/^[A-Za-z]:[\/\\\\]/', $program) === 1; - } } diff --git a/src/Build/ExecutableLocator.php b/src/Build/ExecutableLocator.php new file mode 100644 index 00000000..29b5042b --- /dev/null +++ b/src/Build/ExecutableLocator.php @@ -0,0 +1,77 @@ + + */ + private static function executableExtensions(string $program): array + { + if (DIRECTORY_SEPARATOR !== '\\' || pathinfo($program, PATHINFO_EXTENSION) !== '') { + return ['']; + } + + $pathExtensions = getenv('PATHEXT') ?: '.COM;.EXE;.BAT;.CMD'; + return array_values(array_filter(explode(';', $pathExtensions), static fn (string $extension): bool => $extension !== '')); + } +} diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 5b2a3552..2e4e77e9 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -762,10 +762,8 @@ class CompilerBase implements PropertyAccessContext $message = 'Error: Unsupported ' . $this->getLang() . ' Syntax,'; $message .= ' Line: ' . $this->getLine($node) . ', Type: ' . $this->getType($node) . PHP_EOL; if ($this->mode === 'cli') { - if (defined('TYPEPHP_DEBUG') && TYPEPHP_DEBUG) { - var_dump($node); - debug_print_backtrace(); - } + var_dump($node); + debug_print_backtrace(); } else { header('Content-Type: application/json'); echo json_encode($node, JSON_PRETTY_PRINT); diff --git a/src/compiler.php b/src/compiler.php index 22409c75..7a99cf3b 100644 --- a/src/compiler.php +++ b/src/compiler.php @@ -3,6 +3,7 @@ use TypePhp\Translator; use TypePhp\Build\WasiToolchain; use TypePhp\Build\WasiProjectConfig; use TypePhp\Build\PhpxLocator; +use TypePhp\Build\ExecutableLocator; use TypePhp\Build\NativeSourceProjectBuilder; use TypePhp\Build\NativeSourceProjectConfig; use TypePhp\Build\ProjectBuildRunner; @@ -15,18 +16,13 @@ function main(int $argc, array $argv): void // memory. The default CLI limit (commonly 128M) is too small for larger builds. ini_set('memory_limit', '-1'); - if (!defined('TYPEPHP_ROOT_PATH')) { - define('TYPEPHP_ROOT_PATH', getenv("TYPEPHP_HOME") ?: getcwd()); - } - if (!defined('TYPEPHP_DEBUG')) { - define('TYPEPHP_DEBUG', true); - } + $compilerExecutable = ExecutableLocator::resolve($argv[0]) ?? $argv[0]; if (!defined('TYPEPHP_COMPILER_EXECUTABLE')) { - $compilerExecutable = realpath($argv[0]); - define( - 'TYPEPHP_COMPILER_EXECUTABLE', - $compilerExecutable !== false ? $compilerExecutable : $argv[0], - ); + 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 @@ -63,7 +59,7 @@ function main(int $argc, array $argv): void } if (getenv('TYPEPHP_WASM_INTERNAL_COMPILE') !== '1' && shouldCompileWasm($argv)) { - compileWasmProgram($argv); + compileWasmProgram($argv, $compilerExecutable); return; } @@ -159,7 +155,7 @@ function compileNativeSourceProject(array $argv): void * The lower-level build scripts are implementation details and are not part of * the user-facing workflow. */ -function compileWasmProgram(array $argv): void +function compileWasmProgram(array $argv, string $compilerExecutable): void { $input = null; $buildDir = null; @@ -282,8 +278,7 @@ function compileWasmProgram(array $argv): void $environment['TYPEPHP_WASM_PACKAGE'] = $project->package; $environment['TYPEPHP_WASM_WORLD'] = $project->world; $environment['TYPEPHP_WASM_NANO'] = $nano ? '1' : '0'; - $compilerExecutable = realpath($argv[0]); - if ($compilerExecutable === false || !is_executable($compilerExecutable)) { + if (!is_file($compilerExecutable) || !is_executable($compilerExecutable)) { fwrite(STDERR, "Unable to resolve the current TypePHP compiler executable: {$argv[0]}\n"); exit(1); }