Merge pull request #5882 from johnstevenson/xdebug-handler-ini
Use random name for tmp ini and delete after usepull/5897/head
commit
f1eb787013
|
@ -19,6 +19,7 @@ use Composer\Downloader\TransportException;
|
|||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Util\ConfigValidator;
|
||||
use Composer\Util\IniHelper;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\StreamContextFactory;
|
||||
|
@ -436,14 +437,9 @@ EOT
|
|||
// code below taken from getcomposer.org/installer, any changes should be made there and replicated here
|
||||
$errors = array();
|
||||
$warnings = array();
|
||||
|
||||
$iniPath = php_ini_loaded_file();
|
||||
$displayIniMessage = false;
|
||||
if ($iniPath) {
|
||||
$iniMessage = PHP_EOL.PHP_EOL.'The php.ini used by your command-line PHP is: ' . $iniPath;
|
||||
} else {
|
||||
$iniMessage = PHP_EOL.PHP_EOL.'A php.ini file does not exist. You will have to create one.';
|
||||
}
|
||||
|
||||
$iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage();
|
||||
$iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.';
|
||||
|
||||
if (!function_exists('json_decode')) {
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Util\IniHelper;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
*/
|
||||
|
@ -58,21 +60,13 @@ class SolverProblemsException extends \RuntimeException
|
|||
|
||||
private function createExtensionHint()
|
||||
{
|
||||
$paths = array();
|
||||
$paths = IniHelper::getAll();
|
||||
|
||||
if (($iniPath = php_ini_loaded_file()) !== false) {
|
||||
$paths[] = $iniPath;
|
||||
}
|
||||
|
||||
if (!defined('HHVM_VERSION') && $additionalIniPaths = php_ini_scanned_files()) {
|
||||
$paths = array_merge($paths, array_map("trim", explode(",", $additionalIniPaths)));
|
||||
}
|
||||
|
||||
if (count($paths) === 0) {
|
||||
if (count($paths) === 1 && empty($paths[0])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$text = "\n To enable extensions, verify that they are enabled in those .ini files:\n - ";
|
||||
$text = "\n To enable extensions, verify that they are enabled in your .ini files:\n - ";
|
||||
$text .= implode("\n - ", $paths);
|
||||
$text .= "\n You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.";
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\Downloader;
|
|||
use Composer\Config;
|
||||
use Composer\Cache;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Util\IniHelper;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
@ -55,13 +56,7 @@ class RarDownloader extends ArchiveDownloader
|
|||
|
||||
if (!class_exists('RarArchive')) {
|
||||
// php.ini path is added to the error message to help users find the correct file
|
||||
$iniPath = php_ini_loaded_file();
|
||||
|
||||
if ($iniPath) {
|
||||
$iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath;
|
||||
} else {
|
||||
$iniMessage = 'A php.ini file does not exist. You will have to create one.';
|
||||
}
|
||||
$iniMessage = IniHelper::getMessage();
|
||||
|
||||
$error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n"
|
||||
. $iniMessage . "\n" . $processError;
|
||||
|
|
|
@ -16,6 +16,7 @@ use Composer\Config;
|
|||
use Composer\Cache;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\IniHelper;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
@ -49,14 +50,7 @@ class ZipDownloader extends ArchiveDownloader
|
|||
|
||||
if (!class_exists('ZipArchive') && !self::$hasSystemUnzip) {
|
||||
// php.ini path is added to the error message to help users find the correct file
|
||||
$iniPath = php_ini_loaded_file();
|
||||
|
||||
if ($iniPath) {
|
||||
$iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath;
|
||||
} else {
|
||||
$iniMessage = 'A php.ini file does not exist. You will have to create one.';
|
||||
}
|
||||
|
||||
$iniMessage = IniHelper::getMessage();
|
||||
$error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage;
|
||||
|
||||
throw new \RuntimeException($error);
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<?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\Util;
|
||||
|
||||
/**
|
||||
* Provides ini file location functions that work with and without a restart.
|
||||
* When the process has restarted it uses a tmp ini and stores the original
|
||||
* ini locations in an environment variable.
|
||||
*
|
||||
* @author John Stevenson <john-stevenson@blueyonder.co.uk>
|
||||
*/
|
||||
class IniHelper
|
||||
{
|
||||
const ENV_ORIGINAL = 'COMPOSER_ORIGINAL_INIS';
|
||||
|
||||
/**
|
||||
* Returns an array of php.ini locations with at least one entry
|
||||
*
|
||||
* The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
|
||||
* The loaded ini location is the first entry and may be empty.
|
||||
|
||||
* @return array
|
||||
*/
|
||||
public static function getAll()
|
||||
{
|
||||
if ($env = strval(getenv(self::ENV_ORIGINAL))) {
|
||||
return explode(PATH_SEPARATOR, $env);
|
||||
}
|
||||
|
||||
$paths = array(strval(php_ini_loaded_file()));
|
||||
|
||||
if ($scanned = php_ini_scanned_files()) {
|
||||
$paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the location of the loaded php.ini file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMessage()
|
||||
{
|
||||
$paths = self::getAll();
|
||||
|
||||
if (empty($paths[0])) {
|
||||
return 'A php.ini file does not exist. You will have to create one.';
|
||||
}
|
||||
|
||||
return 'The php.ini used by your command-line PHP is: '.$paths[0];
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Util\IniHelper;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
|
@ -25,6 +26,7 @@ class XdebugHandler
|
|||
private $output;
|
||||
private $loaded;
|
||||
private $envScanDir;
|
||||
private $tmpIni;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -41,7 +43,8 @@ class XdebugHandler
|
|||
*
|
||||
* 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 value.
|
||||
* and PHP_INI_SCAN_DIR is set to an empty value. Current ini locations are
|
||||
* are stored in COMPOSER_ORIGINAL_INIS, for use in the restarted process.
|
||||
*
|
||||
* This behaviour can be disabled by setting the COMPOSER_ALLOW_XDEBUG
|
||||
* environment variable to 1. This variable is used internally so that the
|
||||
|
@ -73,13 +76,18 @@ class XdebugHandler
|
|||
}
|
||||
|
||||
/**
|
||||
* Executes the restarted command
|
||||
* Executes the restarted command then deletes the tmp ini
|
||||
*
|
||||
* @param string $command
|
||||
*/
|
||||
protected function restart($command)
|
||||
{
|
||||
passthru($command, $exitCode);
|
||||
|
||||
if (!empty($this->tmpIni)) {
|
||||
@unlink($this->tmpIni);
|
||||
}
|
||||
|
||||
exit($exitCode);
|
||||
}
|
||||
|
||||
|
@ -113,82 +121,75 @@ class XdebugHandler
|
|||
*/
|
||||
private function prepareRestart(&$command)
|
||||
{
|
||||
$iniFiles = array();
|
||||
if ($loadedIni = php_ini_loaded_file()) {
|
||||
$iniFiles[] = $loadedIni;
|
||||
}
|
||||
$this->tmpIni = '';
|
||||
$iniPaths = IniHelper::getAll();
|
||||
$files = $this->getWorkingSet($iniPaths, $replace);
|
||||
|
||||
$additional = $this->getAdditionalInis($iniFiles, $replace);
|
||||
$tmpIni = $this->writeTmpIni($iniFiles, $replace);
|
||||
|
||||
if (false !== $tmpIni) {
|
||||
$command = $this->getCommand($tmpIni);
|
||||
return $this->setEnvironment($additional);
|
||||
if ($this->writeTmpIni($files, $replace)) {
|
||||
$command = $this->getCommand();
|
||||
return $this->setEnvironment($iniPaths);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the tmp ini file and returns its filename
|
||||
* Returns true if the tmp ini file was written
|
||||
*
|
||||
* The filename is passed as the -c option when the process restarts. On
|
||||
* non-Windows platforms the filename is prefixed with the username to
|
||||
* avoid any multi-user conflict. Windows always uses the user temp dir.
|
||||
* The filename is passed as the -c option when the process restarts.
|
||||
*
|
||||
* @param array $iniFiles The php.ini locations
|
||||
* @param bool $replace Whether we need to modify the files
|
||||
* @param bool $replace Whether the files need modifying
|
||||
*
|
||||
* @return bool|string False if the tmp ini could not be created
|
||||
* @return bool
|
||||
*/
|
||||
private function writeTmpIni(array $iniFiles, $replace)
|
||||
{
|
||||
if (empty($iniFiles)) {
|
||||
// Unlikely, maybe xdebug was loaded through a command line option.
|
||||
return '';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (function_exists('posix_getpwuid')) {
|
||||
$user = posix_getpwuid(posix_getuid());
|
||||
if (!$this->tmpIni = tempnam(sys_get_temp_dir(), '')) {
|
||||
return false;
|
||||
}
|
||||
$prefix = !empty($user) ? $user['name'].'-' : '';
|
||||
$tmpIni = sys_get_temp_dir().'/'.$prefix.'composer-php.ini';
|
||||
|
||||
$content = $this->getIniHeader($iniFiles);
|
||||
$content = '';
|
||||
foreach ($iniFiles as $file) {
|
||||
$content .= $this->getIniData($file, $replace);
|
||||
}
|
||||
|
||||
return @file_put_contents($tmpIni, $content) ? $tmpIni : false;
|
||||
return @file_put_contents($this->tmpIni, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if additional inis were loaded
|
||||
* Returns an array of ini files to use
|
||||
*
|
||||
* @param array $iniFiles Populated by method
|
||||
* @param bool $replace Whether we need to modify the files
|
||||
* @param array $iniPaths Locations used by the current prcoess
|
||||
* @param null|bool $replace Whether the files need modifying, set by method
|
||||
*
|
||||
* @return bool
|
||||
* @return array
|
||||
*/
|
||||
private function getAdditionalInis(array &$iniFiles, &$replace)
|
||||
private function getWorkingSet(array $iniPaths, &$replace)
|
||||
{
|
||||
$replace = true;
|
||||
$result = array();
|
||||
|
||||
if ($scanned = php_ini_scanned_files()) {
|
||||
$list = explode(',', $scanned);
|
||||
if (empty($iniPaths[0])) {
|
||||
// There is no loaded ini
|
||||
array_shift($iniPaths);
|
||||
}
|
||||
|
||||
foreach ($list as $file) {
|
||||
$file = trim($file);
|
||||
foreach ($iniPaths as $file) {
|
||||
if (preg_match('/xdebug.ini$/', $file)) {
|
||||
// Skip the file, no need for regex replacing
|
||||
$replace = false;
|
||||
} else {
|
||||
$iniFiles[] = $file;
|
||||
}
|
||||
$result[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($scanned);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,9 +202,8 @@ class XdebugHandler
|
|||
*/
|
||||
private function getIniData($iniFile, $replace)
|
||||
{
|
||||
$data = str_repeat(PHP_EOL, 3);
|
||||
$data .= sprintf('; %s%s', $iniFile, PHP_EOL);
|
||||
$contents = file_get_contents($iniFile);
|
||||
$data = PHP_EOL;
|
||||
|
||||
if ($replace) {
|
||||
// Comment out xdebug config
|
||||
|
@ -219,13 +219,11 @@ class XdebugHandler
|
|||
/**
|
||||
* Returns the restart command line
|
||||
*
|
||||
* @param string $tmpIni The temporary ini file location
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getCommand($tmpIni)
|
||||
private function getCommand()
|
||||
{
|
||||
$phpArgs = array(PHP_BINARY, '-c', $tmpIni);
|
||||
$phpArgs = array(PHP_BINARY, '-c', $this->tmpIni);
|
||||
$params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv']));
|
||||
|
||||
return implode(' ', array_map(array($this, 'escape'), $params));
|
||||
|
@ -234,12 +232,25 @@ class XdebugHandler
|
|||
/**
|
||||
* Returns true if the restart environment variables were set
|
||||
*
|
||||
* @param bool $additional Whether additional inis were loaded
|
||||
* @param array $iniPaths Locations used by the current prcoess
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function setEnvironment($additional)
|
||||
private function setEnvironment(array $iniPaths)
|
||||
{
|
||||
// Set scan dir to an empty value if additional ini files were used
|
||||
$additional = count($iniPaths) > 1;
|
||||
|
||||
if ($additional && !putenv('PHP_INI_SCAN_DIR=')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make original inis available to restarted process
|
||||
if (!putenv(IniHelper::ENV_ORIGINAL.'='.implode(PATH_SEPARATOR, $iniPaths))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Flag restarted process and save env scan dir state
|
||||
$args = array(self::RESTART_ID);
|
||||
|
||||
if (false !== $this->envScanDir) {
|
||||
|
@ -247,10 +258,6 @@ class XdebugHandler
|
|||
$args[] = $this->envScanDir;
|
||||
}
|
||||
|
||||
if ($additional && !putenv('PHP_INI_SCAN_DIR=')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return putenv(self::ENV_ALLOW.'='.implode('|', $args));
|
||||
}
|
||||
|
||||
|
@ -317,29 +324,4 @@ class XdebugHandler
|
|||
|
||||
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,68 @@
|
|||
<?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\Util;
|
||||
|
||||
use Composer\Util\IniHelper;
|
||||
|
||||
/**
|
||||
* @author John Stevenson <john-stevenson@blueyonder.co.uk>
|
||||
*/
|
||||
class IniHelperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public static $envOriginal;
|
||||
|
||||
public function testWithLoadedIni()
|
||||
{
|
||||
$paths = array(
|
||||
'loaded.ini',
|
||||
);
|
||||
|
||||
$this->setEnv($paths);
|
||||
$this->assertContains('loaded.ini', IniHelper::getMessage());
|
||||
$this->assertEquals($paths, IniHelper::getAll());
|
||||
}
|
||||
|
||||
public function testWithoutLoadedIni()
|
||||
{
|
||||
$paths = array(
|
||||
'',
|
||||
'one.ini',
|
||||
'two.ini',
|
||||
);
|
||||
|
||||
$this->setEnv($paths);
|
||||
$this->assertContains('does not exist', IniHelper::getMessage());
|
||||
$this->assertEquals($paths, IniHelper::getAll());
|
||||
}
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
// Save current state
|
||||
self::$envOriginal = getenv(IniHelper::ENV_ORIGINAL);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
// Restore original state
|
||||
if (false !== self::$envOriginal) {
|
||||
putenv(IniHelper::ENV_ORIGINAL.'='.self::$envOriginal);
|
||||
} else {
|
||||
putenv(IniHelper::ENV_ORIGINAL);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setEnv(array $paths)
|
||||
{
|
||||
putenv(IniHelper::ENV_ORIGINAL.'='.implode(PATH_SEPARATOR, $paths));
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Test;
|
||||
|
||||
use Composer\Test\Mock\XdebugHandlerMock;
|
||||
use Composer\Util\IniHelper;
|
||||
|
||||
/**
|
||||
* @author John Stevenson <john-stevenson@blueyonder.co.uk>
|
||||
|
@ -22,8 +23,7 @@ use Composer\Test\Mock\XdebugHandlerMock;
|
|||
*/
|
||||
class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public static $envAllow;
|
||||
public static $envIniScanDir;
|
||||
public static $env = array();
|
||||
|
||||
public function testRestartWhenLoaded()
|
||||
{
|
||||
|
@ -32,6 +32,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
|
|||
$xdebug = new XdebugHandlerMock($loaded);
|
||||
$xdebug->check();
|
||||
$this->assertTrue($xdebug->restarted);
|
||||
$this->assertInternalType('string', getenv(IniHelper::ENV_ORIGINAL));
|
||||
}
|
||||
|
||||
public function testNoRestartWhenNotLoaded()
|
||||
|
@ -41,6 +42,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
|
|||
$xdebug = new XdebugHandlerMock($loaded);
|
||||
$xdebug->check();
|
||||
$this->assertFalse($xdebug->restarted);
|
||||
$this->assertFalse(getenv(IniHelper::ENV_ORIGINAL));
|
||||
}
|
||||
|
||||
public function testNoRestartWhenLoadedAndAllowed()
|
||||
|
@ -106,28 +108,35 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
self::$envAllow = getenv(XdebugHandlerMock::ENV_ALLOW);
|
||||
self::$envIniScanDir = getenv('PHP_INI_SCAN_DIR');
|
||||
// Save current state
|
||||
$names = array(
|
||||
XdebugHandlerMock::ENV_ALLOW,
|
||||
'PHP_INI_SCAN_DIR',
|
||||
IniHelper::ENV_ORIGINAL,
|
||||
);
|
||||
|
||||
foreach ($names as $name) {
|
||||
self::$env[$name] = getenv($name);
|
||||
}
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
if (false !== self::$envAllow) {
|
||||
putenv(XdebugHandlerMock::ENV_ALLOW.'='.self::$envAllow);
|
||||
// Restore original state
|
||||
foreach (self::$env as $name => $value) {
|
||||
if (false !== $value) {
|
||||
putenv($name.'='.$value);
|
||||
} else {
|
||||
putenv(XdebugHandlerMock::ENV_ALLOW);
|
||||
putenv($name);
|
||||
}
|
||||
|
||||
if (false !== self::$envIniScanDir) {
|
||||
putenv('PHP_INI_SCAN_DIR='.self::$envIniScanDir);
|
||||
} else {
|
||||
putenv('PHP_INI_SCAN_DIR');
|
||||
}
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
// Ensure env is unset
|
||||
putenv(XdebugHandlerMock::ENV_ALLOW);
|
||||
putenv('PHP_INI_SCAN_DIR');
|
||||
putenv(IniHelper::ENV_ORIGINAL);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue