From 5761228068baa934f6312b471ce309591611cdf2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jun 2020 10:20:03 +0200 Subject: [PATCH] Make installer classes forward promises from downloaders to InstallationManager --- .../Installer/InstallationManager.php | 5 +- src/Composer/Installer/LibraryInstaller.php | 85 +++++++++++++------ src/Composer/Installer/PluginInstaller.php | 58 +++++++++---- src/Composer/Installer/ProjectInstaller.php | 6 +- 4 files changed, 106 insertions(+), 48 deletions(-) diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index bd1bf8aee..059ea7169 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -26,6 +26,7 @@ use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\StreamContextFactory; use Composer\Util\Loop; +use React\Promise\PromiseInterface; /** * Package operation manager. @@ -296,8 +297,8 @@ class InstallationManager $io = $this->io; $promise = $installer->prepare($opType, $package, $initialPackage); - if (null === $promise) { - $promise = new \React\Promise\Promise(function ($resolve, $reject) { $resolve(); }); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } $promise = $promise->then(function () use ($opType, $installManager, $repo, $operation) { diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 5e99e1f47..d70cd4f63 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -19,6 +19,7 @@ use Composer\Package\PackageInterface; use Composer\Util\Filesystem; use Composer\Util\Silencer; use Composer\Util\Platform; +use React\Promise\PromiseInterface; /** * Package installation manager. @@ -131,11 +132,19 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface $this->binaryInstaller->removeBinaries($package); } - $this->installCode($package); - $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package)); - if (!$repo->hasPackage($package)) { - $repo->addPackage(clone $package); + $promise = $this->installCode($package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } + + $binaryInstaller = $this->binaryInstaller; + $installPath = $this->getInstallPath($package); + return $promise->then(function () use ($binaryInstaller, $installPath, $package, $repo) { + $binaryInstaller->installBinaries($package, $installPath); + if (!$repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + }); } /** @@ -150,12 +159,20 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface $this->initializeVendorDir(); $this->binaryInstaller->removeBinaries($initial); - $this->updateCode($initial, $target); - $this->binaryInstaller->installBinaries($target, $this->getInstallPath($target)); - $repo->removePackage($initial); - if (!$repo->hasPackage($target)) { - $repo->addPackage(clone $target); + $promise = $this->updateCode($initial, $target); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } + + $binaryInstaller = $this->binaryInstaller; + $installPath = $this->getInstallPath($target); + return $promise->then(function () use ($binaryInstaller, $installPath, $target, $initial, $repo) { + $binaryInstaller->installBinaries($target, $installPath); + $repo->removePackage($initial); + if (!$repo->hasPackage($target)) { + $repo->addPackage(clone $target); + } + }); } /** @@ -167,17 +184,25 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface throw new \InvalidArgumentException('Package is not installed: '.$package); } - $this->removeCode($package); - $this->binaryInstaller->removeBinaries($package); - $repo->removePackage($package); - - $downloadPath = $this->getPackageBasePath($package); - if (strpos($package->getName(), '/')) { - $packageVendorDir = dirname($downloadPath); - if (is_dir($packageVendorDir) && $this->filesystem->isDirEmpty($packageVendorDir)) { - Silencer::call('rmdir', $packageVendorDir); - } + $promise = $this->removeCode($package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } + + $binaryInstaller = $this->binaryInstaller; + $downloadPath = $this->getPackageBasePath($package); + $filesystem = $this->filesystem; + return $promise->then(function () use ($binaryInstaller, $filesystem, $downloadPath, $package, $repo) { + $binaryInstaller->removeBinaries($package); + $repo->removePackage($package); + + if (strpos($package->getName(), '/')) { + $packageVendorDir = dirname($downloadPath); + if (is_dir($packageVendorDir) && $filesystem->isDirEmpty($packageVendorDir)) { + Silencer::call('rmdir', $packageVendorDir); + } + } + }); } /** @@ -227,7 +252,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface protected function installCode(PackageInterface $package) { $downloadPath = $this->getInstallPath($package); - $this->downloadManager->install($package, $downloadPath); + return $this->downloadManager->install($package, $downloadPath); } protected function updateCode(PackageInterface $initial, PackageInterface $target) @@ -240,21 +265,31 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface if (substr($initialDownloadPath, 0, strlen($targetDownloadPath)) === $targetDownloadPath || substr($targetDownloadPath, 0, strlen($initialDownloadPath)) === $initialDownloadPath ) { - $this->removeCode($initial); - $this->installCode($target); + $promise = $this->removeCode($initial); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); + } - return; + $self = $this; + return $promise->then(function () use ($self, $target) { + $reflMethod = new \ReflectionMethod($self, 'installCode'); + $reflMethod->setAccessible(true); + + // equivalent of $this->installCode($target) with php 5.3 support + // TODO remove this once 5.3 support is dropped + return $reflMethod->invoke($self, $target); + }); } $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); } - $this->downloadManager->update($initial, $target, $targetDownloadPath); + return $this->downloadManager->update($initial, $target, $targetDownloadPath); } protected function removeCode(PackageInterface $package) { $downloadPath = $this->getPackageBasePath($package); - $this->downloadManager->remove($package, $downloadPath); + return $this->downloadManager->remove($package, $downloadPath); } protected function initializeVendorDir() diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index 6d5a26be0..324ef5a99 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -16,6 +16,7 @@ use Composer\Composer; use Composer\IO\IOInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; +use React\Promise\PromiseInterface; /** * Installer for plugin packages @@ -65,15 +66,20 @@ class PluginInstaller extends LibraryInstaller */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { - parent::install($repo, $package); - try { - $this->composer->getPluginManager()->registerPackage($package, true); - } catch (\Exception $e) { - // Rollback installation - $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin'); - parent::uninstall($repo, $package); - throw $e; + $promise = parent::install($repo, $package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } + + $pluginManager = $this->composer->getPluginManager(); + $self = $this; + return $promise->then(function () use ($self, $pluginManager, $package, $repo) { + try { + $pluginManager->registerPackage($package, true); + } catch (\Exception $e) { + $self->rollbackInstall($e, $repo, $package); + } + }); } /** @@ -81,22 +87,38 @@ class PluginInstaller extends LibraryInstaller */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { - parent::update($repo, $initial, $target); - - 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; + $promise = parent::update($repo, $initial, $target); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } + + $pluginManager = $this->composer->getPluginManager(); + $self = $this; + return $promise->then(function () use ($self, $pluginManager, $initial, $target, $repo) { + try { + $pluginManager->deactivatePackage($initial, true); + $pluginManager->registerPackage($target, true); + } catch (\Exception $e) { + $self->rollbackInstall($e, $repo, $target); + } + }); } public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->composer->getPluginManager()->uninstallPackage($package, true); + + return parent::uninstall($repo, $package); + } + + /** + * TODO v3 should make this private once we can drop PHP 5.3 support + * @private + */ + public function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin'); parent::uninstall($repo, $package); + throw $e; } } diff --git a/src/Composer/Installer/ProjectInstaller.php b/src/Composer/Installer/ProjectInstaller.php index 069c741ec..2875e0a65 100644 --- a/src/Composer/Installer/ProjectInstaller.php +++ b/src/Composer/Installer/ProjectInstaller.php @@ -76,7 +76,7 @@ class ProjectInstaller implements InstallerInterface */ public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null) { - $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage); + return $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage); } /** @@ -84,7 +84,7 @@ class ProjectInstaller implements InstallerInterface */ public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null) { - $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage); + return $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage); } /** @@ -92,7 +92,7 @@ class ProjectInstaller implements InstallerInterface */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { - $this->downloadManager->install($package, $this->installPath); + return $this->downloadManager->install($package, $this->installPath); } /**