From 8b0da77a1d5173f65b026d7475a1526bc29df939 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 9 Apr 2020 10:48:48 +0200 Subject: [PATCH] Make sure cleanup is called for every package in case any operation fails to execute --- .../Installer/InstallationManager.php | 140 ++++++++++-------- 1 file changed, 81 insertions(+), 59 deletions(-) diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index c3b4b9242..739a1dc88 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -196,71 +196,93 @@ class InstallationManager $this->loop->wait($promises); } - foreach ($operations as $operation) { - $opType = $operation->getOperationType(); + $cleanupPromises = array(); + try { + foreach ($operations as $operation) { + $opType = $operation->getOperationType(); - // ignoring alias ops as they don't need to execute anything - if (!in_array($opType, array('update', 'install', 'uninstall'))) { - // output alias ops in debug verbosity as they have no output otherwise - if ($this->io->isDebug()) { - $this->io->writeError(' - ' . $operation->show(false)); - } - $this->$opType($repo, $operation); + // ignoring alias ops as they don't need to execute anything + if (!in_array($opType, array('update', 'install', 'uninstall'))) { + // output alias ops in debug verbosity as they have no output otherwise + if ($this->io->isDebug()) { + $this->io->writeError(' - ' . $operation->show(false)); + } + $this->$opType($repo, $operation); - continue; - } - - if ($opType === 'update') { - $package = $operation->getTargetPackage(); - $initialPackage = $operation->getInitialPackage(); - } else { - $package = $operation->getPackage(); - $initialPackage = null; - } - $installer = $this->getInstaller($package->getType()); - - $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($opType); - if (defined($event) && $runScripts && $this->eventDispatcher) { - $this->eventDispatcher->dispatchPackageEvent(constant($event), $devMode, $repo, $operations, $operation); - } - - $dispatcher = $this->eventDispatcher; - $installManager = $this; - $loop = $this->loop; - $io = $this->io; - - $promise = $installer->prepare($opType, $package, $initialPackage); - if (null === $promise) { - $promise = new \React\Promise\Promise(function ($resolve, $reject) { $resolve(); }); - } - - $promise = $promise->then(function () use ($opType, $installManager, $repo, $operation) { - return $installManager->$opType($repo, $operation); - })->then(function () use ($opType, $installer, $package, $initialPackage) { - return $installer->cleanup($opType, $package, $initialPackage); - })->then(function () use ($opType, $runScripts, $dispatcher, $installManager, $devMode, $repo, $operations, $operation) { - $repo->write($devMode, $installManager); - - $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($opType); - if (defined($event) && $runScripts && $dispatcher) { - $dispatcher->dispatchPackageEvent(constant($event), $devMode, $repo, $operations, $operation); - } - }, function ($e) use ($opType, $installer, $package, $initialPackage, $loop, $io) { - $io->writeError(' ' . ucfirst($opType) .' of '.$package->getPrettyName().' failed'); - - $promise = $installer->cleanup($opType, $package, $initialPackage); - if ($promise) { - $loop->wait(array($promise)); + continue; } - throw $e; - }); + if ($opType === 'update') { + $package = $operation->getTargetPackage(); + $initialPackage = $operation->getInitialPackage(); + } else { + $package = $operation->getPackage(); + $initialPackage = null; + } + $installer = $this->getInstaller($package->getType()); - $promises[] = $promise; - } + $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($opType); + if (defined($event) && $runScripts && $this->eventDispatcher) { + $this->eventDispatcher->dispatchPackageEvent(constant($event), $devMode, $repo, $operations, $operation); + } - if (!empty($promises)) { - $this->loop->wait($promises); + $dispatcher = $this->eventDispatcher; + $installManager = $this; + $loop = $this->loop; + $io = $this->io; + + $promise = $installer->prepare($opType, $package, $initialPackage); + if (null === $promise) { + $promise = new \React\Promise\Promise(function ($resolve, $reject) { $resolve(); }); + } + + $cleanupPromise = function () use ($opType, $installer, $package, $initialPackage) { + return $installer->cleanup($opType, $package, $initialPackage); + }; + + $promise = $promise->then(function () use ($opType, $installManager, $repo, $operation) { + return $installManager->$opType($repo, $operation); + })->then($cleanupPromise) + ->then(function () use ($opType, $runScripts, $dispatcher, $installManager, $devMode, $repo, $operations, $operation) { + $repo->write($devMode, $installManager); + + $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($opType); + if (defined($event) && $runScripts && $dispatcher) { + $dispatcher->dispatchPackageEvent(constant($event), $devMode, $repo, $operations, $operation); + } + }, function ($e) use ($opType, $installer, $package, $initialPackage, $loop, $io) { + $io->writeError(' ' . ucfirst($opType) .' of '.$package->getPrettyName().' failed'); + + throw $e; + }); + + $cleanupPromises[] = $cleanupPromise; + $promises[] = $promise; + } + + if (!empty($promises)) { + $this->loop->wait($promises); + } + } catch (\Exception $e) { + $promises = array(); + foreach ($cleanupPromises as $cleanup) { + $promises[] = new \React\Promise\Promise(function ($resolve, $reject) use ($cleanup) { + $promise = $cleanup(); + if (null === $promise) { + $resolve(); + } else { + $promise->then(function () use ($resolve) { + $resolve(); + }); + } + }); + } + + if (!empty($promises)) { + $this->loop->wait($promises); + } + + throw $e; } }