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