diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 65cd8e3ed..898db2355 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -288,12 +288,10 @@ class Factory if ($fullLoad) { $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins); - $pm = $this->createPluginManager($io, $composer, $globalComposer); + $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins); $composer->setPluginManager($pm); - if (!$disablePlugins) { - $pm->loadInstalledPlugins(); - } + $pm->loadInstalledPlugins(); // once we have plugins and custom installers we can // purge packages from local repos if they have been deleted on the filesystem @@ -433,11 +431,12 @@ class Factory * @param IOInterface $io * @param Composer $composer * @param Composer $globalComposer + * @param bool $disablePlugins * @return Plugin\PluginManager */ - protected function createPluginManager(IOInterface $io, Composer $composer, Composer $globalComposer = null) + protected function createPluginManager(IOInterface $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false) { - return new Plugin\PluginManager($io, $composer, $globalComposer); + return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins); } /** diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 174055f4e..bbfcc2f2c 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -36,6 +36,7 @@ class PluginManager protected $io; protected $globalComposer; protected $versionParser; + protected $disablePlugins = false; protected $plugins = array(); protected $registeredPlugins = array(); @@ -48,13 +49,15 @@ class PluginManager * @param IOInterface $io * @param Composer $composer * @param Composer $globalComposer + * @param bool $disablePlugins */ - public function __construct(IOInterface $io, Composer $composer, Composer $globalComposer = null) + public function __construct(IOInterface $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false) { $this->io = $io; $this->composer = $composer; $this->globalComposer = $globalComposer; $this->versionParser = new VersionParser(); + $this->disablePlugins = $disablePlugins; } /** @@ -62,6 +65,10 @@ class PluginManager */ public function loadInstalledPlugins() { + if ($this->disablePlugins) { + return; + } + $repo = $this->composer->getRepositoryManager()->getLocalRepository(); $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; if ($repo) { @@ -72,24 +79,6 @@ class PluginManager } } - /** - * Adds a plugin, activates it and registers it with the event dispatcher - * - * @param PluginInterface $plugin plugin instance - */ - public function addPlugin(PluginInterface $plugin) - { - if ($this->io->isDebug()) { - $this->io->writeError('Loading plugin '.get_class($plugin)); - } - $this->plugins[] = $plugin; - $plugin->activate($this->composer, $this->io); - - if ($plugin instanceof EventSubscriberInterface) { - $this->composer->getEventDispatcher()->addSubscriber($plugin); - } - } - /** * Gets all currently active plugin instances * @@ -100,97 +89,6 @@ class PluginManager return $this->plugins; } - /** - * Load all plugins and installers from a repository - * - * Note that plugins in the specified repository that rely on events that - * have fired prior to loading will be missed. This means you likely want to - * call this method as early as possible. - * - * @param RepositoryInterface $repo Repository to scan for plugins to install - * - * @throws \RuntimeException - */ - public function loadRepository(RepositoryInterface $repo) - { - foreach ($repo->getPackages() as $package) { /** @var PackageInterface $package */ - if ($package instanceof AliasPackage) { - continue; - } - if ('composer-plugin' === $package->getType()) { - $requiresComposer = null; - foreach ($package->getRequires() as $link) { /** @var Link $link */ - if ('composer-plugin-api' === $link->getTarget()) { - $requiresComposer = $link->getConstraint(); - break; - } - } - - if (!$requiresComposer) { - throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); - } - - $currentPluginApiVersion = $this->getPluginApiVersion(); - $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion)); - - if (!$requiresComposer->matches($currentPluginApiConstraint)) { - $this->io->writeError('The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); - continue; - } - - $this->registerPackage($package); - - // Backward compatibility - } elseif ('composer-installer' === $package->getType()) { - $this->registerPackage($package); - } - } - } - - /** - * Recursively generates a map of package names to packages for all deps - * - * @param Pool $pool Package pool of installed packages - * @param array $collected Current state of the map for recursion - * @param PackageInterface $package The package to analyze - * - * @return array Map of package names to packages - */ - protected function collectDependencies(Pool $pool, array $collected, PackageInterface $package) - { - $requires = array_merge( - $package->getRequires(), - $package->getDevRequires() - ); - - foreach ($requires as $requireLink) { - $requiredPackage = $this->lookupInstalledPackage($pool, $requireLink); - if ($requiredPackage && !isset($collected[$requiredPackage->getName()])) { - $collected[$requiredPackage->getName()] = $requiredPackage; - $collected = $this->collectDependencies($pool, $collected, $requiredPackage); - } - } - - return $collected; - } - - /** - * Resolves a package link to a package in the installed pool - * - * Since dependencies are already installed this should always find one. - * - * @param Pool $pool Pool of installed packages only - * @param Link $link Package link to look up - * - * @return PackageInterface|null The found package - */ - protected function lookupInstalledPackage(Pool $pool, Link $link) - { - $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint()); - - return (!empty($packages)) ? $packages[0] : null; - } - /** * Register a plugin package, activate it etc. * @@ -204,6 +102,10 @@ class PluginManager */ public function registerPackage(PackageInterface $package, $failOnMissingClasses = false) { + if ($this->disablePlugins) { + return; + } + $oldInstallerPlugin = ($package->getType() === 'composer-installer'); if (in_array($package->getName(), $this->registeredPlugins)) { @@ -261,23 +163,6 @@ class PluginManager } } - /** - * Retrieves the path a package is installed to. - * - * @param PackageInterface $package - * @param bool $global Whether this is a global package - * - * @return string Install path - */ - public function getInstallPath(PackageInterface $package, $global = false) - { - if (!$global) { - return $this->composer->getInstallationManager()->getInstallPath($package); - } - - return $this->globalComposer->getInstallationManager()->getInstallPath($package); - } - /** * Returns the version of the internal composer-plugin-api package. * @@ -287,4 +172,130 @@ class PluginManager { return PluginInterface::PLUGIN_API_VERSION; } + + /** + * Adds a plugin, activates it and registers it with the event dispatcher + * + * @param PluginInterface $plugin plugin instance + */ + private function addPlugin(PluginInterface $plugin) + { + if ($this->io->isDebug()) { + $this->io->writeError('Loading plugin '.get_class($plugin)); + } + $this->plugins[] = $plugin; + $plugin->activate($this->composer, $this->io); + + if ($plugin instanceof EventSubscriberInterface) { + $this->composer->getEventDispatcher()->addSubscriber($plugin); + } + } + + /** + * Load all plugins and installers from a repository + * + * Note that plugins in the specified repository that rely on events that + * have fired prior to loading will be missed. This means you likely want to + * call this method as early as possible. + * + * @param RepositoryInterface $repo Repository to scan for plugins to install + * + * @throws \RuntimeException + */ + private function loadRepository(RepositoryInterface $repo) + { + foreach ($repo->getPackages() as $package) { /** @var PackageInterface $package */ + if ($package instanceof AliasPackage) { + continue; + } + if ('composer-plugin' === $package->getType()) { + $requiresComposer = null; + foreach ($package->getRequires() as $link) { /** @var Link $link */ + if ('composer-plugin-api' === $link->getTarget()) { + $requiresComposer = $link->getConstraint(); + break; + } + } + + if (!$requiresComposer) { + throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); + } + + $currentPluginApiVersion = $this->getPluginApiVersion(); + $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion)); + + if (!$requiresComposer->matches($currentPluginApiConstraint)) { + $this->io->writeError('The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); + continue; + } + + $this->registerPackage($package); + + // Backward compatibility + } elseif ('composer-installer' === $package->getType()) { + $this->registerPackage($package); + } + } + } + + /** + * Recursively generates a map of package names to packages for all deps + * + * @param Pool $pool Package pool of installed packages + * @param array $collected Current state of the map for recursion + * @param PackageInterface $package The package to analyze + * + * @return array Map of package names to packages + */ + private function collectDependencies(Pool $pool, array $collected, PackageInterface $package) + { + $requires = array_merge( + $package->getRequires(), + $package->getDevRequires() + ); + + foreach ($requires as $requireLink) { + $requiredPackage = $this->lookupInstalledPackage($pool, $requireLink); + if ($requiredPackage && !isset($collected[$requiredPackage->getName()])) { + $collected[$requiredPackage->getName()] = $requiredPackage; + $collected = $this->collectDependencies($pool, $collected, $requiredPackage); + } + } + + return $collected; + } + + /** + * Resolves a package link to a package in the installed pool + * + * Since dependencies are already installed this should always find one. + * + * @param Pool $pool Pool of installed packages only + * @param Link $link Package link to look up + * + * @return PackageInterface|null The found package + */ + private function lookupInstalledPackage(Pool $pool, Link $link) + { + $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint()); + + return (!empty($packages)) ? $packages[0] : null; + } + + /** + * Retrieves the path a package is installed to. + * + * @param PackageInterface $package + * @param bool $global Whether this is a global package + * + * @return string Install path + */ + private function getInstallPath(PackageInterface $package, $global = false) + { + if (!$global) { + return $this->composer->getInstallationManager()->getInstallPath($package); + } + + return $this->globalComposer->getInstallationManager()->getInstallPath($package); + } }