Merge remote-tracking branch 'johnstevenson/restart'
commit
994211523c
11
bin/composer
11
bin/composer
|
@ -7,10 +7,19 @@ if (PHP_SAPI !== 'cli') {
|
|||
|
||||
require __DIR__.'/../src/bootstrap.php';
|
||||
|
||||
use Composer\Factory;
|
||||
use Composer\XdebugHandler;
|
||||
use Composer\Console\Application;
|
||||
|
||||
error_reporting(-1);
|
||||
|
||||
// Create output for XdebugHandler and Application
|
||||
$output = Factory::createOutput();
|
||||
|
||||
$xdebug = new XdebugHandler($output);
|
||||
$xdebug->check();
|
||||
unset($xdebug);
|
||||
|
||||
if (function_exists('ini_set')) {
|
||||
@ini_set('display_errors', 1);
|
||||
|
||||
|
@ -41,4 +50,4 @@ if (function_exists('ini_set')) {
|
|||
|
||||
// run the command application
|
||||
$application = new Application();
|
||||
$application->run();
|
||||
$application->run(null, $output);
|
||||
|
|
|
@ -18,8 +18,6 @@ use Symfony\Component\Console\Application as BaseApplication;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Composer\Command;
|
||||
use Composer\Composer;
|
||||
use Composer\Factory;
|
||||
|
@ -96,9 +94,7 @@ class Application extends BaseApplication
|
|||
public function run(InputInterface $input = null, OutputInterface $output = null)
|
||||
{
|
||||
if (null === $output) {
|
||||
$styles = Factory::createAdditionalStyles();
|
||||
$formatter = new OutputFormatter(null, $styles);
|
||||
$output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter);
|
||||
$output = Factory::createOutput();
|
||||
}
|
||||
|
||||
return parent::run($input, $output);
|
||||
|
|
|
@ -28,7 +28,9 @@ use Composer\Util\Silencer;
|
|||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\EventDispatcher\Event;
|
||||
use Seld\JsonLint\DuplicateKeyException;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Autoload\AutoloadGenerator;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
|
@ -225,6 +227,19 @@ class Factory
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ConsoleOutput instance
|
||||
*
|
||||
* @return ConsoleOutput
|
||||
*/
|
||||
public static function createOutput()
|
||||
{
|
||||
$styles = self::createAdditionalStyles();
|
||||
$formatter = new OutputFormatter(null, $styles);
|
||||
|
||||
return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use Composer\Repository\RepositoryFactory::defaultRepos instead
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,306 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author John Stevenson <john-stevenson@blueyonder.co.uk>
|
||||
*/
|
||||
class XdebugHandler
|
||||
{
|
||||
const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG';
|
||||
|
||||
private $output;
|
||||
private $loaded;
|
||||
private $tmpIni;
|
||||
private $scanDir;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(OutputInterface $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
$this->loaded = extension_loaded('xdebug');
|
||||
$tmp = sys_get_temp_dir();
|
||||
$this->tmpIni = $tmp.'/composer-php.ini';
|
||||
$this->scanDir = $tmp.'/composer-php-empty';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if xdebug is loaded and composer needs to be restarted
|
||||
*
|
||||
* If so, then a tmp ini is created with the xdebug ini entry commented out.
|
||||
* If additional inis have been loaded, these are combined into the tmp ini
|
||||
* and PHP_INI_SCAN_DIR is set to an empty directory. An environment
|
||||
* variable is set so that the new process is created only once.
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
if (!$this->needsRestart()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->prepareRestart($command)) {
|
||||
$this->restart($command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the restarted command
|
||||
*
|
||||
* @param string $command
|
||||
*/
|
||||
protected function restart($command)
|
||||
{
|
||||
passthru($command, $exitCode);
|
||||
exit($exitCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a restart is needed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function needsRestart()
|
||||
{
|
||||
if (PHP_SAPI !== 'cli' || !defined('PHP_BINARY')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !getenv(self::ENV_ALLOW) && $this->loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if everything was written for the restart
|
||||
*
|
||||
* If any of the following fails (however unlikely) we must return false to
|
||||
* stop potential recursion:
|
||||
* - tmp ini file creation
|
||||
* - environment variable creation
|
||||
* - tmp scan dir creation
|
||||
*
|
||||
* @param null|string $command The command to run, set by method
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function prepareRestart(&$command)
|
||||
{
|
||||
$iniFiles = array();
|
||||
if ($loadedIni = php_ini_loaded_file()) {
|
||||
$iniFiles[] = $loadedIni;
|
||||
}
|
||||
|
||||
$additional = $this->getAdditionalInis($iniFiles, $replace);
|
||||
if ($this->writeTmpIni($iniFiles, $replace)) {
|
||||
$command = $this->getCommand($additional);
|
||||
}
|
||||
|
||||
return !empty($command) && putenv(self::ENV_ALLOW.'=1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the temporary ini file, or clears its name if no ini
|
||||
*
|
||||
* If there are no ini files, the tmp ini name is cleared so that
|
||||
* an empty value is passed with the -c option.
|
||||
*
|
||||
* @param array $iniFiles The php.ini locations
|
||||
* @param bool $replace Whether we need to modify the files
|
||||
*
|
||||
* @return bool False if the tmp ini could not be created
|
||||
*/
|
||||
private function writeTmpIni(array $iniFiles, $replace)
|
||||
{
|
||||
if (empty($iniFiles)) {
|
||||
// Unlikely, maybe xdebug was loaded through the -d option.
|
||||
$this->tmpIni = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
$content = $this->getIniHeader($iniFiles);
|
||||
foreach ($iniFiles as $file) {
|
||||
$content .= $this->getIniData($file, $replace);
|
||||
}
|
||||
|
||||
return @file_put_contents($this->tmpIni, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if additional inis were loaded
|
||||
*
|
||||
* @param array $iniFiles Populated by method
|
||||
* @param bool $replace Whether we need to modify the files
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function getAdditionalInis(array &$iniFiles, &$replace)
|
||||
{
|
||||
$replace = true;
|
||||
|
||||
if ($scanned = php_ini_scanned_files()) {
|
||||
$list = explode(',', $scanned);
|
||||
|
||||
foreach ($list as $file) {
|
||||
$file = trim($file);
|
||||
if (preg_match('/xdebug.ini$/', $file)) {
|
||||
// Skip the file, no need for regex replacing
|
||||
$replace = false;
|
||||
} else {
|
||||
$iniFiles[] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($scanned);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns formatted ini file data
|
||||
*
|
||||
* @param string $iniFile The location of the ini file
|
||||
* @param bool $replace Whether to regex replace content
|
||||
*
|
||||
* @return string The ini data
|
||||
*/
|
||||
private function getIniData($iniFile, $replace)
|
||||
{
|
||||
$data = str_repeat(PHP_EOL, 3);
|
||||
$data .= sprintf('; %s%s', $iniFile, PHP_EOL);
|
||||
$contents = file_get_contents($iniFile);
|
||||
|
||||
if ($replace) {
|
||||
// Comment out xdebug config
|
||||
$regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi';
|
||||
$data .= preg_replace($regex, ';$1', $contents);
|
||||
} else {
|
||||
$data .= $contents;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the required environment and returns the restart command line
|
||||
*
|
||||
* @param bool $additional Whether additional inis were loaded
|
||||
*
|
||||
* @return string|null The command line or null on failure
|
||||
*/
|
||||
private function getCommand($additional)
|
||||
{
|
||||
if ($additional) {
|
||||
if (!file_exists($this->scanDir) && !@mkdir($this->scanDir, 0777)) {
|
||||
return;
|
||||
}
|
||||
if (!putenv('PHP_INI_SCAN_DIR='.$this->scanDir)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$phpArgs = array(PHP_BINARY, '-c', $this->tmpIni);
|
||||
$params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv']));
|
||||
|
||||
return implode(' ', array_map(array($this, 'escape'), $params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the restart script arguments, adding --ansi if required
|
||||
*
|
||||
* If we are a terminal with color support we must ensure that the --ansi
|
||||
* option is set, because the restarted output is piped.
|
||||
*
|
||||
* @param array $args The argv array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getScriptArgs(array $args)
|
||||
{
|
||||
if (in_array('--no-ansi', $args) || in_array('--ansi', $args)) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
if ($this->output->isDecorated()) {
|
||||
$offset = count($args) > 1 ? 2: 1;
|
||||
array_splice($args, $offset, 0, '--ansi');
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string to be used as a shell argument.
|
||||
*
|
||||
* From https://github.com/johnstevenson/winbox-args
|
||||
* MIT Licensed (c) John Stevenson <john-stevenson@blueyonder.co.uk>
|
||||
*
|
||||
* @param string $arg The argument to be escaped
|
||||
* @param bool $meta Additionally escape cmd.exe meta characters
|
||||
*
|
||||
* @return string The escaped argument
|
||||
*/
|
||||
private function escape($arg, $meta = true)
|
||||
{
|
||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
return escapeshellarg($arg);
|
||||
}
|
||||
|
||||
$quote = strpbrk($arg, " \t") !== false || $arg === '';
|
||||
$arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes);
|
||||
|
||||
if ($meta) {
|
||||
$meta = $dquotes || preg_match('/%[^%]+%/', $arg);
|
||||
|
||||
if (!$meta && !$quote) {
|
||||
$quote = strpbrk($arg, '^&|<>()') !== false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($quote) {
|
||||
$arg = preg_replace('/(\\\\*)$/', '$1$1', $arg);
|
||||
$arg = '"'.$arg.'"';
|
||||
}
|
||||
|
||||
if ($meta) {
|
||||
$arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg);
|
||||
}
|
||||
|
||||
return $arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location of the original ini data used.
|
||||
*
|
||||
* @param array $iniFiles loaded php.ini locations
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getIniHeader($iniFiles)
|
||||
{
|
||||
$ini = implode(PHP_EOL.'; ', $iniFiles);
|
||||
$header = <<<EOD
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
; This file was automatically generated by Composer and can now be deleted.
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
; It is a modified copy of your php.ini configuration, found at:
|
||||
; {$ini}
|
||||
|
||||
; Make any changes there because this data will not be used again.
|
||||
EOD;
|
||||
|
||||
$header .= str_repeat(PHP_EOL, 50);
|
||||
return $header;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Test\Mock;
|
||||
|
||||
use Composer\Factory;
|
||||
use Composer\XdebugHandler;
|
||||
|
||||
class XdebugHandlerMock extends XdebugHandler
|
||||
{
|
||||
public $restarted;
|
||||
public $output;
|
||||
|
||||
public function __construct($loaded = null)
|
||||
{
|
||||
$this->output = Factory::createOutput();
|
||||
parent::__construct($this->output);
|
||||
|
||||
$loaded = null === $loaded ? true: $loaded;
|
||||
$class = new \ReflectionClass(get_parent_class($this));
|
||||
$prop = $class->getProperty('loaded');
|
||||
$prop->setAccessible(true);
|
||||
$prop->setValue($this, $loaded);
|
||||
|
||||
$this->restarted = false;
|
||||
}
|
||||
|
||||
protected function restart($command)
|
||||
{
|
||||
$this->restarted = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Test;
|
||||
|
||||
use Composer\Test\Mock\XdebugHandlerMock;
|
||||
|
||||
/**
|
||||
* @author John Stevenson <john-stevenson@blueyonder.co.uk>
|
||||
*/
|
||||
class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public static $envAllow;
|
||||
|
||||
public function testRestartWhenLoaded()
|
||||
{
|
||||
$loaded = true;
|
||||
|
||||
$xdebug = new XdebugHandlerMock($loaded);
|
||||
$xdebug->check();
|
||||
$this->assertTrue($xdebug->restarted || !defined('PHP_BINARY'));
|
||||
}
|
||||
|
||||
public function testNoRestartWhenNotLoaded()
|
||||
{
|
||||
$loaded = false;
|
||||
|
||||
$xdebug = new XdebugHandlerMock($loaded);
|
||||
$xdebug->check();
|
||||
$this->assertFalse($xdebug->restarted);
|
||||
}
|
||||
|
||||
public function testNoRestartWhenLoadedAndAllowed()
|
||||
{
|
||||
$loaded = true;
|
||||
putenv(XdebugHandlerMock::ENV_ALLOW.'=1');
|
||||
|
||||
$xdebug = new XdebugHandlerMock($loaded);
|
||||
$xdebug->check();
|
||||
$this->assertFalse($xdebug->restarted);
|
||||
}
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
self::$envAllow = (bool) getenv(XdebugHandlerMock::ENV_ALLOW);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
if (self::$envAllow) {
|
||||
putenv(XdebugHandlerMock::ENV_ALLOW.'=1');
|
||||
} else {
|
||||
putenv(XdebugHandlerMock::ENV_ALLOW.'=0');
|
||||
}
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
putenv(XdebugHandlerMock::ENV_ALLOW.'=0');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue