Add plugin callbacks for deactivation and uninstall, fixes #3000
parent
1b7e957cc1
commit
3fc9ede24b
|
@ -70,7 +70,7 @@ class PluginInstaller extends LibraryInstaller
|
|||
$this->composer->getPluginManager()->registerPackage($package, true);
|
||||
} catch (\Exception $e) {
|
||||
// Rollback installation
|
||||
$this->io->writeError('Plugin installation failed, rolling back');
|
||||
$this->io->writeError('Plugin initialization failed, uninstalling plugin');
|
||||
parent::uninstall($repo, $package);
|
||||
throw $e;
|
||||
}
|
||||
|
@ -81,12 +81,22 @@ class PluginInstaller extends LibraryInstaller
|
|||
*/
|
||||
public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
|
||||
{
|
||||
$extra = $target->getExtra();
|
||||
if (empty($extra['class'])) {
|
||||
throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
|
||||
}
|
||||
|
||||
parent::update($repo, $initial, $target);
|
||||
$this->composer->getPluginManager()->registerPackage($target, true);
|
||||
|
||||
try {
|
||||
$this->composer->getPluginManager()->deactivatePackage($initial, true);
|
||||
$this->composer->getPluginManager()->registerPackage($target, true);
|
||||
} catch (\Exception $e) {
|
||||
// Rollback installation
|
||||
$this->io->writeError('Plugin initialization failed, uninstalling plugin');
|
||||
parent::uninstall($repo, $target);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||
{
|
||||
$this->composer->getPluginManager()->uninstallPackage($package, true);
|
||||
parent::uninstall($repo, $package);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,4 +36,22 @@ interface PluginInterface
|
|||
* @param IOInterface $io
|
||||
*/
|
||||
public function activate(Composer $composer, IOInterface $io);
|
||||
|
||||
/**
|
||||
* Remove any hooks from Composer
|
||||
*
|
||||
* @param Composer $composer
|
||||
* @param IOInterface $io
|
||||
*/
|
||||
public function deactivate(Composer $composer, IOInterface $io);
|
||||
|
||||
/**
|
||||
* Prepare the plugin to be uninstalled
|
||||
*
|
||||
* This will be called after deactivate
|
||||
*
|
||||
* @param Composer $composer
|
||||
* @param IOInterface $io
|
||||
*/
|
||||
public function uninstall(Composer $composer, IOInterface $io);
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ class PluginManager
|
|||
|
||||
$oldInstallerPlugin = ($package->getType() === 'composer-installer');
|
||||
|
||||
if (in_array($package->getName(), $this->registeredPlugins)) {
|
||||
if (isset($this->registeredPlugins[$package->getName()])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -200,16 +200,82 @@ class PluginManager
|
|||
if ($oldInstallerPlugin) {
|
||||
$installer = new $class($this->io, $this->composer);
|
||||
$this->composer->getInstallationManager()->addInstaller($installer);
|
||||
$this->registeredPlugins[$package->getName()] = $installer;
|
||||
} elseif (class_exists($class)) {
|
||||
$plugin = new $class();
|
||||
$this->addPlugin($plugin);
|
||||
$this->registeredPlugins[] = $package->getName();
|
||||
$this->registeredPlugins[$package->getName()] = $plugin;
|
||||
} elseif ($failOnMissingClasses) {
|
||||
throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates a plugin package
|
||||
*
|
||||
* If it's of type composer-installer it is unregistered from the installers
|
||||
* instead for BC
|
||||
*
|
||||
* @param PackageInterface $package
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function deactivatePackage(PackageInterface $package)
|
||||
{
|
||||
if ($this->disablePlugins) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oldInstallerPlugin = ($package->getType() === 'composer-installer');
|
||||
|
||||
if (!isset($this->registeredPlugins[$package->getName()])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($oldInstallerPlugin) {
|
||||
$installer = $this->registeredPlugins[$package->getName()];
|
||||
unset($this->registeredPlugins[$package->getName()]);
|
||||
$this->composer->getInstallationManager()->removeInstaller($installer);
|
||||
} else {
|
||||
$plugin = $this->registeredPlugins[$package->getName()];
|
||||
unset($this->registeredPlugins[$package->getName()]);
|
||||
$this->removePlugin($plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall a plugin package
|
||||
*
|
||||
* If it's of type composer-installer it is unregistered from the installers
|
||||
* instead for BC
|
||||
*
|
||||
* @param PackageInterface $package
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function uninstallPackage(PackageInterface $package)
|
||||
{
|
||||
if ($this->disablePlugins) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oldInstallerPlugin = ($package->getType() === 'composer-installer');
|
||||
|
||||
if (!isset($this->registeredPlugins[$package->getName()])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($oldInstallerPlugin) {
|
||||
$this->deactivatePackage($package);
|
||||
} else {
|
||||
$plugin = $this->registeredPlugins[$package->getName()];
|
||||
unset($this->registeredPlugins[$package->getName()]);
|
||||
$this->removePlugin($plugin);
|
||||
$this->uninstallPlugin($plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the internal composer-plugin-api package.
|
||||
*
|
||||
|
@ -240,6 +306,44 @@ class PluginManager
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a plugin, deactivates it and removes any listener the plugin has set on the plugin instance
|
||||
*
|
||||
* Ideally plugin packages should be deactivated via deactivatePackage, but if you use Composer
|
||||
* programmatically and want to deregister a plugin class directly this is a valid way
|
||||
* to do it.
|
||||
*
|
||||
* @param PluginInterface $plugin plugin instance
|
||||
*/
|
||||
public function removePlugin(PluginInterface $plugin)
|
||||
{
|
||||
$index = array_search($plugin, $this->plugins, true);
|
||||
if ($index === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->writeError('Unloading plugin '.get_class($plugin), true, IOInterface::DEBUG);
|
||||
unset($this->plugins[$index]);
|
||||
$plugin->deactivate($this->composer, $this->io);
|
||||
|
||||
$this->composer->getEventDispatcher()->removeListener($plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies a plugin it is being uninstalled and should clean up
|
||||
*
|
||||
* Ideally plugin packages should be uninstalled via uninstallPackage, but if you use Composer
|
||||
* programmatically and want to deregister a plugin class directly this is a valid way
|
||||
* to do it.
|
||||
*
|
||||
* @param PluginInterface $plugin plugin instance
|
||||
*/
|
||||
public function uninstallPlugin(PluginInterface $plugin)
|
||||
{
|
||||
$this->io->writeError('Uninstalling plugin '.get_class($plugin), true, IOInterface::DEBUG);
|
||||
$plugin->uninstall($this->composer, $this->io);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all plugins and installers from a repository
|
||||
*
|
||||
|
|
|
@ -12,5 +12,16 @@ class Plugin implements PluginInterface
|
|||
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v1');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v1');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v1');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,16 @@ class Plugin2 implements PluginInterface
|
|||
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v2');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v2');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v2');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,16 @@ class Plugin2 implements PluginInterface
|
|||
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v3');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v3');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v3');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,16 @@ class Plugin1 implements PluginInterface
|
|||
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v4-plugin1');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v4-plugin1');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v4-plugin1');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,16 @@ class Plugin2 implements PluginInterface
|
|||
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v4-plugin2');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v4-plugin2');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v4-plugin2');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,5 +10,16 @@ class Plugin5 implements PluginInterface
|
|||
{
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v5');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v5');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v5');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,5 +10,16 @@ class Plugin6 implements PluginInterface
|
|||
{
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v6');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v6');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v6');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,5 +10,16 @@ class Plugin7 implements PluginInterface
|
|||
{
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v7');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v7');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v7');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,17 @@ class Plugin8 implements PluginInterface, Capable
|
|||
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v8');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v8');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v8');
|
||||
}
|
||||
|
||||
public function getCapabilities()
|
||||
|
|
|
@ -14,5 +14,16 @@ class Plugin implements PluginInterface
|
|||
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('activate v9');
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('deactivate v9');
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$io->write('uninstall v9');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@ use Composer\Package\CompletePackage;
|
|||
use Composer\Package\Loader\JsonLoader;
|
||||
use Composer\Package\Loader\ArrayLoader;
|
||||
use Composer\Plugin\PluginManager;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Composer\IO\BufferIO;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Autoload\AutoloadGenerator;
|
||||
use Composer\Test\TestCase;
|
||||
use Composer\Util\Filesystem;
|
||||
|
@ -96,7 +99,7 @@ class PluginInstallerTest extends TestCase
|
|||
return __DIR__.'/Fixtures/'.$package->getPrettyName();
|
||||
}));
|
||||
|
||||
$this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
|
||||
$this->io = new BufferIO();
|
||||
|
||||
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
|
||||
$this->autoloadGenerator = new AutoloadGenerator($dispatcher);
|
||||
|
@ -108,6 +111,7 @@ class PluginInstallerTest extends TestCase
|
|||
$this->composer->setRepositoryManager($rm);
|
||||
$this->composer->setInstallationManager($im);
|
||||
$this->composer->setAutoloadGenerator($this->autoloadGenerator);
|
||||
$this->composer->setEventDispatcher(new EventDispatcher($this->composer, $this->io));
|
||||
|
||||
$this->pm = new PluginManager($this->io, $this->composer);
|
||||
$this->composer->setPluginManager($this->pm);
|
||||
|
@ -140,6 +144,7 @@ class PluginInstallerTest extends TestCase
|
|||
|
||||
$plugins = $this->pm->getPlugins();
|
||||
$this->assertEquals('installer-v1', $plugins[0]->version);
|
||||
$this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput());
|
||||
}
|
||||
|
||||
public function testInstallMultiplePlugins()
|
||||
|
@ -158,6 +163,7 @@ class PluginInstallerTest extends TestCase
|
|||
$this->assertEquals('installer-v4', $plugins[0]->version);
|
||||
$this->assertEquals('plugin2', $plugins[1]->name);
|
||||
$this->assertEquals('installer-v4', $plugins[1]->version);
|
||||
$this->assertEquals('activate v4-plugin1'.PHP_EOL.'activate v4-plugin2'.PHP_EOL, $this->io->getOutput());
|
||||
}
|
||||
|
||||
public function testUpgradeWithNewClassName()
|
||||
|
@ -176,7 +182,29 @@ class PluginInstallerTest extends TestCase
|
|||
$installer->update($this->repository, $this->packages[0], $this->packages[1]);
|
||||
|
||||
$plugins = $this->pm->getPlugins();
|
||||
$this->assertCount(1, $plugins);
|
||||
$this->assertEquals('installer-v2', $plugins[1]->version);
|
||||
$this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'activate v2'.PHP_EOL, $this->io->getOutput());
|
||||
}
|
||||
|
||||
public function testUninstall()
|
||||
{
|
||||
$this->repository
|
||||
->expects($this->once())
|
||||
->method('getPackages')
|
||||
->will($this->returnValue(array($this->packages[0])));
|
||||
$this->repository
|
||||
->expects($this->exactly(1))
|
||||
->method('hasPackage')
|
||||
->will($this->onConsecutiveCalls(true, false));
|
||||
$installer = new PluginInstaller($this->io, $this->composer);
|
||||
$this->pm->loadInstalledPlugins();
|
||||
|
||||
$installer->uninstall($this->repository, $this->packages[0]);
|
||||
|
||||
$plugins = $this->pm->getPlugins();
|
||||
$this->assertCount(0, $plugins);
|
||||
$this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'uninstall v1'.PHP_EOL, $this->io->getOutput());
|
||||
}
|
||||
|
||||
public function testUpgradeWithSameClassName()
|
||||
|
@ -196,6 +224,7 @@ class PluginInstallerTest extends TestCase
|
|||
|
||||
$plugins = $this->pm->getPlugins();
|
||||
$this->assertEquals('installer-v3', $plugins[1]->version);
|
||||
$this->assertEquals('activate v2'.PHP_EOL.'deactivate v2'.PHP_EOL.'activate v3'.PHP_EOL, $this->io->getOutput());
|
||||
}
|
||||
|
||||
public function testRegisterPluginOnlyOneTime()
|
||||
|
@ -213,6 +242,7 @@ class PluginInstallerTest extends TestCase
|
|||
$plugins = $this->pm->getPlugins();
|
||||
$this->assertCount(1, $plugins);
|
||||
$this->assertEquals('installer-v1', $plugins[0]->version);
|
||||
$this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue