diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index a4c919b57..dc3711aac 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -180,7 +180,6 @@ class InstallationManager */ public function execute(RepositoryInterface $repo, array $operations, $devMode = true, $runScripts = true) { - $promises = array(); $cleanupPromises = array(); $loop = $this->loop; @@ -234,61 +233,14 @@ class InstallationManager } try { - foreach ($operations as $index => $operation) { - $opType = $operation->getOperationType(); - - // ignoring alias ops as they don't need to execute anything at this stage - if (!in_array($opType, array('update', 'install', 'uninstall'))) { - continue; - } - - if ($opType === 'update') { - $package = $operation->getTargetPackage(); - $initialPackage = $operation->getInitialPackage(); - } else { - $package = $operation->getPackage(); - $initialPackage = null; - } - $installer = $this->getInstaller($package->getType()); - - $cleanupPromises[$index] = function () use ($opType, $installer, $package, $initialPackage) { - // avoid calling cleanup if the download was not even initialized for a package - // as without installation source configured nothing will work - if (!$package->getInstallationSource()) { - return; - } - - return $installer->cleanup($opType, $package, $initialPackage); - }; - - if ($opType !== 'uninstall') { - $promise = $installer->download($package, $initialPackage); - if ($promise) { - $promises[] = $promise; - } - } - } - - // execute all downloads first - if (!empty($promises)) { - $progress = null; - if ($this->outputProgress && $this->io instanceof ConsoleIO && !$this->io->isDebug() && count($promises) > 1) { - $progress = $this->io->getProgressBar(); - } - $this->loop->wait($promises, $progress); - if ($progress) { - $progress->clear(); - } - } - - // execute operations in batches to make sure every plugin is installed in the - // right order and activated before the packages depending on it are installed + // execute operations in batches to make sure download-modifying-plugins are installed + // before the other packages get downloaded $batches = array(); $batch = array(); foreach ($operations as $index => $operation) { if (in_array($operation->getOperationType(), array('update', 'install'), true)) { $package = $operation->getOperationType() === 'update' ? $operation->getTargetPackage() : $operation->getPackage(); - if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') { + if ($package->getType() === 'composer-plugin' && $extra = $package->getExtra() && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) { if ($batch) { $batches[] = $batch; } @@ -306,7 +258,7 @@ class InstallationManager } foreach ($batches as $batch) { - $this->executeBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $operations); + $this->downloadAndExecuteBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $operations); } } catch (\Exception $e) { $runCleanup(); @@ -334,12 +286,91 @@ class InstallationManager $repo->write($devMode, $this); } + /** + * @param array $operations List of operations to execute in this batch + * @param array $allOperations Complete list of operations to be executed in the install job, used for event listeners + */ + private function downloadAndExecuteBatch(RepositoryInterface $repo, array $operations, array &$cleanupPromises, $devMode, $runScripts, array $allOperations) + { + $promises = array(); + + foreach ($operations as $index => $operation) { + $opType = $operation->getOperationType(); + + // ignoring alias ops as they don't need to execute anything at this stage + if (!in_array($opType, array('update', 'install', 'uninstall'))) { + continue; + } + + if ($opType === 'update') { + $package = $operation->getTargetPackage(); + $initialPackage = $operation->getInitialPackage(); + } else { + $package = $operation->getPackage(); + $initialPackage = null; + } + $installer = $this->getInstaller($package->getType()); + + $cleanupPromises[$index] = function () use ($opType, $installer, $package, $initialPackage) { + // avoid calling cleanup if the download was not even initialized for a package + // as without installation source configured nothing will work + if (!$package->getInstallationSource()) { + return; + } + + return $installer->cleanup($opType, $package, $initialPackage); + }; + + if ($opType !== 'uninstall') { + $promise = $installer->download($package, $initialPackage); + if ($promise) { + $promises[] = $promise; + } + } + } + + // execute all downloads first + if (count($promises)) { + $this->waitOnPromises($promises); + } + + // execute operations in batches to make sure every plugin is installed in the + // right order and activated before the packages depending on it are installed + $batches = array(); + $batch = array(); + foreach ($operations as $index => $operation) { + if (in_array($operation->getOperationType(), array('update', 'install'), true)) { + $package = $operation->getOperationType() === 'update' ? $operation->getTargetPackage() : $operation->getPackage(); + if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') { + if ($batch) { + $batches[] = $batch; + } + $batches[] = array($index => $operation); + $batch = array(); + + continue; + } + } + $batch[$index] = $operation; + } + + if ($batch) { + $batches[] = $batch; + } + + foreach ($batches as $batch) { + $this->executeBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $allOperations); + } + } + /** * @param array $operations List of operations to execute in this batch * @param array $allOperations Complete list of operations to be executed in the install job, used for event listeners */ private function executeBatch(RepositoryInterface $repo, array $operations, array $cleanupPromises, $devMode, $runScripts, array $allOperations) { + $promises = array(); + foreach ($operations as $index => $operation) { $opType = $operation->getOperationType(); @@ -397,15 +428,20 @@ class InstallationManager } // execute all prepare => installs/updates/removes => cleanup steps - if (!empty($promises)) { - $progress = null; - if ($this->outputProgress && $this->io instanceof ConsoleIO && !$this->io->isDebug() && count($promises) > 1) { - $progress = $this->io->getProgressBar(); - } - $this->loop->wait($promises, $progress); - if ($progress) { - $progress->clear(); - } + if (count($promises)) { + $this->waitOnPromises($promises); + } + } + + private function waitOnPromises(array $promises) + { + $progress = null; + if ($this->outputProgress && $this->io instanceof ConsoleIO && !$this->io->isDebug() && count($promises) > 1) { + $progress = $this->io->getProgressBar(); + } + $this->loop->wait($promises, $progress); + if ($progress) { + $progress->clear(); } }