diff --git a/src/Composer/DependencyResolver/Transaction.php b/src/Composer/DependencyResolver/Transaction.php index 4b67b674e..09c3bc6f5 100644 --- a/src/Composer/DependencyResolver/Transaction.php +++ b/src/Composer/DependencyResolver/Transaction.php @@ -248,6 +248,9 @@ class Transaction */ private function movePluginsToFront(array $operations) { + $dlModyingPluginsNoDeps = array(); + $dlModyingPluginsWithDeps = array(); + $dlModyingPluginRequires = array(); $pluginsNoDeps = array(); $pluginsWithDeps = array(); $pluginRequires = array(); @@ -261,6 +264,30 @@ class Transaction continue; } + $isDownloadsModifyingPlugin = $package->getType() === 'composer-plugin' && ($extra = $package->getExtra()) && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true; + + // is this a downloads modifying plugin or a dependency of one? + if ($isDownloadsModifyingPlugin || count(array_intersect($package->getNames(), $dlModyingPluginRequires))) { + // get the package's requires, but filter out any platform requirements + $requires = array_filter(array_keys($package->getRequires()), function ($req) { + return !PlatformRepository::isPlatformPackage($req); + }); + + // is this a plugin with no meaningful dependencies? + if ($isPlugin && !count($requires)) { + // plugins with no dependencies go to the very front + array_unshift($dlModyingPluginsNoDeps, $op); + } else { + // capture the requirements for this package so those packages will be moved up as well + $dlModyingPluginRequires = array_merge($dlModyingPluginRequires, $requires); + // move the operation to the front + array_unshift($dlModyingPluginsWithDeps, $op); + } + + unset($operations[$idx]); + continue; + } + // is this package a plugin? $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer'; @@ -286,7 +313,7 @@ class Transaction } } - return array_merge($pluginsNoDeps, $pluginsWithDeps, $operations); + return array_merge($dlModyingPluginsNoDeps, $dlModyingPluginsWithDeps, $pluginsNoDeps, $pluginsWithDeps, $operations); } /** diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index dc3711aac..374e37e2d 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -240,7 +240,7 @@ class InstallationManager 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' && $extra = $package->getExtra() && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) { + if ($package->getType() === 'composer-plugin' && ($extra = $package->getExtra()) && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) { if ($batch) { $batches[] = $batch; } diff --git a/tests/Composer/Test/DependencyResolver/TransactionTest.php b/tests/Composer/Test/DependencyResolver/TransactionTest.php index 8599a5da6..39354c006 100644 --- a/tests/Composer/Test/DependencyResolver/TransactionTest.php +++ b/tests/Composer/Test/DependencyResolver/TransactionTest.php @@ -42,8 +42,28 @@ class TransactionTest extends TestCase $packageG = $this->getPackage('g/g', '1.0.0'), $packageA0first = $this->getPackage('a0/first', '1.2.3'), $packageFalias2 = $this->getAliasPackage($packageF, 'dev-bar'), + $plugin = $this->getPackage('x/plugin', '1.0.0'), + $plugin2Dep = $this->getPackage('x/plugin2-dep', '1.0.0'), + $plugin2 = $this->getPackage('x/plugin2', '1.0.0'), + $dlModifyingPlugin = $this->getPackage('x/downloads-modifying', '1.0.0'), + $dlModifyingPlugin2Dep = $this->getPackage('x/downloads-modifying2-dep', '1.0.0'), + $dlModifyingPlugin2 = $this->getPackage('x/downloads-modifying2', '1.0.0'), ); + $plugin->setType('composer-installer'); + foreach (array($plugin2, $dlModifyingPlugin, $dlModifyingPlugin2) as $pluginPackage) { + $pluginPackage->setType('composer-plugin'); + } + + $plugin2->setRequires(array( + 'x/plugin2-dep' => new Link('x/plugin2', 'x/plugin2-dep', $this->getVersionConstraint('=', '1.0.0'), Link::TYPE_REQUIRE), + )); + $dlModifyingPlugin2->setRequires(array( + 'x/downloads-modifying2-dep' => new Link('x/downloads-modifying2', 'x/downloads-modifying2-dep', $this->getVersionConstraint('=', '1.0.0'), Link::TYPE_REQUIRE), + )); + $dlModifyingPlugin->setExtra(array('plugin-modifies-downloads' => true)); + $dlModifyingPlugin2->setExtra(array('plugin-modifies-downloads' => true)); + $packageD->setRequires(array( 'f/f' => new Link('d/d', 'f/f', $this->getVersionConstraint('>', '0.2'), Link::TYPE_REQUIRE), 'g/provider' => new Link('d/d', 'g/provider', $this->getVersionConstraint('>', '0.2'), Link::TYPE_REQUIRE), @@ -54,6 +74,12 @@ class TransactionTest extends TestCase array('job' => 'uninstall', 'package' => $packageC), array('job' => 'uninstall', 'package' => $packageE), array('job' => 'markAliasUninstalled', 'package' => $packageEalias), + array('job' => 'install', 'package' => $dlModifyingPlugin), + array('job' => 'install', 'package' => $dlModifyingPlugin2Dep), + array('job' => 'install', 'package' => $dlModifyingPlugin2), + array('job' => 'install', 'package' => $plugin), + array('job' => 'install', 'package' => $plugin2Dep), + array('job' => 'install', 'package' => $plugin2), array('job' => 'install', 'package' => $packageA0first), array('job' => 'update', 'from' => $packageB, 'to' => $packageBnew), array('job' => 'install', 'package' => $packageG),