Commit 2e5b2b5a authored by fastangel's avatar fastangel Committed by DevShop

Issue #2974291 by fastangel: Add Symfony Process component and a `provision_process()` method

parent 5f96acb6
This diff is collapsed.
......@@ -13,7 +13,9 @@
"issues": "https://www.drupal.org/project/issues/provision",
"docs": "http://docs.aegirproject.org/"
},
"require": {},
"require": {
"symfony/process": "^3.3"
},
"config": {
"sort-packages": true,
"platform": {
......
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c6615761b64ee98b0ecc471fd193dd04",
"packages": [
{
"name": "symfony/process",
"version": "v3.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2017-07-13T13:05:09+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": true,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"platform-overrides": {
"php": "7.0"
}
}
......@@ -104,31 +104,10 @@ function drush_provision_drupal_pre_provision_verify() {
// Composer Install command: Do not interact, do not show download progress.
// Customizable by setting drush option 'provision_composer_install_command'
$composer_command = drush_get_option('provision_composer_install_command', 'composer install --no-interaction --no-progress --no-dev');
drush_log(dt("Running command @command", array(
'@command' => $composer_command
)), 'ok');
$start = time();
// @TODO: Implement Symfony Process component for line-by-line output logging.
if (drush_shell_cd_and_exec($composer_directory, $composer_command)) {
$stop = time();
$output = implode("\n", drush_shell_exec_output());
$log_status = strpos($output, 'Warning:') === FALSE? 'success': 'warning';
drush_log($output, $log_status);
drush_log(dt(strpos($output, 'Warning:') . "Command ran successfully in @times: @command", array(
'@command' => $composer_command,
'@time' => $stop - $start,
)), $log_status);
}
else {
drush_log(implode("\n", drush_shell_exec_output()), 'error');
drush_set_error('DRUSH_COMPOSER_ERROR', dt('The composer command failed in @dir: @command', array(
'@dir' => $composer_directory,
'@command' => $composer_command,
)));
}
// Implement Symfony Process component for line-by-line output logging.
provision_process($composer_command, $composer_directory);
}
}
......
......@@ -432,6 +432,46 @@ function provision_backend_invoke($target, $command, $arguments = array(), $data
return drush_invoke_process($context, $command, $arguments, $data, array('method' => $mode, 'integrate' => TRUE, 'dispatch-using-alias' => TRUE));
}
/**
* Run a Symfony Process saving to drush logs.
*/
function provision_process($command, $cwd = null, $label = 'Process', $env = array(), $log_output = TRUE, $error_message = 'Process Failed') {
drush_log("[$label] $command", 'p_command');
// Merge in env vars, inheriting the CLI's
if (is_array($env)) {
$env = array_merge($_SERVER, $env);
}
else {
$env = $_SERVER;
}
// Make sure colors always come through
$env['TERM'] = 'xterm';
$process = new \Symfony\Component\Process\Process(escapeshellcmd($command), $cwd, $env);
$process->setTimeout(NULL);
if ($log_output) {
$exit_code = $process->run(function ($type, $buffer) {
drush_log($buffer, 'p_info');
});
}
else {
$exit_code = $process->run();
}
// check exit code
if ($exit_code === 0) {
drush_log('', 'p_ok');
return $process->getOutput();
}
else {
drush_log('', 'p_error');
drush_set_error('PROVISION_PROCESS_ERROR', dt($error_message));
return $process->getErrorOutput();
}
}
/**
* the aegir version of the backend
*
......
......@@ -377,11 +377,11 @@ class ClassLoader
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath.'\\';
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
$length = $this->prefixLengthsPsr4[$first][$search];
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
......
......@@ -6,4 +6,5 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
);
......@@ -6,6 +6,20 @@ namespace Composer\Autoload;
class ComposerStaticInit93ce438f8ce8708ef24439d2070dee06
{
public static $prefixLengthsPsr4 = array (
'S' =>
array (
'Symfony\\Component\\Process\\' => 26,
),
);
public static $prefixDirsPsr4 = array (
'Symfony\\Component\\Process\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/process',
),
);
public static $prefixesPsr0 = array (
'P' =>
array (
......@@ -19,6 +33,8 @@ class ComposerStaticInit93ce438f8ce8708ef24439d2070dee06
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit93ce438f8ce8708ef24439d2070dee06::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit93ce438f8ce8708ef24439d2070dee06::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit93ce438f8ce8708ef24439d2070dee06::$prefixesPsr0;
}, null, ClassLoader::class);
......
[]
[
{
"name": "symfony/process",
"version": "v3.3.6",
"version_normalized": "3.3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"time": "2017-07-13T13:05:09+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com"
}
]
vendor/
composer.lock
phpunit.xml
CHANGELOG
=========
3.3.0
-----
* added command line arrays in the `Process` class
* added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods
* deprecated the `ProcessUtils::escapeArgument()` method
* deprecated not inheriting environment variables
* deprecated configuring `proc_open()` options
* deprecated configuring enhanced Windows compatibility
* deprecated configuring enhanced sigchild compatibility
2.5.0
-----
* added support for PTY mode
* added the convenience method "mustRun"
* deprecation: Process::setStdin() is deprecated in favor of Process::setInput()
* deprecation: Process::getStdin() is deprecated in favor of Process::getInput()
* deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types
2.4.0
-----
* added the ability to define an idle timeout
2.3.0
-----
* added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
* added Process::signal()
* added Process::getPid()
* added support for a TTY mode
2.2.0
-----
* added ProcessBuilder::setArguments() to reset the arguments on a builder
* added a way to retrieve the standard and error output incrementally
* added Process:restart()
2.1.0
-----
* added support for non-blocking processes (start(), wait(), isRunning(), stop())
* enhanced Windows compatibility
* added Process::getExitCodeText() that returns a string representation for
the exit code returned by the process
* added ProcessBuilder
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* Marker Interface for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* InvalidArgumentException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* LogicException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception for failed processes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessFailedException extends RuntimeException
{
private $process;
public function __construct(Process $process)
{
if ($process->isSuccessful()) {
throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
}
$error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
$process->getCommandLine(),
$process->getExitCode(),
$process->getExitCodeText(),
$process->getWorkingDirectory()
);
if (!$process->isOutputDisabled()) {
$error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
$process->getOutput(),
$process->getErrorOutput()
);
}
parent::__construct($error);
$this->process = $process;
}
public function getProcess()
{
return $this->process;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception that is thrown when a process times out.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessTimedOutException extends RuntimeException
{
const TYPE_GENERAL = 1;
const TYPE_IDLE = 2;
private $process;
private $timeoutType;
public function __construct(Process $process, $timeoutType)
{
$this->process = $process;
$this->timeoutType = $timeoutType;
parent::__construct(sprintf(
'The process "%s" exceeded the timeout of %s seconds.',
$process->getCommandLine(),
$this->getExceededTimeout()
));
}
public function getProcess()
{
return $this->process;
}
public function isGeneralTimeout()
{
return $this->timeoutType === self::TYPE_GENERAL;
}
public function isIdleTimeout()
{
return $this->timeoutType === self::TYPE_IDLE;
}
public function getExceededTimeout()
{
switch ($this->timeoutType) {
case self::TYPE_GENERAL:
return $this->process->getTimeout();
case self::TYPE_IDLE:
return $this->process->getIdleTimeout();
default:
throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* RuntimeException for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
/**
* Generic executable finder.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ExecutableFinder
{
private $suffixes = array('.exe', '.bat', '.cmd', '.com');
/**
* Replaces default suffixes of executable.
*
* @param array $suffixes
*/
public function setSuffixes(array $suffixes)
{
$this->suffixes = $suffixes;
}
/**
* Adds new possible suffix to check for executable.
*
* @param string $suffix
*/
public function addSuffix($suffix)
{
$this->suffixes[] = $suffix;
}
/**
* Finds an executable by name.
*
* @param string $name The executable name (without the extension)
* @param string $default The default to return if no executable is found
* @param array $extraDirs Additional dirs to check into
*
* @return string The executable path or default value
*/
public function find($name, $default = null, array $extraDirs = array())
{
if (ini_get('open_basedir')) {
$searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir'));
$dirs = array();
foreach ($searchPath as $path) {
// Silencing against https://bugs.php.net/69240
if (@is_dir($path)) {
$dirs[] = $path;
} else {
if (basename($path) == $name && @is_executable($path)) {
return $path;
}
}
}
} else {
$dirs = array_merge(
explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
$extraDirs
);
}
$suffixes = array('');
if ('\\' === DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT');
$suffixes = array_merge($suffixes, $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes);
}
foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) {
if (@is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || is_executable($file))) {
return $file;
}
}
}
return $default;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* Provides a way to continuously write to the input of a Process until the InputStream is closed.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class InputStream implements \IteratorAggregate
{
private $onEmpty = null;
private $input = array();
private $open = true;
/**
* Sets a callback that is called when the write buffer becomes empty.
*/
public function onEmpty(callable $onEmpty = null)
{
$this->onEmpty = $onEmpty;
}
/**
* Appends an input to the write buffer.
*
* @param resource|scalar|\Traversable|null The input to append as stream resource, scalar or \Traversable
*/
public function write($input)
{
if (null === $input) {
return;
}
if ($this->isClosed()) {
throw new RuntimeException(sprintf('%s is closed', static::class));
}
$this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
}
/**
* Closes the write buffer.
*/
public function close()
{
$this->open = false;
}
/**
* Tells whether the write buffer is closed or not.
*/
public function isClosed()
{
return !$this->open;
}
public function getIterator()
{
$this->open = true;
while ($this->open || $this->input) {
if (!$this->input) {
yield '';
continue;
}
$current = array_shift($this->input);
if ($current instanceof \Iterator) {
foreach ($current as $cur) {
yield $cur;
}
} else {
yield $current;
}
if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {