Allow custom binary path or locate binary

This commit is contained in:
Aaron Piotrowski 2017-12-05 18:21:39 -06:00
parent cc07650c3e
commit e3b1cfd0cf
No known key found for this signature in database
GPG Key ID: ADD1EF783EDE9EEB
5 changed files with 115 additions and 12 deletions

View File

@ -17,6 +17,9 @@ use function Amp\asyncCall;
use function Amp\call;
class Process implements Context {
/** @var string|null Cached path to located PHP binary. */
private static $binaryPath;
/** @var \Amp\Process\Process */
private $process;
@ -26,10 +29,11 @@ class Process implements Context {
/**
* @param string|array $script Path to PHP script or array with first element as path and following elements options
* to the PHP script (e.g.: ['bin/worker', '-eOptionValue', '-nOptionValue'].
* @param string $binary Path to PHP binary. Null will attempt to automatically locate the binary.
* @param string $cwd Working directory.
* @param mixed[] $env Array of environment variables.
*/
public function __construct($script, string $cwd = "", array $env = []) {
public function __construct($script, string $binary = null, string $cwd = "", array $env = []) {
$options = [
"html_errors" => "0",
"display_errors" => "0",
@ -42,19 +46,35 @@ class Process implements Context {
$script = \escapeshellarg($script);
}
$options = (\PHP_SAPI === "phpdbg" ? " -b -qrr " : " ") . $this->formatOptions($options);
$separator = \PHP_SAPI === "phpdbg" ? " -- " : " ";
$command = \escapeshellarg(\PHP_BINARY) . $options . $separator . $script;
$processOptions = [];
if (\strncasecmp(\PHP_OS, "WIN", 3) === 0) {
$processOptions = ["bypass_shell" => true];
if ($binary === null) {
$binary = \PHP_BINARY;
}
// Locate PHP executable when running under non-cli SAPI and no custom binary path was provided.
if ($binary === \PHP_BINARY && \PHP_SAPI !== "cli") {
$binary = self::$binaryPath ?? self::locateBinary();
} elseif (!\is_executable($binary)) {
throw new \Error(\sprintf("The PHP binary path '%s' was not found or is not executable", $binary));
}
$command = \escapeshellarg($binary) . " " . $this->formatOptions($options) . " " . $script;
$processOptions = \strncasecmp(\PHP_OS, "WIN", 3) === 0 ? ["bypass_shell" => true] : [];
$this->process = new BaseProcess($command, $cwd, $env, $processOptions);
}
private static function locateBinary(): string {
$executable = \strncasecmp(\PHP_OS, "WIN", 3) === 0 ? "php.exe" : "php";
foreach (\explode(\PATH_SEPARATOR, \getenv("PATH")) as $path) {
$path .= \DIRECTORY_SEPARATOR . $executable;
if (\is_executable($path)) {
return self::$binaryPath = $path;
}
}
throw new \Error("Could not locate PHP executable binary");
}
private function formatOptions(array $options) {
$result = [];

View File

@ -22,6 +22,9 @@ class DefaultWorkerFactory implements WorkerFactory {
return new WorkerThread;
}
return new WorkerProcess;
return new WorkerProcess(
\getenv("AMP_PHP_BINARY") ?:
\defined("AMP_PHP_BINARY") ? \AMP_PHP_BINARY : null
);
}
}

View File

@ -9,17 +9,18 @@ use Amp\Parallel\Context\Process;
*/
class WorkerProcess extends AbstractWorker {
/**
* @param string|null $binary Path to PHP binary. Null will attempt to automatically locate the binary.
* @param string $envClassName Name of class implementing \Amp\Parallel\Worker\Environment to instigate.
* Defaults to \Amp\Parallel\Worker\BasicEnvironment.
* @param mixed[] $env Array of environment variables to pass to the worker. Empty array inherits from the current
* PHP process. See the $env parameter of \Amp\Process\Process::__construct().
*/
public function __construct(string $envClassName = BasicEnvironment::class, array $env = []) {
public function __construct(string $binary = null, string $envClassName = BasicEnvironment::class, array $env = []) {
$dir = \dirname(__DIR__, 2) . '/bin';
$script = [
$dir . "/worker",
"-e" . $envClassName,
];
parent::__construct(new Process($script, $dir, $env));
parent::__construct(new Process($script, $binary, $dir, $env));
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Amp\Parallel\Test\Context;
use Amp\Parallel\Context\Process;
use Amp\PHPUnit\TestCase;
use Amp\Promise;
class ProcessTest extends TestCase {
public function testBasicProcess() {
$process = new Process([
dirname(__DIR__) . "/bin/process",
"-sTest"
]);
$process->start();
$this->assertSame("Test", Promise\wait($process->join()));
}
}

61
test/bin/process Normal file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env php
<?php
use Amp\Parallel\Sync;
// Doesn't exist in phpdbg...
if (function_exists("cli_set_process_title")) {
@cli_set_process_title("process-test");
}
// Redirect all output written using echo, print, printf, etc. to STDERR.
ob_start(function ($data) {
fwrite(STDERR, $data);
return '';
}, 1, PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_FLUSHABLE);
(function () {
$paths = [
dirname(__DIR__, 2) . "/vendor/autoload.php",
];
foreach ($paths as $path) {
if (file_exists($path)) {
$autoloadPath = $path;
break;
}
}
if (!isset($autoloadPath)) {
fwrite(STDERR, "Could not locate autoload.php");
exit(1);
}
require $autoloadPath;
})();
Amp\Loop::run(function () {
$channel = new Sync\ChannelledSocket(STDIN, STDOUT);
try {
$value = (function (): string {
$options = getopt("s:");
if (!isset($options["s"])) {
throw new Error("No string provided");
}
return $options["s"];
})();
$result = new Sync\ExitSuccess($value);
} catch (Throwable $exception) {
$result = new Sync\ExitFailure($exception);
}
try {
yield $channel->send($result);
} catch (Throwable $exception) {
exit(1); // Parent context died, simply exit.
}
});