diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index f594478a6..c22364ad2 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -20,11 +20,13 @@ use Composer\Package\BasePackage; use Composer\Package\CompletePackage; use Composer\Package\Locker; use Composer\Package\Package; +use Composer\Package\RootPackageInterface; use Composer\Package\Version\VersionParser; use Composer\PartialComposer; use Composer\Pcre\Preg; use Composer\Repository\RepositoryInterface; use Composer\Repository\InstalledRepository; +use Composer\Repository\RepositoryUtils; use Composer\Repository\RootPackageRepository; use Composer\Package\PackageInterface; use Composer\Package\Link; @@ -98,7 +100,7 @@ class PluginManager { if (!$this->arePluginsDisabled('local')) { $repo = $this->composer->getRepositoryManager()->getLocalRepository(); - $this->loadRepository($repo, false); + $this->loadRepository($repo, false, $this->composer->getPackage()); } if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { @@ -445,9 +447,11 @@ class PluginManager * * @param RepositoryInterface $repo Repository to scan for plugins to install * + * @phpstan-param ($isGlobalRepo is true ? null : RootPackageInterface) $rootPackage + * * @throws \RuntimeException */ - private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo): void + private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo, ?RootPackageInterface $rootPackage = null): void { $packages = $repo->getPackages(); @@ -462,10 +466,28 @@ class PluginManager } $sortedPackages = PackageSorter::sortPackages($packages, $weights); + if (!$isGlobalRepo) { + $requiredPackages = RepositoryUtils::filterRequiredPackages($packages, $rootPackage, true); + } + foreach ($sortedPackages as $package) { if (!($package instanceof CompletePackage)) { continue; } + + if (!in_array($package->getType(), ['composer-plugin', 'composer-installer'], true)) { + continue; + } + + if ( + !$isGlobalRepo + && !in_array($package, $requiredPackages, true) + && !$this->isPluginAllowed($package->getName(), false, true, false) + ) { + $this->io->writeError('The "'.$package->getName().'" plugin was not loaded as it is not listed in allow-plugins and is not required by the root package anymore.'); + continue; + } + if ('composer-plugin' === $package->getType()) { $this->registerPackage($package, false, $isGlobalRepo); // Backward compatibility @@ -668,7 +690,7 @@ class PluginManager /** * @internal */ - public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = false): bool + public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = false, bool $prompt = true): bool { if ($isGlobalPlugin) { $rules = &$this->allowGlobalPluginRules; @@ -703,7 +725,7 @@ class PluginManager return false; } - if ($this->io->isInteractive()) { + if ($this->io->isInteractive() && $prompt) { $composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer; $this->io->writeError(''.$package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins'); diff --git a/src/Composer/Repository/RepositoryUtils.php b/src/Composer/Repository/RepositoryUtils.php index 62e1c5b0f..e6960c63d 100644 --- a/src/Composer/Repository/RepositoryUtils.php +++ b/src/Composer/Repository/RepositoryUtils.php @@ -24,23 +24,28 @@ class RepositoryUtils /** * Find all of $packages which are required by $requirer, either directly or transitively * - * Require-dev is ignored + * Require-dev is ignored by default, you can enable the require-dev of the initial $requirer + * packages by passing $includeRequireDev=true, but require-dev of transitive dependencies + * are always ignored. * * @template T of PackageInterface * @param array $packages * @param list $bucket Do not pass this in, only used to avoid recursion with circular deps * @return list */ - public static function filterRequiredPackages(array $packages, PackageInterface $requirer, array $bucket = []): array + public static function filterRequiredPackages(array $packages, PackageInterface $requirer, bool $includeRequireDev = false, array $bucket = []): array { $requires = $requirer->getRequires(); + if ($includeRequireDev) { + $requires = array_merge($requires, $requirer->getDevRequires()); + } foreach ($packages as $candidate) { foreach ($candidate->getNames() as $name) { if (isset($requires[$name])) { if (!in_array($candidate, $bucket, true)) { $bucket[] = $candidate; - $bucket = self::filterRequiredPackages($packages, $candidate, $bucket); + $bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket); } break; } diff --git a/tests/Composer/Test/Repository/RepositoryUtilsTest.php b/tests/Composer/Test/Repository/RepositoryUtilsTest.php index ddc328998..e85d2f2f5 100644 --- a/tests/Composer/Test/Repository/RepositoryUtilsTest.php +++ b/tests/Composer/Test/Repository/RepositoryUtilsTest.php @@ -24,13 +24,13 @@ class RepositoryUtilsTest extends TestCase * @param PackageInterface[] $pkgs * @param string[] $expected */ - public function testFilterRequiredPackages(array $pkgs, PackageInterface $requirer, array $expected): void + public function testFilterRequiredPackages(array $pkgs, PackageInterface $requirer, array $expected, bool $includeRequireDev = false): void { $expected = array_map(static function (string $name) use ($pkgs): PackageInterface { return $pkgs[$name]; }, $expected); - self::assertSame($expected, RepositoryUtils::filterRequiredPackages($pkgs, $requirer)); + self::assertSame($expected, RepositoryUtils::filterRequiredPackages($pkgs, $requirer, $includeRequireDev)); } /** @@ -72,6 +72,10 @@ class RepositoryUtilsTest extends TestCase self::configureLinks($requirer, ['require-dev' => ['required/a' => '*']]); yield 'require-dev has no effect' => [$pkgs, $requirer, []]; + $requirer = self::getPackage('requirer/pkg'); + self::configureLinks($requirer, ['require-dev' => ['required/a' => '*']]); + yield 'require-dev works if called with it enabled' => [$pkgs, $requirer, ['a'], true]; + $requirer = self::getPackage('requirer/pkg'); self::configureLinks($requirer, ['require' => ['required/a' => '*']]); yield 'simple require' => [$pkgs, $requirer, ['a']];