From 1c04fab1d359de2f64d0709e83acf3c4bf51e2de Mon Sep 17 00:00:00 2001 From: Sander Visser Date: Sat, 7 Dec 2024 01:55:06 +0100 Subject: [PATCH] Fix plugin upgrade handling (#71) * Fix plugin upgrade handling --- ruleset.xml | 9 ++++++++- src/Plugin.php | 33 ++++++++++++++++++++++++++++----- tests/Unit/PluginTest.php | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/ruleset.xml b/ruleset.xml index a11f18f..0591ed0 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -9,7 +9,14 @@ - + + + + + + + + diff --git a/src/Plugin.php b/src/Plugin.php index cef68b5..0b202cb 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -21,12 +21,14 @@ use Composer\Plugin\Capability\CommandProvider as ComposerCommandProvider; use Composer\Plugin\Capable; use Composer\Plugin\PluginInterface; +use Composer\Script\Event; use Composer\Script\ScriptEvents; use Composer\Util\Filesystem as ComposerFileSystem; use ComposerLink\Actions\LinkPackages; use ComposerLink\Repository\Repository; use ComposerLink\Repository\RepositoryFactory; use RuntimeException; +use Throwable; class Plugin implements PluginInterface, Capable, EventSubscriberInterface { @@ -40,6 +42,15 @@ class Plugin implements PluginInterface, Capable, EventSubscriberInterface protected Composer $composer; + /** + * It can happen that activation doesn't work, this happens when this plugin is upgraded. + * Composer runs this file through an eval() with renamed class names, but all other classes + * in this library are still the old ones loaded in memory. + * + * We try to detect this, and skip the event callbacks if it happens + */ + protected bool $couldNotActivate = false; + public function __construct( ?ComposerFileSystem $filesystem = null, protected ?LinkPackages $linkPackages = null, @@ -73,10 +84,15 @@ public function activate(Composer $composer, IOInterface $io): void $io->debug("[ComposerLink]\tPlugin is activating"); $this->composer = $composer; - $this->initializeRepository(); - $this->initializeLinkedPackageFactory(); - $this->initializeLinkManager(); - $this->initializeLinkPackages(); + try { + $this->initializeRepository(); + $this->initializeLinkedPackageFactory(); + $this->initializeLinkManager(); + $this->initializeLinkPackages(); + } catch (Throwable $e) { + $io->debug("[ComposerLink]\tException: " . $e->getMessage()); + $this->couldNotActivate = true; + } } protected function initializeRepository(): void @@ -136,8 +152,15 @@ public function getLinkManager(): LinkManager return $this->linkManager; } - public function linkLinkedPackages(): void + public function linkLinkedPackages(Event $event): void { + // Plugin couldn't be activated probably because the plugin was updated + if ($this->couldNotActivate) { + $event->getIO()->warning('Composer link couldn\'t be activated because it was probably upgraded, run `composer install` again to link packages'); + + return; + } + if (is_null($this->linkPackages)) { throw new RuntimeException('Plugin not activated'); } diff --git a/tests/Unit/PluginTest.php b/tests/Unit/PluginTest.php index 6732056..ddb90bc 100644 --- a/tests/Unit/PluginTest.php +++ b/tests/Unit/PluginTest.php @@ -23,14 +23,17 @@ use Composer\Plugin\Capability\CommandProvider as ComposerCommandProvider; use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\RepositoryManager; +use Composer\Script\Event; use Composer\Script\ScriptEvents; use Composer\Util\Filesystem; use Composer\Util\Loop; use ComposerLink\Actions\LinkPackages; use ComposerLink\CommandProvider; use ComposerLink\Plugin; +use ComposerLink\Repository\RepositoryFactory; use PHPUnit\Framework\MockObject\MockObject; use RuntimeException; +use TypeError; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -103,6 +106,30 @@ public function test_if_plugin_can_be_utilized(): void $plugin->uninstall($this->composer, $this->io); } + public function test_unable_to_activate_plugin(): void + { + $repositoryFactory = $this->createMock(RepositoryFactory::class); + $linkPackages = $this->createMock(LinkPackages::class); + $event = $this->createMock(Event::class); + $event->method('getIO')->willReturn($this->io); + + $repositoryFactory->method('create') + ->willThrowException(new TypeError('test error')); + + $plugin = new Plugin( + $this->filesystem, + $linkPackages, + $repositoryFactory, + ); + + $plugin->activate($this->composer, $this->io); + + $this->io->expects(static::once())->method('warning')->with( + static::stringContains('Composer link couldn\'t be activated') + ); + $plugin->linkLinkedPackages($event); + } + public function test_is_global(): void { $this->config->method('get') @@ -143,13 +170,14 @@ public function test_plugin_throws_exception_repository(): void public function test_plugin_link_linked_packages(): void { + $event = $this->createMock(Event::class); $linkPackages = $this->createMock(LinkPackages::class); $linkPackages->expects(static::once())->method('execute'); $plugin = new Plugin($this->createMock(Filesystem::class), $linkPackages); - $plugin->linkLinkedPackages(); + $plugin->linkLinkedPackages($event); static::expectException(RuntimeException::class); $plugin = new Plugin($this->createMock(Filesystem::class)); - $plugin->linkLinkedPackages(); + $plugin->linkLinkedPackages($event); } }