|
@@ -20,13 +20,11 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|
|
class XdebugHandler
|
|
|
{
|
|
|
const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG';
|
|
|
- const ENV_INI_SCAN_DIR = 'PHP_INI_SCAN_DIR';
|
|
|
- const ENV_INI_SCAN_DIR_OLD = 'COMPOSER_PHP_INI_SCAN_DIR_OLD';
|
|
|
+ const RESTART_ID = 'internal';
|
|
|
|
|
|
private $output;
|
|
|
private $loaded;
|
|
|
- private $tmpIni;
|
|
|
- private $scanDir;
|
|
|
+ private $envScanDir;
|
|
|
|
|
|
/**
|
|
|
* Constructor
|
|
@@ -35,9 +33,7 @@ class XdebugHandler
|
|
|
{
|
|
|
$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';
|
|
|
+ $this->envScanDir = getenv('PHP_INI_SCAN_DIR');
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -45,23 +41,34 @@ 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 directory. An environment
|
|
|
- * variable is set so that the new process is created only once.
|
|
|
+ * and PHP_INI_SCAN_DIR is set to an empty value.
|
|
|
+ *
|
|
|
+ * This behaviour can be disabled by setting the COMPOSER_ALLOW_XDEBUG
|
|
|
+ * environment variable to 1. This variable is used internally so that the
|
|
|
+ * restarted process is created only once and PHP_INI_SCAN_DIR can be
|
|
|
+ * restored to its original value.
|
|
|
*/
|
|
|
public function check()
|
|
|
{
|
|
|
- if ($this->needsRestart()) {
|
|
|
+ $args = explode('|', strval(getenv(self::ENV_ALLOW)), 2);
|
|
|
+
|
|
|
+ if ($this->needsRestart($args[0])) {
|
|
|
$this->prepareRestart($command) && $this->restart($command);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- $originalIniScanDir = getenv(self::ENV_INI_SCAN_DIR_OLD);
|
|
|
+ // Restore environment variables if we are restarting
|
|
|
+ if (self::RESTART_ID === $args[0]) {
|
|
|
+ putenv(self::ENV_ALLOW);
|
|
|
|
|
|
- if ($originalIniScanDir) {
|
|
|
- putenv(self::ENV_INI_SCAN_DIR_OLD);
|
|
|
- putenv(self::ENV_INI_SCAN_DIR . '=' . $originalIniScanDir);
|
|
|
- } else {
|
|
|
- putenv(self::ENV_INI_SCAN_DIR);
|
|
|
+ if (false !== $this->envScanDir) {
|
|
|
+ // $args[1] contains the original value
|
|
|
+ if (isset($args[1])) {
|
|
|
+ putenv('PHP_INI_SCAN_DIR='.$args[1]);
|
|
|
+ } else {
|
|
|
+ putenv('PHP_INI_SCAN_DIR');
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -79,15 +86,17 @@ class XdebugHandler
|
|
|
/**
|
|
|
* Returns true if a restart is needed
|
|
|
*
|
|
|
+ * @param string $allow Environment value
|
|
|
+ *
|
|
|
* @return bool
|
|
|
*/
|
|
|
- private function needsRestart()
|
|
|
+ private function needsRestart($allow)
|
|
|
{
|
|
|
if (PHP_SAPI !== 'cli' || !defined('PHP_BINARY')) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- return !getenv(self::ENV_ALLOW) && $this->loaded;
|
|
|
+ return empty($allow) && $this->loaded;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -97,7 +106,6 @@ class XdebugHandler
|
|
|
* 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
|
|
|
*
|
|
@@ -111,38 +119,47 @@ class XdebugHandler
|
|
|
}
|
|
|
|
|
|
$additional = $this->getAdditionalInis($iniFiles, $replace);
|
|
|
- if ($this->writeTmpIni($iniFiles, $replace)) {
|
|
|
- $command = $this->getCommand($additional);
|
|
|
+ $tmpIni = $this->writeTmpIni($iniFiles, $replace);
|
|
|
+
|
|
|
+ if (false !== $tmpIni) {
|
|
|
+ $command = $this->getCommand($tmpIni);
|
|
|
+ return $this->setEnvironment($additional);
|
|
|
}
|
|
|
|
|
|
- return !empty($command) && putenv(self::ENV_ALLOW.'=1');
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Writes the temporary ini file, or clears its name if no ini
|
|
|
+ * Writes the tmp ini file and returns its filename
|
|
|
*
|
|
|
- * If there are no ini files, the tmp ini name is cleared so that
|
|
|
- * an empty value is passed with the -c option.
|
|
|
+ * 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.
|
|
|
*
|
|
|
* @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
|
|
|
+ * @return bool|string 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;
|
|
|
+ // Unlikely, maybe xdebug was loaded through a command line option.
|
|
|
+ return '';
|
|
|
}
|
|
|
|
|
|
+ if (function_exists('posix_getpwuid')) {
|
|
|
+ $user = posix_getpwuid(posix_getuid());
|
|
|
+ }
|
|
|
+ $prefix = !empty($user) ? $user['name'].'-' : '';
|
|
|
+ $tmpIni = sys_get_temp_dir().'/'.$prefix.'composer-php.ini';
|
|
|
+
|
|
|
$content = $this->getIniHeader($iniFiles);
|
|
|
foreach ($iniFiles as $file) {
|
|
|
$content .= $this->getIniData($file, $replace);
|
|
|
}
|
|
|
|
|
|
- return @file_put_contents($this->tmpIni, $content);
|
|
|
+ return @file_put_contents($tmpIni, $content) ? $tmpIni : false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -200,37 +217,41 @@ class XdebugHandler
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Creates the required environment and returns the restart command line
|
|
|
+ * Returns the restart command line
|
|
|
*
|
|
|
- * @param bool $additional Whether additional inis were loaded
|
|
|
+ * @param string $tmpIni The temporary ini file location
|
|
|
*
|
|
|
- * @return string|null The command line or null on failure
|
|
|
+ * @return string
|
|
|
*/
|
|
|
- private function getCommand($additional)
|
|
|
+ private function getCommand($tmpIni)
|
|
|
{
|
|
|
- if ($additional) {
|
|
|
- if (!file_exists($this->scanDir) && !@mkdir($this->scanDir, 0777)) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ $phpArgs = array(PHP_BINARY, '-c', $tmpIni);
|
|
|
+ $params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv']));
|
|
|
|
|
|
- $currentIniScanDir = getenv(self::ENV_INI_SCAN_DIR);
|
|
|
- if ($currentIniScanDir) {
|
|
|
- putenv(self::ENV_INI_SCAN_DIR_OLD.'='.$currentIniScanDir);
|
|
|
- } else {
|
|
|
- // make sure the env var does not exist if none is to be set
|
|
|
- // otherwise the child process will reset it incorrectly
|
|
|
- putenv(self::ENV_INI_SCAN_DIR_OLD);
|
|
|
- }
|
|
|
+ return implode(' ', array_map(array($this, 'escape'), $params));
|
|
|
+ }
|
|
|
|
|
|
- if (!putenv(self::ENV_INI_SCAN_DIR.'='.$this->scanDir)) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * Returns true if the restart environment variables were set
|
|
|
+ *
|
|
|
+ * @param bool $additional Whether additional inis were loaded
|
|
|
+ *
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ private function setEnvironment($additional)
|
|
|
+ {
|
|
|
+ $args = array(self::RESTART_ID);
|
|
|
+
|
|
|
+ if (false !== $this->envScanDir) {
|
|
|
+ // Save current PHP_INI_SCAN_DIR
|
|
|
+ $args[] = $this->envScanDir;
|
|
|
}
|
|
|
|
|
|
- $phpArgs = array(PHP_BINARY, '-c', $this->tmpIni);
|
|
|
- $params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv']));
|
|
|
+ if ($additional && !putenv('PHP_INI_SCAN_DIR=')) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
- return implode(' ', array_map(array($this, 'escape'), $params));
|
|
|
+ return putenv(self::ENV_ALLOW.'='.implode('|', $args));
|
|
|
}
|
|
|
|
|
|
/**
|