diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58adb6fec..43419d94a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+### [2.3.10] 2022-07-13
+
+ * Fixed plugins from CWD/vendor being loaded in some cases like create-project or validate even though the target directory is outside of CWD (#10935)
+ * Fixed support for legacy (Composer 1.x, e.g. hirak/prestissimo) plugins which will not warn/error anymore if not in allow-plugins, as they are anyway not loaded (#10928)
+ * Fixed pre-install check for allowed plugins not taking --no-plugins into account (#10925)
+ * Fixed support for disable_functions containing disk_free_space (#10936)
+ * Fixed RootPackageRepository usages to always clone the root package to avoid interoperability issues with plugins (#10940)
+
### [2.3.9] 2022-07-05
* Fixed non-interactive behavior of allow-plugins to throw instead of continue with a warning to avoid broken installs (#10920)
@@ -111,6 +119,14 @@
* Fixed symlink creation in linux VM guest filesystems to be recognized by Windows (#10592)
* Performance improvement in pool optimization step (#10585)
+### [2.2.17] 2022-07-13
+
+ * Fixed plugins from CWD/vendor being loaded in some cases like create-project or validate even though the target directory is outside of CWD (#10935)
+ * Fixed support for legacy (Composer 1.x, e.g. hirak/prestissimo) plugins which will not warn/error anymore if not in allow-plugins, as they are anyway not loaded (#10928)
+ * Fixed pre-install check for allowed plugins not taking --no-plugins into account (#10925)
+ * Fixed support for disable_functions containing disk_free_space (#10936)
+ * Fixed RootPackageRepository usages to always clone the root package to avoid interoperability issues with plugins (#10940)
+
### [2.2.16] 2022-07-05
* Fixed non-interactive behavior of allow-plugins to throw instead of continue with a warning to avoid broken installs (#10920)
@@ -1574,6 +1590,7 @@
* Initial release
+[2.3.10]: https://github.com/composer/composer/compare/2.3.9...2.3.10
[2.3.9]: https://github.com/composer/composer/compare/2.3.8...2.3.9
[2.3.8]: https://github.com/composer/composer/compare/2.3.7...2.3.8
[2.3.7]: https://github.com/composer/composer/compare/2.3.6...2.3.7
@@ -1586,6 +1603,7 @@
[2.3.0]: https://github.com/composer/composer/compare/2.3.0-RC2...2.3.0
[2.3.0-RC2]: https://github.com/composer/composer/compare/2.3.0-RC1...2.3.0-RC2
[2.3.0-RC1]: https://github.com/composer/composer/compare/2.2.9...2.3.0-RC1
+[2.2.17]: https://github.com/composer/composer/compare/2.2.16...2.2.17
[2.2.16]: https://github.com/composer/composer/compare/2.2.15...2.2.16
[2.2.15]: https://github.com/composer/composer/compare/2.2.14...2.2.15
[2.2.14]: https://github.com/composer/composer/compare/2.2.13...2.2.14
diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php
index bdd1aa7fb..ccf990fca 100644
--- a/src/Composer/Cache.php
+++ b/src/Composer/Cache.php
@@ -162,11 +162,11 @@ class Cache
unlink($tempFileName);
$message = sprintf(
- 'Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$u bytes of free space available',
+ 'Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$s bytes of free space available',
$tempFileName,
$m[1],
$m[2],
- @disk_free_space(dirname($tempFileName))
+ function_exists('disk_free_space') ? @disk_free_space(dirname($tempFileName)) : 'unknown'
);
$this->io->writeError($message);
diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php
index 9788ffa23..6bccf931d 100644
--- a/src/Composer/Command/BaseDependencyCommand.php
+++ b/src/Composer/Command/BaseDependencyCommand.php
@@ -60,7 +60,7 @@ abstract class BaseDependencyCommand extends BaseCommand
$repos = [];
- $repos[] = new RootPackageRepository($composer->getPackage());
+ $repos[] = new RootPackageRepository(clone $composer->getPackage());
if ($input->getOption('locked')) {
$locker = $composer->getLocker();
diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php
index 6854ee690..1053fc62c 100644
--- a/src/Composer/Command/CheckPlatformReqsCommand.php
+++ b/src/Composer/Command/CheckPlatformReqsCommand.php
@@ -77,7 +77,7 @@ EOT
$requires[$require] = array($link);
}
- $installedRepo = new InstalledRepository(array($installedRepo, new RootPackageRepository($composer->getPackage())));
+ $installedRepo = new InstalledRepository(array($installedRepo, new RootPackageRepository(clone $composer->getPackage())));
foreach ($installedRepo->getPackages() as $package) {
if (in_array($package->getName(), $removePackages, true)) {
continue;
diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php
index fecdd4c89..9721656b9 100644
--- a/src/Composer/Command/CreateProjectCommand.php
+++ b/src/Composer/Command/CreateProjectCommand.php
@@ -26,6 +26,7 @@ use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\Package\Version\VersionSelector;
use Composer\Package\AliasPackage;
use Composer\Pcre\Preg;
+use Composer\Plugin\PluginBlockedException;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
@@ -274,9 +275,15 @@ EOT
$installer->disablePlugins();
}
- $status = $installer->run();
- if (0 !== $status) {
- return $status;
+ try {
+ $status = $installer->run();
+ if (0 !== $status) {
+ return $status;
+ }
+ } catch (PluginBlockedException $e) {
+ $io->writeError('Hint: To allow running the config command recommended below before dependencies are installed, run create-project with --no-install.');
+ $io->writeError('You can then cd into '.getcwd().', configure allow-plugins, and finally run a composer install to complete the process.');
+ throw $e;
}
}
@@ -409,15 +416,7 @@ EOT
throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities)));
}
- $composerJson = array_merge(
- // prevent version guessing from happening
- array('version' => '1.0.0'),
- $config->all(),
- // ensure the vendor dir and its plugins does not get loaded if CWD/vendor has plugins in it
- array('config' => array('vendor-dir' => Platform::getDevNull()))
- );
- $factory = new Factory;
- $composer = $factory->createComposer($io, $composerJson, $disablePlugins, Platform::getDevNull(), true, $disableScripts);
+ $composer = Factory::create($io, $config->all(), $disablePlugins, $disableScripts);
$config = $composer->getConfig();
$rm = $composer->getRepositoryManager();
diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php
index 255a2be21..4a4e2d465 100644
--- a/src/Composer/Command/DiagnoseCommand.php
+++ b/src/Composer/Command/DiagnoseCommand.php
@@ -377,6 +377,10 @@ EOT
*/
private function checkDiskSpace(Config $config)
{
+ if (!function_exists('disk_free_space')) {
+ return true;
+ }
+
$minSpaceFree = 1024 * 1024;
if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
|| (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php
index 8fc4bc9db..7b88a0877 100644
--- a/src/Composer/Command/HomeCommand.php
+++ b/src/Composer/Command/HomeCommand.php
@@ -166,7 +166,7 @@ EOT
if ($composer) {
return array_merge(
- array(new RootPackageRepository($composer->getPackage())), // root package
+ array(new RootPackageRepository(clone $composer->getPackage())), // root package
array($composer->getRepositoryManager()->getLocalRepository()), // installed packages
$composer->getRepositoryManager()->getRepositories() // remotes
);
diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php
index a49f1e063..aa66c3f6a 100644
--- a/src/Composer/Command/ShowCommand.php
+++ b/src/Composer/Command/ShowCommand.php
@@ -196,7 +196,7 @@ EOT
$lockedRepo = null;
if ($input->getOption('self')) {
- $package = $this->requireComposer()->getPackage();
+ $package = clone $this->requireComposer()->getPackage();
if ($input->getOption('name-only')) {
$io->write($package->getName());
diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php
index 82cc0b2a0..9964fe72c 100644
--- a/src/Composer/Console/Application.php
+++ b/src/Composer/Console/Application.php
@@ -410,7 +410,7 @@ class Application extends BaseApplication
Silencer::suppress();
try {
$composer = $this->getComposer(false, true);
- if ($composer) {
+ if (null !== $composer && function_exists('disk_free_space')) {
$config = $composer->getConfig();
$minSpaceFree = 1024 * 1024;
diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php
index 340f499b2..fba112be2 100644
--- a/src/Composer/Factory.php
+++ b/src/Composer/Factory.php
@@ -276,7 +276,7 @@ class Factory
* @param IOInterface $io IO instance
* @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will
* read from the default filename
- * @param bool $disablePlugins Whether plugins should not be loaded
+ * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
* @param bool $disableScripts Whether scripts should not be run
* @param string|null $cwd
* @param bool $fullLoad Whether to initialize everything or only main project stuff (used when loading the global composer)
@@ -285,7 +285,7 @@ class Factory
* @return Composer|PartialComposer Composer if $fullLoad is true, otherwise PartialComposer
* @phpstan-return ($fullLoad is true ? Composer : PartialComposer)
*/
- public function createComposer(IOInterface $io, $localConfig = null, bool $disablePlugins = false, ?string $cwd = null, bool $fullLoad = true, bool $disableScripts = false)
+ public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, ?string $cwd = null, bool $fullLoad = true, bool $disableScripts = false)
{
$cwd = $cwd ?? Platform::getCwd(true);
@@ -473,11 +473,15 @@ class Factory
}
/**
+ * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
* @return PartialComposer|Composer|null By default PartialComposer, but Composer if $fullLoad is set to true
* @phpstan-return ($fullLoad is true ? Composer|null : PartialComposer|null)
*/
- protected function createGlobalComposer(IOInterface $io, Config $config, bool $disablePlugins, bool $disableScripts, bool $fullLoad = false): ?PartialComposer
+ protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, bool $disableScripts, bool $fullLoad = false): ?PartialComposer
{
+ // make sure if disable plugins was 'local' it is now turned off
+ $disablePlugins = $disablePlugins === 'global' || $disablePlugins === true;
+
$composer = null;
try {
$composer = $this->createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad, $disableScripts);
@@ -558,9 +562,10 @@ class Factory
}
/**
+ * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
* @return Plugin\PluginManager
*/
- protected function createPluginManager(IOInterface $io, Composer $composer, PartialComposer $globalComposer = null, bool $disablePlugins = false): Plugin\PluginManager
+ protected function createPluginManager(IOInterface $io, Composer $composer, PartialComposer $globalComposer = null, $disablePlugins = false): Plugin\PluginManager
{
return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins);
}
@@ -608,14 +613,22 @@ class Factory
* @param IOInterface $io IO instance
* @param mixed $config either a configuration array or a filename to read from, if null it will read from
* the default filename
- * @param bool $disablePlugins Whether plugins should not be loaded
+ * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
* @param bool $disableScripts Whether scripts should not be run
* @return Composer
*/
- public static function create(IOInterface $io, $config = null, bool $disablePlugins = false, bool $disableScripts = false): Composer
+ public static function create(IOInterface $io, $config = null, $disablePlugins = false, bool $disableScripts = false): Composer
{
$factory = new static();
+ // for BC reasons, if a config is passed in either as array or a path that is not the default composer.json path
+ // we disable local plugins as they really should not be loaded from CWD
+ // If you want to avoid this behavior, you should be calling createComposer directly with a $cwd arg set correctly
+ // to the path where the composer.json being loaded resides
+ if ($config !== null && $config !== self::getComposerFile() && $disablePlugins === false) {
+ $disablePlugins = 'local';
+ }
+
return $factory->createComposer($io, $config, $disablePlugins, null, true, $disableScripts);
}
diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php
index 04fd446a7..0b34418db 100644
--- a/src/Composer/Installer/PluginInstaller.php
+++ b/src/Composer/Installer/PluginInstaller.php
@@ -49,7 +49,7 @@ class PluginInstaller extends LibraryInstaller
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
// fail install process early if it is going to fail due to a plugin not being allowed
- if (($type === 'install' || $type === 'update') && !$this->getPluginManager()->arePluginsDisabled()) {
+ if (($type === 'install' || $type === 'update') && !$this->getPluginManager()->arePluginsDisabled('local')) {
$this->getPluginManager()->isPluginAllowed($package->getName(), false);
}
diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php
index cdefbfa43..509a33e1a 100644
--- a/src/Composer/Package/BasePackage.php
+++ b/src/Composer/Package/BasePackage.php
@@ -134,7 +134,12 @@ abstract class BasePackage implements PackageInterface
public function setRepository(RepositoryInterface $repository): void
{
if ($this->repository && $repository !== $this->repository) {
- throw new \LogicException('A package can only be added to one repository');
+ throw new \LogicException(sprintf(
+ 'Package "%s" cannot be added to repository "%s" as it is already in repository "%s".',
+ $this->getPrettyName(),
+ $repository->getRepoName(),
+ $this->repository->getRepoName()
+ ));
}
$this->repository = $repository;
}
diff --git a/src/Composer/Plugin/PluginBlockedException.php b/src/Composer/Plugin/PluginBlockedException.php
new file mode 100644
index 000000000..04a8db005
--- /dev/null
+++ b/src/Composer/Plugin/PluginBlockedException.php
@@ -0,0 +1,19 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Plugin;
+
+use UnexpectedValueException;
+
+class PluginBlockedException extends UnexpectedValueException
+{
+}
diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php
index 36ca8a318..3f91832e5 100644
--- a/src/Composer/Plugin/PluginManager.php
+++ b/src/Composer/Plugin/PluginManager.php
@@ -48,7 +48,7 @@ class PluginManager
protected $globalComposer;
/** @var VersionParser */
protected $versionParser;
- /** @var bool */
+ /** @var bool|'local'|'global' */
protected $disablePlugins = false;
/** @var array */
@@ -69,7 +69,10 @@ class PluginManager
/** @var int */
private static $classCounter = 0;
- public function __construct(IOInterface $io, Composer $composer, PartialComposer $globalComposer = null, bool $disablePlugins = false)
+ /**
+ * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
+ */
+ public function __construct(IOInterface $io, Composer $composer, PartialComposer $globalComposer = null, $disablePlugins = false)
{
$this->io = $io;
$this->composer = $composer;
@@ -87,15 +90,13 @@ class PluginManager
*/
public function loadInstalledPlugins(): void
{
- if ($this->disablePlugins) {
- return;
+ if (!$this->arePluginsDisabled('local')) {
+ $repo = $this->composer->getRepositoryManager()->getLocalRepository();
+ $this->loadRepository($repo, false);
}
- $repo = $this->composer->getRepositoryManager()->getLocalRepository();
- $globalRepo = $this->globalComposer !== null ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
- $this->loadRepository($repo, false);
- if ($globalRepo) {
- $this->loadRepository($globalRepo, true);
+ if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) {
+ $this->loadRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true);
}
}
@@ -106,13 +107,12 @@ class PluginManager
*/
public function deactivateInstalledPlugins(): void
{
- if ($this->disablePlugins) {
- return;
+ if (!$this->arePluginsDisabled('local')) {
+ $repo = $this->composer->getRepositoryManager()->getLocalRepository();
+ $this->deactivateRepository($repo, false);
}
- $repo = $this->composer->getRepositoryManager()->getLocalRepository();
- $this->deactivateRepository($repo, false);
- if ($this->globalComposer !== null) {
+ if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) {
$this->deactivateRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true);
}
}
@@ -151,7 +151,7 @@ class PluginManager
*/
public function registerPackage(PackageInterface $package, bool $failOnMissingClasses = false, bool $isGlobalPlugin = false): void
{
- if ($this->disablePlugins) {
+ if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) {
return;
}
@@ -310,10 +310,6 @@ class PluginManager
*/
public function deactivatePackage(PackageInterface $package): void
{
- if ($this->disablePlugins) {
- return;
- }
-
if (!isset($this->registeredPlugins[$package->getName()])) {
return;
}
@@ -341,10 +337,6 @@ class PluginManager
*/
public function uninstallPackage(PackageInterface $package): void
{
- if ($this->disablePlugins) {
- return;
- }
-
if (!isset($this->registeredPlugins[$package->getName()])) {
return;
}
@@ -384,6 +376,10 @@ class PluginManager
*/
public function addPlugin(PluginInterface $plugin, bool $isGlobalPlugin = false, PackageInterface $sourcePackage = null): void
{
+ if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) {
+ return;
+ }
+
if ($sourcePackage === null) {
trigger_error('Calling PluginManager::addPlugin without $sourcePackage is deprecated, if you are using this please get in touch with us to explain the use case', E_USER_DEPRECATED);
} elseif (!$this->isPluginAllowed($sourcePackage->getName(), $isGlobalPlugin)) {
@@ -676,11 +672,12 @@ class PluginManager
/**
* @internal
*
+ * @param 'local'|'global' $type
* @return bool
*/
- public function arePluginsDisabled()
+ public function arePluginsDisabled($type)
{
- return $this->disablePlugins;
+ return $this->disablePlugins === true || $this->disablePlugins === $type;
}
/**
@@ -769,7 +766,7 @@ class PluginManager
}
}
- throw new \UnexpectedValueException(
+ throw new PluginBlockedException(
$package.($isGlobalPlugin ? ' (installed globally)' : '').' contains a Composer plugin which is blocked by your allow-plugins config. You may add it to the list if you consider it safe.'.PHP_EOL.
'You can run "composer '.($isGlobalPlugin ? 'global ' : '').'config --no-plugins allow-plugins.'.$package.' [true|false]" to enable it (true) or disable it explicitly and suppress this exception (false)'.PHP_EOL.
'See https://getcomposer.org/allow-plugins'