From eb2aa1483035a52cd518f67197fc5a92684c1ba7 Mon Sep 17 00:00:00 2001 From: nevvermind Date: Tue, 2 Jun 2015 02:21:06 +0100 Subject: [PATCH 1/5] Make plugins have actual constraints instead of fixed versions Instead of developing plugins against a single, fixed Plugin API version - `"composer-plugin-api": "1.0.0"`, this change will allow plugin developers to use versions like `"composer-plugin-api": "~1.1"` or `"composer-plugin-api": ">=2.1 <3.0"`, aka actual Composer-compatible constraints. Only the "1.0", "1.0.0" and "1.0.0" Plugin API versions will be regarded as BC versions, and internally converted to "^1.0"; every other declared version string will be kept as it is. Because of this new constraint flexibility, plugin version mismatches will be skipped, which means those plugin will NOT be registered to the system. Previously, a mismatch triggered a warning, but plugins were still registered. --- src/Composer/Config.php | 11 ++ .../Package/Version/VersionParser.php | 16 ++ src/Composer/Plugin/PluginInterface.php | 1 + src/Composer/Plugin/PluginManager.php | 12 +- .../Repository/PlatformRepository.php | 27 +++- .../Package/Version/VersionParserTest.php | 67 ++++++++ .../Fixtures/plugin-v5/Installer/Plugin.php | 14 ++ .../Plugin/Fixtures/plugin-v5/composer.json | 12 ++ .../Fixtures/plugin-v6/Installer/Plugin.php | 14 ++ .../Plugin/Fixtures/plugin-v6/composer.json | 12 ++ .../Fixtures/plugin-v7/Installer/Plugin.php | 14 ++ .../Plugin/Fixtures/plugin-v7/composer.json | 12 ++ .../Test/Plugin/PluginInstallerTest.php | 149 +++++++++++++++++- 13 files changed, 350 insertions(+), 11 deletions(-) create mode 100644 tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin.php create mode 100644 tests/Composer/Test/Plugin/Fixtures/plugin-v5/composer.json create mode 100644 tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin.php create mode 100644 tests/Composer/Test/Plugin/Fixtures/plugin-v6/composer.json create mode 100644 tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin.php create mode 100644 tests/Composer/Test/Plugin/Fixtures/plugin-v7/composer.json diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 828b98e75..4d0ac5858 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -13,6 +13,7 @@ namespace Composer; use Composer\Config\ConfigSourceInterface; +use Composer\Plugin\PluginInterface; /** * @author Jordi Boggiano @@ -340,4 +341,14 @@ class Config return false; } + + /** + * Returns the version of the internal composer-plugin-api package. + * + * @return string + */ + public function getPluginApiVersion() + { + return PluginInterface::PLUGIN_API_VERSION; + } } diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 3a214f150..6a3ec65bb 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -227,12 +227,28 @@ class VersionParser } else { $parsedConstraint = $this->parseConstraints($constraint); } + + // if the required Plugin API version is exactly "1.0.0", convert it to "^1.0", to keep BC + if ('composer-plugin-api' === $target && $this->isOldStylePluginApiVersion($constraint)) { + $parsedConstraint = $this->parseConstraints('^1.0'); + } + $res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint); } return $res; } + /** + * @param string $requiredPluginApiVersion + * @return bool + */ + private function isOldStylePluginApiVersion($requiredPluginApiVersion) + { + // catch "1.0", "1.0.0", "1.0.0.0" etc. + return (bool) preg_match('#^1(\.0)+$#', trim($requiredPluginApiVersion)); + } + /** * Parses as constraint string into LinkConstraint objects * diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php index dea5828c1..34fc0957a 100644 --- a/src/Composer/Plugin/PluginInterface.php +++ b/src/Composer/Plugin/PluginInterface.php @@ -26,6 +26,7 @@ interface PluginInterface * Version number of the fake composer-plugin-api package * * @var string + * @deprecated Use \Composer\Config::getPluginApiVersion() instead. */ const PLUGIN_API_VERSION = '1.0.0'; diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 14021dc45..29bdd2e52 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -122,6 +122,7 @@ class PluginManager foreach ($package->getRequires() as $link) { /** @var Link $link */ if ('composer-plugin-api' === $link->getTarget()) { $requiresComposer = $link->getConstraint(); + break; } } @@ -129,14 +130,17 @@ class PluginManager throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); } - if (!$requiresComposer->matches(new VersionConstraint('==', $this->versionParser->normalize(PluginInterface::PLUGIN_API_VERSION)))) { - $this->io->writeError("The plugin ".$package->getName()." requires a version of composer-plugin-api that does not match your composer installation. You may need to run composer update with the '--no-plugins' option."); + $currPluginApiVersion = $this->composer->getConfig()->getPluginApiVersion(); + $currPluginApiConstraint = new VersionConstraint('==', $this->versionParser->normalize($currPluginApiVersion)); + if (!$requiresComposer->matches($currPluginApiConstraint)) { + $this->io->writeError('The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); + continue; } $this->registerPackage($package); - } + // Backward compatibility - if ('composer-installer' === $package->getType()) { + } elseif ('composer-installer' === $package->getType()) { $this->registerPackage($package); } } diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 4afd3d4e9..976cd5d04 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -12,6 +12,7 @@ namespace Composer\Repository; +use Composer\Config; use Composer\Package\PackageInterface; use Composer\Package\CompletePackage; use Composer\Package\Version\VersionParser; @@ -33,6 +34,11 @@ class PlatformRepository extends ArrayRepository */ private $overrides; + /** + * @var Config + */ + private $config; + public function __construct(array $packages = array(), array $overrides = array()) { parent::__construct($packages); @@ -62,7 +68,7 @@ class PlatformRepository extends ArrayRepository parent::addPackage($package); } - $prettyVersion = PluginInterface::PLUGIN_API_VERSION; + $prettyVersion = $this->getConfig()->getPluginApiVersion(); $version = $versionParser->normalize($prettyVersion); $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion); $composerPluginApi->setDescription('The Composer Plugin API'); @@ -210,4 +216,23 @@ class PlatformRepository extends ArrayRepository { return 'ext-' . str_replace(' ', '-', $name); } + + /** + * @param Config $config + */ + public function setConfig(Config $config) + { + $this->config = $config; + } + + /** + * @return Config + */ + public function getConfig() + { + if (!$this->config) { + $this->config = new Config; + } + return $this->config; + } } diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index ceb959051..87416e7eb 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -12,6 +12,7 @@ namespace Composer\Test\Package\Version; +use Composer\Package\Link; use Composer\Package\Version\VersionParser; use Composer\Package\LinkConstraint\MultiConstraint; use Composer\Package\LinkConstraint\VersionConstraint; @@ -513,4 +514,70 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase array('RC', '2.0.0rc1') ); } + + public function oldStylePluginApiVersions() + { + return array( + array('1.0'), + array('1.0.0'), + array('1.0.0.0'), + ); + } + + public function newStylePluginApiVersions() + { + return array( + array('1'), + array('=1.0.0'), + array('==1.0'), + array('~1.0.0'), + array('*'), + array('3.0.*'), + array('@stable'), + array('1.0.0@stable'), + array('^5.1'), + array('>=1.0.0 <2.5'), + array('x'), + array('1.0.0-dev'), + ); + } + + /** + * @dataProvider oldStylePluginApiVersions + */ + public function testOldStylePluginApiVersionGetsConvertedIntoAnotherConstraintToKeepBc($apiVersion) + { + $parser = new VersionParser; + + /** @var Link[] $links */ + $links = $parser->parseLinks('Plugin', '9.9.9', '', array('composer-plugin-api' => $apiVersion)); + + $this->assertArrayHasKey('composer-plugin-api', $links); + $this->assertSame('^1.0', $links['composer-plugin-api']->getConstraint()->getPrettyString()); + } + + /** + * @dataProvider newStylePluginApiVersions + */ + public function testNewStylePluginApiVersionAreKeptAsDeclared($apiVersion) + { + $parser = new VersionParser; + + /** @var Link[] $links */ + $links = $parser->parseLinks('Plugin', '9.9.9', '', array('composer-plugin-api' => $apiVersion)); + + $this->assertArrayHasKey('composer-plugin-api', $links); + $this->assertSame($apiVersion, $links['composer-plugin-api']->getConstraint()->getPrettyString()); + } + + public function testPluginApiVersionDoesSupportSelfVersion() + { + $parser = new VersionParser; + + /** @var Link[] $links */ + $links = $parser->parseLinks('Plugin', '6.6.6', '', array('composer-plugin-api' => 'self.version')); + + $this->assertArrayHasKey('composer-plugin-api', $links); + $this->assertSame('6.6.6', $links['composer-plugin-api']->getConstraint()->getPrettyString()); + } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin.php new file mode 100644 index 000000000..2dae1b48b --- /dev/null +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin.php @@ -0,0 +1,14 @@ +=3.0.0 <5.5" + } +} diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index a2090082f..ba45bad0b 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -15,29 +15,62 @@ namespace Composer\Test\Installer; use Composer\Composer; use Composer\Config; use Composer\Installer\PluginInstaller; +use Composer\Package\CompletePackage; use Composer\Package\Loader\JsonLoader; use Composer\Package\Loader\ArrayLoader; use Composer\Plugin\PluginManager; use Composer\Autoload\AutoloadGenerator; +use Composer\TestCase; use Composer\Util\Filesystem; -class PluginInstallerTest extends \PHPUnit_Framework_TestCase +class PluginInstallerTest extends TestCase { + /** + * @var Composer + */ protected $composer; - protected $packages; - protected $im; + + /** + * @var PluginManager + */ protected $pm; - protected $repository; - protected $io; + + /** + * @var AutoloadGenerator + */ protected $autoloadGenerator; + + /** + * @var CompletePackage[] + */ + protected $packages; + + /** + * @var string + */ protected $directory; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $im; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $repository; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $io; + protected function setUp() { $loader = new JsonLoader(new ArrayLoader()); $this->packages = array(); $this->directory = sys_get_temp_dir() . '/' . uniqid(); - for ($i = 1; $i <= 4; $i++) { + for ($i = 1; $i <= 7; $i++) { $filename = '/Fixtures/plugin-v'.$i.'/composer.json'; mkdir(dirname($this->directory . $filename), 0777, true); $this->packages[] = $loader->load(__DIR__ . $filename); @@ -181,4 +214,108 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $this->assertCount(1, $plugins); $this->assertEquals('installer-v1', $plugins[0]->version); } + + /** + * @param string $newPluginApiVersion + * @param CompletePackage[] $plugins + */ + private function setPluginApiVersionWithPlugins($newPluginApiVersion, array $plugins = array()) + { + // reset the plugin manager's installed plugins + $this->pm = new PluginManager($this->io, $this->composer); + + /** @var \PHPUnit_Framework_MockObject_MockObject $config */ + $config = $this->getMock('Composer\Config', array('getPluginApiVersion')); + + // mock Config to return whatever Plugin API version we wish + $config->expects($this->any()) + ->method('getPluginApiVersion') + ->will($this->returnValue($newPluginApiVersion)); + + // transfer the defaults in our Config mock and set it + $config->merge($this->composer->getConfig()->raw()); + $this->composer->setConfig($config); + + $plugApiInternalPackage = $this->getPackage( + 'composer-plugin-api', + $newPluginApiVersion, + 'Composer\Package\CompletePackage' + ); + + // Add the plugins to the repo along with the internal Plugin package on which they all rely. + $this->repository + ->expects($this->any()) + ->method('getPackages') + ->will($this->returnCallback(function() use($plugApiInternalPackage, $plugins) { + return array_merge(array($plugApiInternalPackage), $plugins); + })); + + $this->pm->loadInstalledPlugins(); + } + + public function testOldPluginVersionStyleWorksWithAPIUntil199() + { + $pluginsWithOldStyleAPIVersions = array( + $this->packages[0], + $this->packages[1], + $this->packages[2], + ); + + $this->setPluginApiVersionWithPlugins('1.0.0', $pluginsWithOldStyleAPIVersions); + $this->assertCount(3, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('1.9.9', $pluginsWithOldStyleAPIVersions); + $this->assertCount(3, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('2.0.0-dev', $pluginsWithOldStyleAPIVersions); + $this->assertCount(0, $this->pm->getPlugins()); + } + + public function testStarPluginVersionWorksWithAnyAPIVersion() + { + $starVersionPlugin = array($this->packages[4]); + + $this->setPluginApiVersionWithPlugins('1.0.0', $starVersionPlugin); + $this->assertCount(1, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('1.9.9', $starVersionPlugin); + $this->assertCount(1, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('2.0.0-dev', $starVersionPlugin); + $this->assertCount(1, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('100.0.0-stable', $starVersionPlugin); + $this->assertCount(1, $this->pm->getPlugins()); + } + + public function testPluginConstraintWorksOnlyWithCertainAPIVersion() + { + $pluginWithApiConstraint = array($this->packages[5]); + + $this->setPluginApiVersionWithPlugins('1.0.0', $pluginWithApiConstraint); + $this->assertCount(0, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('1.1.9', $pluginWithApiConstraint); + $this->assertCount(0, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('1.2.0', $pluginWithApiConstraint); + $this->assertCount(1, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('1.9.9', $pluginWithApiConstraint); + $this->assertCount(1, $this->pm->getPlugins()); + } + + public function testPluginRangeConstraintsWorkOnlyWithCertainAPIVersion() + { + $pluginWithApiConstraint = array($this->packages[6]); + + $this->setPluginApiVersionWithPlugins('1.0.0', $pluginWithApiConstraint); + $this->assertCount(0, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('3.0.0', $pluginWithApiConstraint); + $this->assertCount(1, $this->pm->getPlugins()); + + $this->setPluginApiVersionWithPlugins('5.5.0', $pluginWithApiConstraint); + $this->assertCount(0, $this->pm->getPlugins()); + } } From 3032f0a538d74ca91e0366644a94134e77cfb5ce Mon Sep 17 00:00:00 2001 From: nevvermind Date: Tue, 2 Jun 2015 17:39:02 +0100 Subject: [PATCH 2/5] Refactor based on code review - Move the version api getter to the PluginManager And make it such that it can be mocked, but not pollute the public interface. That means "protected" visibility. - The plugin api version constant should still be used throughout the code. - Use different fixtures class names - Use regex possessive quantifiers for performance - Use full words for readability --- src/Composer/Config.php | 10 ------- .../Package/Version/VersionParser.php | 2 +- src/Composer/Plugin/PluginInterface.php | 1 - src/Composer/Plugin/PluginManager.php | 19 +++++++++++--- .../Repository/PlatformRepository.php | 26 +------------------ .../Installer/{Plugin.php => Plugin5.php} | 2 +- .../Plugin/Fixtures/plugin-v5/composer.json | 2 +- .../Installer/{Plugin.php => Plugin6.php} | 2 +- .../Plugin/Fixtures/plugin-v6/composer.json | 4 +-- .../Installer/{Plugin.php => Plugin7.php} | 2 +- .../Plugin/Fixtures/plugin-v7/composer.json | 4 +-- .../Test/Plugin/PluginInstallerTest.php | 20 ++++++-------- 12 files changed, 33 insertions(+), 61 deletions(-) rename tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/{Plugin.php => Plugin5.php} (83%) rename tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/{Plugin.php => Plugin6.php} (83%) rename tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/{Plugin.php => Plugin7.php} (83%) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 4d0ac5858..ba8e2bdc7 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -341,14 +341,4 @@ class Config return false; } - - /** - * Returns the version of the internal composer-plugin-api package. - * - * @return string - */ - public function getPluginApiVersion() - { - return PluginInterface::PLUGIN_API_VERSION; - } } diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 6a3ec65bb..a9b78d64d 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -246,7 +246,7 @@ class VersionParser private function isOldStylePluginApiVersion($requiredPluginApiVersion) { // catch "1.0", "1.0.0", "1.0.0.0" etc. - return (bool) preg_match('#^1(\.0)+$#', trim($requiredPluginApiVersion)); + return (bool) preg_match('#^1(\.0)++$#', trim($requiredPluginApiVersion)); } /** diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php index 34fc0957a..dea5828c1 100644 --- a/src/Composer/Plugin/PluginInterface.php +++ b/src/Composer/Plugin/PluginInterface.php @@ -26,7 +26,6 @@ interface PluginInterface * Version number of the fake composer-plugin-api package * * @var string - * @deprecated Use \Composer\Config::getPluginApiVersion() instead. */ const PLUGIN_API_VERSION = '1.0.0'; diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 29bdd2e52..a7d91d5b3 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -130,10 +130,11 @@ class PluginManager throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); } - $currPluginApiVersion = $this->composer->getConfig()->getPluginApiVersion(); - $currPluginApiConstraint = new VersionConstraint('==', $this->versionParser->normalize($currPluginApiVersion)); - if (!$requiresComposer->matches($currPluginApiConstraint)) { - $this->io->writeError('The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); + $currentPluginApiVersion = $this->getPluginApiVersion(); + $currentPluginApiConstraint = new VersionConstraint('==', $this->versionParser->normalize($currentPluginApiVersion)); + + if (!$requiresComposer->matches($currentPluginApiConstraint)) { + $this->io->writeError('The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); continue; } @@ -276,4 +277,14 @@ class PluginManager return $this->globalComposer->getInstallationManager()->getInstallPath($package); } + + /** + * Returns the version of the internal composer-plugin-api package. + * + * @return string + */ + protected function getPluginApiVersion() + { + return PluginInterface::PLUGIN_API_VERSION; + } } diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 976cd5d04..7ef11786b 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -34,11 +34,6 @@ class PlatformRepository extends ArrayRepository */ private $overrides; - /** - * @var Config - */ - private $config; - public function __construct(array $packages = array(), array $overrides = array()) { parent::__construct($packages); @@ -68,7 +63,7 @@ class PlatformRepository extends ArrayRepository parent::addPackage($package); } - $prettyVersion = $this->getConfig()->getPluginApiVersion(); + $prettyVersion = PluginInterface::PLUGIN_API_VERSION; $version = $versionParser->normalize($prettyVersion); $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion); $composerPluginApi->setDescription('The Composer Plugin API'); @@ -216,23 +211,4 @@ class PlatformRepository extends ArrayRepository { return 'ext-' . str_replace(' ', '-', $name); } - - /** - * @param Config $config - */ - public function setConfig(Config $config) - { - $this->config = $config; - } - - /** - * @return Config - */ - public function getConfig() - { - if (!$this->config) { - $this->config = new Config; - } - return $this->config; - } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php similarity index 83% rename from tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin.php rename to tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php index 2dae1b48b..a2ac37bc5 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php @@ -6,7 +6,7 @@ use Composer\Composer; use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; -class Plugin implements PluginInterface +class Plugin5 implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v5/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/composer.json index e52970367..7885cd6fd 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v5/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/composer.json @@ -4,7 +4,7 @@ "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, "extra": { - "class": "Installer\\Plugin" + "class": "Installer\\Plugin5" }, "require": { "composer-plugin-api": "*" diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php similarity index 83% rename from tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin.php rename to tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php index 2dae1b48b..e46c0fcb0 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php @@ -6,7 +6,7 @@ use Composer\Composer; use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; -class Plugin implements PluginInterface +class Plugin6 implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v6/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v6/composer.json index ca8108021..b620edee0 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v6/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v6/composer.json @@ -1,10 +1,10 @@ { - "name": "plugin-v5", + "name": "plugin-v6", "version": "1.0.0", "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, "extra": { - "class": "Installer\\Plugin" + "class": "Installer\\Plugin6" }, "require": { "composer-plugin-api": "~1.2" diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php similarity index 83% rename from tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin.php rename to tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php index 2dae1b48b..5560a6047 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php @@ -6,7 +6,7 @@ use Composer\Composer; use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; -class Plugin implements PluginInterface +class Plugin7 implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v7/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v7/composer.json index c03fca1c8..ee8627cb1 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v7/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v7/composer.json @@ -1,10 +1,10 @@ { - "name": "plugin-v5", + "name": "plugin-v7", "version": "1.0.0", "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, "extra": { - "class": "Installer\\Plugin" + "class": "Installer\\Plugin7" }, "require": { "composer-plugin-api": ">=3.0.0 <5.5" diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index ba45bad0b..8bebe7d28 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -222,19 +222,15 @@ class PluginInstallerTest extends TestCase private function setPluginApiVersionWithPlugins($newPluginApiVersion, array $plugins = array()) { // reset the plugin manager's installed plugins - $this->pm = new PluginManager($this->io, $this->composer); + $this->pm = $this->getMockBuilder('Composer\Plugin\PluginManager') + ->setMethods(array('getPluginApiVersion')) + ->setConstructorArgs(array($this->io, $this->composer)) + ->getMock(); - /** @var \PHPUnit_Framework_MockObject_MockObject $config */ - $config = $this->getMock('Composer\Config', array('getPluginApiVersion')); - - // mock Config to return whatever Plugin API version we wish - $config->expects($this->any()) - ->method('getPluginApiVersion') - ->will($this->returnValue($newPluginApiVersion)); - - // transfer the defaults in our Config mock and set it - $config->merge($this->composer->getConfig()->raw()); - $this->composer->setConfig($config); + // mock the Plugin API version + $this->pm->expects($this->any()) + ->method('getPluginApiVersion') + ->will($this->returnValue($newPluginApiVersion)); $plugApiInternalPackage = $this->getPackage( 'composer-plugin-api', From 00da5945ec2ce22ba3f77b784c394f491a291ba8 Mon Sep 17 00:00:00 2001 From: nevvermind Date: Tue, 2 Jun 2015 19:09:57 +0100 Subject: [PATCH 3/5] Update docs --- doc/articles/plugins.md | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index 9da3badad..367c74c83 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -16,7 +16,7 @@ specific logic. ## Creating a Plugin -A plugin is a regular composer package which ships its code as part of the +A plugin is a regular Composer package which ships its code as part of the package and may also depend on further packages. ### Plugin Package @@ -24,23 +24,36 @@ package and may also depend on further packages. The package file is the same as any other package file but with the following requirements: -1. the [type][1] attribute must be `composer-plugin`. -2. the [extra][2] attribute must contain an element `class` defining the +1. The [type][1] attribute must be `composer-plugin`. +2. The [extra][2] attribute must contain an element `class` defining the class name of the plugin (including namespace). If a package contains - multiple plugins this can be array of class names. + multiple plugins, this can be array of class names. +3. You must require the special package called `composer-plugin-api` + to define which Plugin API versions your plugin is compatible with. -Additionally you must require the special package called `composer-plugin-api` -to define which composer API versions your plugin is compatible with. The -current composer plugin API version is 1.0.0. +The required version of the `composer-plugin-api` follows the same [rules][7] +as a normal package's, except for the `1.0`, `1.0.0` and `1.0.0.0` _exact_ +values. In only these three cases, Composer will assume your plugin +actually meant `^1.0` instead. This was introduced to maintain BC with +the old style of declaring the Plugin API version. + +In other words, `"require": { "composer-plugin-api": "1.0.0" }` means +`"require": { "composer-plugin-api": "^1.0" }`. -For example +The current composer plugin API version is 1.0.0. + +An example of a valid plugin `composer.json` file (with the autoloading +part omitted): ```json { "name": "my/plugin-package", "type": "composer-plugin", "require": { - "composer-plugin-api": "1.0.0" + "composer-plugin-api": "~1.0" + }, + "extra": { + "class": "My\\Plugin" } } ``` @@ -149,3 +162,4 @@ local project plugins are loaded. [4]: https://github.com/composer/composer/blob/master/src/Composer/Composer.php [5]: https://github.com/composer/composer/blob/master/src/Composer/IO/IOInterface.php [6]: https://github.com/composer/composer/blob/master/src/Composer/EventDispatcher/EventSubscriberInterface.php +[7]: ../01-basic-usage.md#package-versions From 994b1324bcdcd7c22c2c766c58dad562a06328ff Mon Sep 17 00:00:00 2001 From: nevvermind Date: Tue, 2 Jun 2015 19:58:21 +0100 Subject: [PATCH 4/5] fix alignment --- src/Composer/Plugin/PluginManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index a7d91d5b3..833b3e29b 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -130,7 +130,7 @@ class PluginManager throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); } - $currentPluginApiVersion = $this->getPluginApiVersion(); + $currentPluginApiVersion = $this->getPluginApiVersion(); $currentPluginApiConstraint = new VersionConstraint('==', $this->versionParser->normalize($currentPluginApiVersion)); if (!$requiresComposer->matches($currentPluginApiConstraint)) { From 4fd9a3ceeeadb46ca65e71f9d7b56215b5834ca6 Mon Sep 17 00:00:00 2001 From: nevvermind Date: Tue, 2 Jun 2015 19:59:38 +0100 Subject: [PATCH 5/5] compare against correct format --- src/Composer/Package/Version/VersionParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index a9b78d64d..41f9a72a0 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -229,7 +229,7 @@ class VersionParser } // if the required Plugin API version is exactly "1.0.0", convert it to "^1.0", to keep BC - if ('composer-plugin-api' === $target && $this->isOldStylePluginApiVersion($constraint)) { + if ('composer-plugin-api' === strtolower($target) && $this->isOldStylePluginApiVersion($constraint)) { $parsedConstraint = $this->parseConstraints('^1.0'); }