1
0
Fork 0

Fix UX when a non-required plugin is still present in vendor dir (#12000)

Composer now skips it and does not prompt if it is not allowed to run, fixes #11944
pull/12003/head
Jordi Boggiano 2024-05-31 10:29:56 +02:00 committed by GitHub
parent 37d722e73c
commit c1be804a0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 40 additions and 9 deletions

View File

@ -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('<warning>The "'.$package->getName().'" plugin was not loaded as it is not listed in allow-plugins and is not required by the root package anymore.</warning>');
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('<warning>'.$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</warning>');

View File

@ -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<T> $packages
* @param list<T> $bucket Do not pass this in, only used to avoid recursion with circular deps
* @return list<T>
*/
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;
}

View File

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