Browse Source

Merge remote-tracking branch 'johnstevenson/external-xdebug'

Jordi Boggiano 7 years ago
parent
commit
38866ba310

+ 4 - 7
bin/composer

@@ -7,16 +7,13 @@ if (PHP_SAPI !== 'cli') {
 
 require __DIR__.'/../src/bootstrap.php';
 
-use Composer\Factory;
-use Composer\XdebugHandler;
 use Composer\Console\Application;
+use Composer\XdebugHandler\XdebugHandler;
 
 error_reporting(-1);
 
-// Create output for XdebugHandler and Application
-$output = Factory::createOutput();
-
-$xdebug = new XdebugHandler($output);
+// Restart without xdebug
+$xdebug = new XdebugHandler('Composer', '--ansi');
 $xdebug->check();
 unset($xdebug);
 
@@ -56,4 +53,4 @@ putenv('COMPOSER_BINARY='.realpath($_SERVER['argv'][0]));
 
 // run the command application
 $application = new Application();
-$application->run(null, $output);
+$application->run();

+ 1 - 0
composer.json

@@ -27,6 +27,7 @@
         "composer/ca-bundle": "^1.0",
         "composer/semver": "^1.0",
         "composer/spdx-licenses": "^1.2",
+        "composer/xdebug-handler": "^1.1",
         "seld/jsonlint": "^1.4",
         "symfony/console": "^2.7 || ^3.0 || ^4.0",
         "symfony/finder": "^2.7 || ^3.0 || ^4.0",

+ 45 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "a248442611fb58177b28432be1af692c",
+    "content-hash": "0d1f37a66bf7821e9aa424785ea8ab52",
     "packages": [
         {
             "name": "composer/ca-bundle",
@@ -185,6 +185,50 @@
             ],
             "time": "2018-01-31T13:17:27+00:00"
         },
+        {
+            "name": "composer/xdebug-handler",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/composer/xdebug-handler.git",
+                "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/c919dc6c62e221fc6406f861ea13433c0aa24f08",
+                "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.2 || ^7.0",
+                "psr/log": "^1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Composer\\XdebugHandler\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "John Stevenson",
+                    "email": "john-stevenson@blueyonder.co.uk"
+                }
+            ],
+            "description": "Restarts a process without xdebug.",
+            "keywords": [
+                "Xdebug",
+                "performance"
+            ],
+            "time": "2018-04-11T15:42:36+00:00"
+        },
         {
             "name": "justinrainbow/json-schema",
             "version": "5.2.7",

+ 1 - 0
src/Composer/Compiler.php

@@ -122,6 +122,7 @@ class Compiler
             ->in(__DIR__.'/../../vendor/composer/spdx-licenses/')
             ->in(__DIR__.'/../../vendor/composer/semver/')
             ->in(__DIR__.'/../../vendor/composer/ca-bundle/')
+            ->in(__DIR__.'/../../vendor/composer/xdebug-handler/')
             ->in(__DIR__.'/../../vendor/psr/')
             ->sort($finderSort)
         ;

+ 2 - 2
src/Composer/Repository/PlatformRepository.php

@@ -12,12 +12,12 @@
 
 namespace Composer\Repository;
 
-use Composer\XdebugHandler;
 use Composer\Package\CompletePackage;
 use Composer\Package\PackageInterface;
 use Composer\Package\Version\VersionParser;
 use Composer\Plugin\PluginInterface;
 use Composer\Util\Silencer;
+use Composer\XdebugHandler\XdebugHandler;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
@@ -120,7 +120,7 @@ class PlatformRepository extends ArrayRepository
         }
 
         // Check for xdebug in a restarted process
-        if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = strval(getenv(XdebugHandler::ENV_VERSION)))) {
+        if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) {
             $this->addExtension('xdebug', $prettyVersion);
         }
 

+ 3 - 15
src/Composer/Util/IniHelper.php

@@ -12,6 +12,8 @@
 
 namespace Composer\Util;
 
+use Composer\XdebugHandler\XdebugHandler;
+
 /**
  * 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
@@ -21,8 +23,6 @@ namespace Composer\Util;
  */
 class IniHelper
 {
-    const ENV_ORIGINAL = 'COMPOSER_ORIGINAL_INIS';
-
     /**
      * Returns an array of php.ini locations with at least one entry
      *
@@ -33,19 +33,7 @@ class IniHelper
      */
     public static function getAll()
     {
-        $env = getenv(self::ENV_ORIGINAL);
-
-        if (false !== $env) {
-            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;
+        return XdebugHandler::getAllIniFiles();
     }
 
     /**

+ 5 - 275
src/Composer/XdebugHandler.php

@@ -12,290 +12,20 @@
 
 namespace Composer;
 
-use Composer\Util\IniHelper;
 use Symfony\Component\Console\Output\OutputInterface;
 
+trigger_error('The ' . __NAMESPACE__ . '\XdebugHandler class is deprecated, use Composer\XdebugHandler\XdebugHandler instead,', E_USER_DEPRECATED);
+
 /**
- * @author John Stevenson <john-stevenson@blueyonder.co.uk>
+ * @deprecated use Composer\XdebugHandler\XdebugHandler instead
  */
-class XdebugHandler
+class XdebugHandler extends XdebugHandler\XdebugHandler
 {
     const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG';
     const ENV_VERSION = 'COMPOSER_XDEBUG_VERSION';
-    const RESTART_ID = 'internal';
-
-    private $output;
-    private $loaded;
-    private $envScanDir;
-    private $version;
-    private $tmpIni;
 
-    /**
-     * Constructor
-     */
     public function __construct(OutputInterface $output)
     {
-        $this->output = $output;
-        $this->loaded = extension_loaded('xdebug');
-        $this->envScanDir = getenv('PHP_INI_SCAN_DIR');
-
-        if ($this->loaded) {
-            $ext = new \ReflectionExtension('xdebug');
-            $this->version = strval($ext->getVersion());
-        }
-    }
-
-    /**
-     * 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 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
-     * restarted process is created only once and PHP_INI_SCAN_DIR can be
-     * restored to its original value.
-     */
-    public function check()
-    {
-        $args = explode('|', strval(getenv(self::ENV_ALLOW)), 2);
-
-        if ($this->needsRestart($args[0])) {
-            if ($this->prepareRestart()) {
-                $command = $this->getCommand();
-                $this->restart($command);
-            }
-
-            return;
-        }
-
-        // Restore environment variables if we are restarting
-        if (self::RESTART_ID === $args[0]) {
-            putenv(self::ENV_ALLOW);
-
-            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');
-                }
-            }
-
-            // Clear version if the restart failed to disable xdebug
-            if ($this->loaded) {
-                putenv(self::ENV_VERSION);
-            }
-        }
-    }
-
-    /**
-     * 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);
-    }
-
-    /**
-     * Returns true if a restart is needed
-     *
-     * @param string $allow Environment value
-     *
-     * @return bool
-     */
-    private function needsRestart($allow)
-    {
-        if (PHP_SAPI !== 'cli' || !defined('PHP_BINARY')) {
-            return false;
-        }
-
-        return empty($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
-     *
-     * @return bool
-     */
-    private function prepareRestart()
-    {
-        $this->tmpIni = '';
-        $iniPaths = IniHelper::getAll();
-        $additional = count($iniPaths) > 1;
-
-        if ($this->writeTmpIni($iniPaths)) {
-            return $this->setEnvironment($additional, $iniPaths);
-        }
-
-        return false;
-    }
-
-    /**
-     * Returns true if the tmp ini file was written
-     *
-     * The filename is passed as the -c option when the process restarts.
-     *
-     * @param array $iniPaths Locations reported by the current process
-     *
-     * @return bool
-     */
-    private function writeTmpIni(array $iniPaths)
-    {
-        if (!$this->tmpIni = tempnam(sys_get_temp_dir(), '')) {
-            return false;
-        }
-
-        // $iniPaths has at least one item and it may be empty
-        if (empty($iniPaths[0])) {
-            array_shift($iniPaths);
-        }
-
-        $content = '';
-        $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi';
-
-        foreach ($iniPaths as $file) {
-            $data = preg_replace($regex, ';$1', file_get_contents($file));
-            $content .= $data.PHP_EOL;
-        }
-
-        $content .= 'allow_url_fopen='.ini_get('allow_url_fopen').PHP_EOL;
-        $content .= 'disable_functions="'.ini_get('disable_functions').'"'.PHP_EOL;
-        $content .= 'memory_limit='.ini_get('memory_limit').PHP_EOL;
-
-        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
-            // Work-around for PHP windows bug, see issue #6052
-            $content .= 'opcache.enable_cli=0'.PHP_EOL;
-        }
-
-        return @file_put_contents($this->tmpIni, $content);
-    }
-
-    /**
-     * Returns the restart command line
-     *
-     * @return string
-     */
-    private function getCommand()
-    {
-        $phpArgs = array(PHP_BINARY, '-c', $this->tmpIni);
-        $params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv']));
-
-        return implode(' ', array_map(array($this, 'escape'), $params));
-    }
-
-    /**
-     * Returns true if the restart environment variables were set
-     *
-     * @param bool  $additional Whether there were additional inis
-     * @param array $iniPaths   Locations reported by the current process
-     *
-     * @return bool
-     */
-    private function setEnvironment($additional, array $iniPaths)
-    {
-        // Set scan dir to an empty value if additional ini files were used
-        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;
-        }
-
-        // Make xdebug version available to restarted process
-        if (!putenv(self::ENV_VERSION.'='.$this->version)) {
-            return false;
-        }
-
-        // Flag restarted process and save env scan dir state
-        $args = array(self::RESTART_ID);
-
-        if (false !== $this->envScanDir) {
-            // Save current PHP_INI_SCAN_DIR
-            $args[] = $this->envScanDir;
-        }
-
-        return putenv(self::ENV_ALLOW.'='.implode('|', $args));
-    }
-
-    /**
-     * 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;
+        parent::__construct('composer', '--ansi');
     }
 }

+ 0 - 48
tests/Composer/Test/Mock/XdebugHandlerMock.php

@@ -1,48 +0,0 @@
-<?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 $testVersion = '2.5.0';
-
-    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);
-
-        $prop = $class->getProperty('version');
-        $prop->setAccessible(true);
-        $version = $loaded ? $this->testVersion : '';
-        $prop->setValue($this, $version);
-
-        $this->restarted = false;
-    }
-
-    protected function restart($command)
-    {
-        $this->restarted = true;
-    }
-}

+ 7 - 5
tests/Composer/Test/Util/IniHelperTest.php

@@ -13,6 +13,7 @@
 namespace Composer\Test\Util;
 
 use Composer\Util\IniHelper;
+use Composer\XdebugHandler\XdebugHandler;
 use PHPUnit\Framework\TestCase;
 
 /**
@@ -41,7 +42,6 @@ class IniHelperTest extends TestCase
 
         $this->setEnv($paths);
         $this->assertContains('loaded.ini', IniHelper::getMessage());
-        $this->assertEquals($paths, IniHelper::getAll());
     }
 
     public function testWithLoadedIniAndAdditional()
@@ -72,22 +72,24 @@ class IniHelperTest extends TestCase
 
     public static function setUpBeforeClass()
     {
+        // Register our name with XdebugHandler
+        $xdebug = new XdebugHandler('composer');
         // Save current state
-        self::$envOriginal = getenv(IniHelper::ENV_ORIGINAL);
+        self::$envOriginal = getenv('COMPOSER_ORIGINAL_INIS');
     }
 
     public static function tearDownAfterClass()
     {
         // Restore original state
         if (false !== self::$envOriginal) {
-            putenv(IniHelper::ENV_ORIGINAL.'='.self::$envOriginal);
+            putenv('COMPOSER_ORIGINAL_INIS='.self::$envOriginal);
         } else {
-            putenv(IniHelper::ENV_ORIGINAL);
+            putenv('COMPOSER_ORIGINAL_INIS');
         }
     }
 
     protected function setEnv(array $paths)
     {
-        putenv(IniHelper::ENV_ORIGINAL.'='.implode(PATH_SEPARATOR, $paths));
+        putenv('COMPOSER_ORIGINAL_INIS='.implode(PATH_SEPARATOR, $paths));
     }
 }

+ 0 - 182
tests/Composer/Test/XdebugHandlerTest.php

@@ -1,182 +0,0 @@
-<?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;
-use Composer\Util\IniHelper;
-use PHPUnit\Framework\TestCase;
-
-/**
- * @author John Stevenson <john-stevenson@blueyonder.co.uk>
- *
- * We use PHP_BINARY which only became available in PHP 5.4 *
- * @requires PHP 5.4
- */
-class XdebugHandlerTest extends TestCase
-{
-    public static $env = array();
-
-    public function testRestartWhenLoaded()
-    {
-        $loaded = true;
-
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertTrue($xdebug->restarted);
-        $this->assertInternalType('string', getenv(IniHelper::ENV_ORIGINAL));
-    }
-
-    public function testNoRestartWhenNotLoaded()
-    {
-        $loaded = false;
-
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertFalse($xdebug->restarted);
-        $this->assertFalse(getenv(IniHelper::ENV_ORIGINAL));
-    }
-
-    public function testNoRestartWhenLoadedAndAllowed()
-    {
-        $loaded = true;
-        putenv(XdebugHandlerMock::ENV_ALLOW.'=1');
-
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertFalse($xdebug->restarted);
-    }
-
-    public function testEnvAllow()
-    {
-        $loaded = true;
-
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $expected = XdebugHandlerMock::RESTART_ID;
-        $this->assertEquals($expected, getenv(XdebugHandlerMock::ENV_ALLOW));
-
-        // Mimic restart
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertFalse($xdebug->restarted);
-        $this->assertFalse(getenv(XdebugHandlerMock::ENV_ALLOW));
-    }
-
-    public function testEnvAllowWithScanDir()
-    {
-        $loaded = true;
-        $dir = '/some/where';
-        putenv('PHP_INI_SCAN_DIR='.$dir);
-
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $expected = XdebugHandlerMock::RESTART_ID.'|'.$dir;
-        $this->assertEquals($expected, getenv(XdebugHandlerMock::ENV_ALLOW));
-
-        // Mimic setting scan dir and restart
-        putenv('PHP_INI_SCAN_DIR=');
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertEquals($dir, getenv('PHP_INI_SCAN_DIR'));
-    }
-
-    public function testEnvAllowWithEmptyScanDir()
-    {
-        $loaded = true;
-        putenv('PHP_INI_SCAN_DIR=');
-
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $expected = XdebugHandlerMock::RESTART_ID.'|';
-        $this->assertEquals($expected, getenv(XdebugHandlerMock::ENV_ALLOW));
-
-        // Unset scan dir and mimic restart
-        putenv('PHP_INI_SCAN_DIR');
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertEquals('', getenv('PHP_INI_SCAN_DIR'));
-    }
-
-    public function testEnvVersionWhenLoaded()
-    {
-        $loaded = true;
-
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertEquals($xdebug->testVersion, getenv(XdebugHandlerMock::ENV_VERSION));
-
-        // Mimic successful restart
-        $loaded = false;
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertEquals($xdebug->testVersion, getenv(XdebugHandlerMock::ENV_VERSION));
-    }
-
-    public function testEnvVersionWhenNotLoaded()
-    {
-        $loaded = false;
-
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertFalse(getenv(XdebugHandlerMock::ENV_VERSION));
-    }
-
-    public function testEnvVersionWhenRestartFails()
-    {
-        $loaded = true;
-
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-
-        // Mimic failed restart
-        $xdebug = new XdebugHandlerMock($loaded);
-        $xdebug->check();
-        $this->assertFalse(getenv(XdebugHandlerMock::ENV_VERSION));
-    }
-
-    public static function setUpBeforeClass()
-    {
-        // Save current state
-        $names = array(
-            XdebugHandlerMock::ENV_ALLOW,
-            XdebugHandlerMock::ENV_VERSION,
-            'PHP_INI_SCAN_DIR',
-            IniHelper::ENV_ORIGINAL,
-        );
-
-        foreach ($names as $name) {
-            self::$env[$name] = getenv($name);
-        }
-    }
-
-    public static function tearDownAfterClass()
-    {
-        // Restore original state
-        foreach (self::$env as $name => $value) {
-            if (false !== $value) {
-                putenv($name.'='.$value);
-            } else {
-                putenv($name);
-            }
-        }
-    }
-
-    protected function setUp()
-    {
-        // Ensure env is unset
-        putenv(XdebugHandlerMock::ENV_ALLOW);
-        putenv(XdebugHandlerMock::ENV_VERSION);
-        putenv('PHP_INI_SCAN_DIR');
-        putenv(IniHelper::ENV_ORIGINAL);
-    }
-}