diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php
index a9091dbe6..bcee49f9a 100644
--- a/src/Composer/Downloader/ArchiveDownloader.php
+++ b/src/Composer/Downloader/ArchiveDownloader.php
@@ -62,6 +62,7 @@ abstract class ArchiveDownloader extends FileDownloader
} while (is_dir($temporaryDir));
$this->addCleanupPath($package, $temporaryDir);
+ $this->addCleanupPath($package, $path);
$this->filesystem->ensureDirectoryExists($temporaryDir);
$fileName = $this->getFileName($package, $path);
@@ -77,6 +78,7 @@ abstract class ArchiveDownloader extends FileDownloader
$filesystem->removeDirectory($path);
$filesystem->removeDirectory($temporaryDir);
$self->removeCleanupPath($package, $temporaryDir);
+ $self->removeCleanupPath($package, $path);
};
$promise = null;
@@ -142,6 +144,7 @@ abstract class ArchiveDownloader extends FileDownloader
$filesystem->removeDirectory($temporaryDir);
$self->removeCleanupPath($package, $temporaryDir);
+ $self->removeCleanupPath($package, $path);
}, function ($e) use ($cleanup) {
$cleanup();
diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php
index 47b3e2914..54f359eaa 100644
--- a/src/Composer/Installer/InstallationManager.php
+++ b/src/Composer/Installer/InstallationManager.php
@@ -272,73 +272,23 @@ class InstallationManager
$this->loop->wait($promises);
}
- foreach ($operations as $index => $operation) {
- $opType = $operation->getOperationType();
+ // 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
+ while ($operations) {
+ $batch = array();
- // 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));
+ foreach ($operations as $index => $operation) {
+ unset($operations[$index]);
+ $batch[$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') {
+ break;
+ }
}
- $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 (!$promise instanceof PromiseInterface) {
- $promise = \React\Promise\resolve();
- }
-
- $promise = $promise->then(function () use ($opType, $installManager, $repo, $operation) {
- return $installManager->$opType($repo, $operation);
- })->then($cleanupPromises[$index])
- ->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, $package, $io) {
- $io->writeError(' ' . ucfirst($opType) .' of '.$package->getPrettyName().' failed');
-
- throw $e;
- });
-
- $promises[] = $promise;
- }
-
- // execute all prepare => installs/updates/removes => cleanup steps
- if (!empty($promises)) {
- $progress = null;
- if ($io instanceof ConsoleIO && !$io->isDebug()) {
- $progress = $io->getProgressBar();
- }
- $this->loop->wait($promises, $progress);
- if ($progress) {
- $progress->clear();
- }
+ $this->executeBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts);
}
} catch (\Exception $e) {
$runCleanup();
@@ -366,6 +316,77 @@ class InstallationManager
$repo->write($devMode, $this);
}
+ private function executeBatch(RepositoryInterface $repo, array $operations, array $cleanupPromises, $devMode, $runScripts)
+ {
+ foreach ($operations as $index => $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);
+
+ 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;
+ $io = $this->io;
+
+ $promise = $installer->prepare($opType, $package, $initialPackage);
+ if (!$promise instanceof PromiseInterface) {
+ $promise = \React\Promise\resolve();
+ }
+
+ $promise = $promise->then(function () use ($opType, $installManager, $repo, $operation) {
+ return $installManager->$opType($repo, $operation);
+ })->then($cleanupPromises[$index])
+ ->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, $package, $io) {
+ $io->writeError(' ' . ucfirst($opType) .' of '.$package->getPrettyName().' failed');
+
+ throw $e;
+ });
+
+ $promises[] = $promise;
+ }
+
+ // execute all prepare => installs/updates/removes => cleanup steps
+ if (!empty($promises)) {
+ $progress = null;
+ if ($io instanceof ConsoleIO && !$io->isDebug() && count($promises) > 1) {
+ $progress = $io->getProgressBar();
+ }
+ $this->loop->wait($promises, $progress);
+ if ($progress) {
+ $progress->clear();
+ }
+ }
+ }
+
/**
* Executes install operation.
*