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']];