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\CompletePackage;
use Composer\Package\Locker; use Composer\Package\Locker;
use Composer\Package\Package; use Composer\Package\Package;
use Composer\Package\RootPackageInterface;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\PartialComposer; use Composer\PartialComposer;
use Composer\Pcre\Preg; use Composer\Pcre\Preg;
use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledRepository; use Composer\Repository\InstalledRepository;
use Composer\Repository\RepositoryUtils;
use Composer\Repository\RootPackageRepository; use Composer\Repository\RootPackageRepository;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Package\Link; use Composer\Package\Link;
@ -98,7 +100,7 @@ class PluginManager
{ {
if (!$this->arePluginsDisabled('local')) { if (!$this->arePluginsDisabled('local')) {
$repo = $this->composer->getRepositoryManager()->getLocalRepository(); $repo = $this->composer->getRepositoryManager()->getLocalRepository();
$this->loadRepository($repo, false); $this->loadRepository($repo, false, $this->composer->getPackage());
} }
if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) {
@ -445,9 +447,11 @@ class PluginManager
* *
* @param RepositoryInterface $repo Repository to scan for plugins to install * @param RepositoryInterface $repo Repository to scan for plugins to install
* *
* @phpstan-param ($isGlobalRepo is true ? null : RootPackageInterface) $rootPackage
*
* @throws \RuntimeException * @throws \RuntimeException
*/ */
private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo): void private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo, ?RootPackageInterface $rootPackage = null): void
{ {
$packages = $repo->getPackages(); $packages = $repo->getPackages();
@ -462,10 +466,28 @@ class PluginManager
} }
$sortedPackages = PackageSorter::sortPackages($packages, $weights); $sortedPackages = PackageSorter::sortPackages($packages, $weights);
if (!$isGlobalRepo) {
$requiredPackages = RepositoryUtils::filterRequiredPackages($packages, $rootPackage, true);
}
foreach ($sortedPackages as $package) { foreach ($sortedPackages as $package) {
if (!($package instanceof CompletePackage)) { if (!($package instanceof CompletePackage)) {
continue; 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()) { if ('composer-plugin' === $package->getType()) {
$this->registerPackage($package, false, $isGlobalRepo); $this->registerPackage($package, false, $isGlobalRepo);
// Backward compatibility // Backward compatibility
@ -668,7 +690,7 @@ class PluginManager
/** /**
* @internal * @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) { if ($isGlobalPlugin) {
$rules = &$this->allowGlobalPluginRules; $rules = &$this->allowGlobalPluginRules;
@ -703,7 +725,7 @@ class PluginManager
return false; return false;
} }
if ($this->io->isInteractive()) { if ($this->io->isInteractive() && $prompt) {
$composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer; $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>'); $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 * 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 * @template T of PackageInterface
* @param array<T> $packages * @param array<T> $packages
* @param list<T> $bucket Do not pass this in, only used to avoid recursion with circular deps * @param list<T> $bucket Do not pass this in, only used to avoid recursion with circular deps
* @return list<T> * @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(); $requires = $requirer->getRequires();
if ($includeRequireDev) {
$requires = array_merge($requires, $requirer->getDevRequires());
}
foreach ($packages as $candidate) { foreach ($packages as $candidate) {
foreach ($candidate->getNames() as $name) { foreach ($candidate->getNames() as $name) {
if (isset($requires[$name])) { if (isset($requires[$name])) {
if (!in_array($candidate, $bucket, true)) { if (!in_array($candidate, $bucket, true)) {
$bucket[] = $candidate; $bucket[] = $candidate;
$bucket = self::filterRequiredPackages($packages, $candidate, $bucket); $bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket);
} }
break; break;
} }

View File

@ -24,13 +24,13 @@ class RepositoryUtilsTest extends TestCase
* @param PackageInterface[] $pkgs * @param PackageInterface[] $pkgs
* @param string[] $expected * @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 { $expected = array_map(static function (string $name) use ($pkgs): PackageInterface {
return $pkgs[$name]; return $pkgs[$name];
}, $expected); }, $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' => '*']]); self::configureLinks($requirer, ['require-dev' => ['required/a' => '*']]);
yield 'require-dev has no effect' => [$pkgs, $requirer, []]; 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'); $requirer = self::getPackage('requirer/pkg');
self::configureLinks($requirer, ['require' => ['required/a' => '*']]); self::configureLinks($requirer, ['require' => ['required/a' => '*']]);
yield 'simple require' => [$pkgs, $requirer, ['a']]; yield 'simple require' => [$pkgs, $requirer, ['a']];