parent
28413e0924
commit
9f26e8d06d
7 changed files with 167 additions and 63 deletions
@ -0,0 +1,73 @@ |
||||
<?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\ExecutableLocator; |
||||
|
||||
/** |
||||
* @internal |
||||
* @coversNothing |
||||
*/ |
||||
final class ExecutableLocatorTest extends TestCase |
||||
{ |
||||
private string|false $originalPath; |
||||
private string $directory; |
||||
|
||||
protected function setUp(): void |
||||
{ |
||||
$this->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; |
||||
} |
||||
} |
||||
@ -0,0 +1,77 @@ |
||||
<?php |
||||
/** |
||||
* This file is part of TypePHP(AOT). |
||||
* |
||||
* @link https://www.swoole.com/aot/ |
||||
* @contact service@swoole.com |
||||
*/ |
||||
|
||||
namespace TypePhp\Build; |
||||
|
||||
final class ExecutableLocator |
||||
{ |
||||
public static function resolve(string $program): ?string |
||||
{ |
||||
if ($program === '') { |
||||
return null; |
||||
} |
||||
|
||||
if (self::isPath($program)) { |
||||
return self::resolveCandidate($program); |
||||
} |
||||
|
||||
$path = getenv('PATH'); |
||||
if ($path === false || $path === '') { |
||||
return null; |
||||
} |
||||
|
||||
foreach (explode(PATH_SEPARATOR, $path) as $directory) { |
||||
if ($directory === '') { |
||||
continue; |
||||
} |
||||
foreach (self::executableExtensions($program) as $extension) { |
||||
$candidate = rtrim($directory, '/\\') . DIRECTORY_SEPARATOR . $program . $extension; |
||||
$resolved = self::resolveCandidate($candidate); |
||||
if ($resolved !== null) { |
||||
return $resolved; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
private static function resolveCandidate(string $path): ?string |
||||
{ |
||||
$resolved = realpath($path); |
||||
if ($resolved === false || !is_file($resolved)) { |
||||
return null; |
||||
} |
||||
|
||||
if (!is_executable($resolved)) { |
||||
return null; |
||||
} |
||||
|
||||
return $resolved; |
||||
} |
||||
|
||||
private static function isPath(string $program): bool |
||||
{ |
||||
return str_contains($program, '/') |
||||
|| str_contains($program, '\\') |
||||
|| preg_match('/^[A-Za-z]:/', $program) === 1; |
||||
} |
||||
|
||||
/** |
||||
* @return list<string> |
||||
*/ |
||||
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 !== '')); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue