diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 751d3c492..8977aa32d 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -43,8 +43,8 @@ Composer fires the following named events during its execution process: ### Installer Events -- **pre-dependencies-solving**: occurs before the dependencies are resolved. -- **post-dependencies-solving**: occurs after the dependencies have been resolved. +- **pre-operations-exec**: occurs before the install/upgrade/.. operations + are executed when installing a lock file. ### Package Events @@ -66,6 +66,8 @@ Composer fires the following named events during its execution process: - **pre-command-run**: occurs before a command is executed and allows you to manipulate the `InputInterface` object's options and arguments to tweak a command's behavior. +- **pre-pool-create**: occurs before the Pool of packages is created, and lets + you filter the list of packages which is going to enter the Solver. > **Note:** Composer makes no assumptions about the state of your dependencies > prior to `install` or `update`. Therefore, you should not specify scripts diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 360924143..d3d14e629 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -21,6 +21,9 @@ use Composer\Repository\PlatformRepository; use Composer\Repository\RootPackageRepository; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\MultiConstraint; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Plugin\PrePoolCreateEvent; +use Composer\Plugin\PluginEvents; /** * @author Nils Adermann @@ -31,6 +34,7 @@ class PoolBuilder private $stabilityFlags; private $rootAliases; private $rootReferences; + private $eventDispatcher; private $aliasMap = array(); private $nameConstraints = array(); @@ -38,12 +42,13 @@ class PoolBuilder private $packages = array(); private $unacceptableFixedPackages = array(); - public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences) + public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, EventDispatcher $eventDispatcher = null) { $this->acceptableStabilities = $acceptableStabilities; $this->stabilityFlags = $stabilityFlags; $this->rootAliases = $rootAliases; $this->rootReferences = $rootReferences; + $this->eventDispatcher = $eventDispatcher; } public function buildPool(array $repositories, Request $request) @@ -132,6 +137,23 @@ class PoolBuilder } } + if ($this->eventDispatcher) { + $prePoolCreateEvent = new PrePoolCreateEvent( + PluginEvents::PRE_POOL_CREATE, + $repositories, + $request, + $this->acceptableStabilities, + $this->stabilityFlags, + $this->rootAliases, + $this->rootReferences, + $this->packages, + $this->unacceptableFixedPackages + ); + $this->eventDispatcher->dispatch($prePoolCreateEvent->getName(), $prePoolCreateEvent); + $this->packages = $prePoolCreateEvent->getPackages(); + $this->unacceptableFixedPackages = $prePoolCreateEvent->getUnacceptableFixedPackages(); + } + $pool = new Pool($this->packages, $this->unacceptableFixedPackages); $this->aliasMap = array(); diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index b2ecdf023..fda5c29fd 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -14,6 +14,8 @@ namespace Composer\EventDispatcher; use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\Request; +use Composer\DependencyResolver\Pool; +use Composer\DependencyResolver\Transaction; use Composer\Installer\InstallerEvent; use Composer\IO\IOInterface; use Composer\Composer; @@ -117,20 +119,17 @@ class EventDispatcher /** * Dispatch a installer event. * - * @param string $eventName The constant in InstallerEvents - * @param bool $devMode Whether or not we are in dev mode - * @param PolicyInterface $policy The policy - * @param RepositorySet $repositorySet The repository set - * @param RepositoryInterface $localRepo The installed repository - * @param Request $request The request - * @param array $operations The list of operations + * @param string $eventName The constant in InstallerEvents + * @param bool $devMode Whether or not we are in dev mode + * @param bool $executeOperations True if operations will be executed, false in --dry-run + * @param Transaction $transaction The transaction contains the list of operations * * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ - public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, RepositoryInterface $localRepo, Request $request, array $operations = array()) + public function dispatchInstallerEvent($eventName, $devMode, $executeOperations, Transaction $transaction) { - return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $repositorySet, $localRepo, $request, $operations)); + return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $executeOperations, $transaction)); } /** diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 9d67e7d83..a1f93695d 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -381,10 +381,7 @@ class Installer } } - // TODO reenable events - //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request); - - $pool = $repositorySet->createPool($request); + $pool = $repositorySet->createPool($request, $this->eventDispatcher); // solve dependencies $solver = new Solver($policy, $pool, $this->io, $repositorySet); @@ -402,9 +399,6 @@ class Installer return max(1, $e->getCode()); } - // TODO should we warn people / error if plugins in vendor folder do not match contents of lock file before update? - //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $lockedRepository, $request, $lockTransaction); - $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE); @@ -524,13 +518,11 @@ class Installer $request->requireName($link->getTarget(), $link->getConstraint()); } - $pool = $repositorySet->createPool($request); + $pool = $repositorySet->createPool($request, $this->eventDispatcher); - //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request); $solver = new Solver($policy, $pool, $this->io, $repositorySet); try { $nonDevLockTransaction = $solver->solve($request, $this->ignorePlatformReqs); - //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops); $solver = null; } catch (SolverProblemsException $e) { $this->io->writeError('Unable to find a compatible set of packages based on your non-dev requirements alone.', true, IOInterface::QUIET); @@ -549,22 +541,22 @@ class Installer */ protected function doInstall(RepositoryInterface $localRepo, $alreadySolved = false) { - $platformRepo = $this->createPlatformRepo(false); - $lockedRepository = $this->locker->getLockedRepository($this->devMode); - - // creating repository set - $policy = $this->createPolicy(false); - // use aliases from lock file only, so empty root aliases here - $repositorySet = $this->createRepositorySet(false, $platformRepo, array(), $lockedRepository); - $repositorySet->addRepository($lockedRepository); - $this->io->writeError('Installing dependencies from lock file'.($this->devMode ? ' (including require-dev)' : '').''); + $lockedRepository = $this->locker->getLockedRepository($this->devMode); + // verify that the lock file works with the current platform repository // we can skip this part if we're doing this as the second step after an update if (!$alreadySolved) { $this->io->writeError('Verifying lock file contents can be installed on current platform.'); + $platformRepo = $this->createPlatformRepo(false); + // creating repository set + $policy = $this->createPolicy(false); + // use aliases from lock file only, so empty root aliases here + $repositorySet = $this->createRepositorySet(false, $platformRepo, array(), $lockedRepository); + $repositorySet->addRepository($lockedRepository); + // creating requirements request $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); @@ -580,9 +572,7 @@ class Installer $request->requireName($link->getTarget(), $link->getConstraint()); } - //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request); - - $pool = $repositorySet->createPool($request); + $pool = $repositorySet->createPool($request, $this->eventDispatcher); // solve dependencies $solver = new Solver($policy, $pool, $this->io, $repositorySet); @@ -602,13 +592,11 @@ class Installer return max(1, $e->getCode()); } - - // TODO should we warn people / error if plugins in vendor folder do not match contents of lock file before update? - //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request, $lockTransaction); } // TODO in how far do we need to do anything here to ensure dev packages being updated to latest in lock without version change are treated correctly? $localRepoTransaction = new LocalRepoTransaction($lockedRepository, $localRepo); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_OPERATIONS_EXEC, $this->devMode, $this->executeOperations, $localRepoTransaction); if (!$localRepoTransaction->getOperations()) { $this->io->writeError('Nothing to install, update or remove'); diff --git a/src/Composer/Installer/InstallerEvent.php b/src/Composer/Installer/InstallerEvent.php index 7752f6920..1b8f03de4 100644 --- a/src/Composer/Installer/InstallerEvent.php +++ b/src/Composer/Installer/InstallerEvent.php @@ -14,18 +14,13 @@ namespace Composer\Installer; use Composer\Composer; use Composer\DependencyResolver\PolicyInterface; -use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Request; +use Composer\DependencyResolver\Pool; +use Composer\DependencyResolver\Transaction; use Composer\EventDispatcher\Event; use Composer\IO\IOInterface; -use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositorySet; -/** - * An event for all installer. - * - * @author François Pluchino - */ class InstallerEvent extends Event { /** @@ -44,29 +39,14 @@ class InstallerEvent extends Event private $devMode; /** - * @var PolicyInterface + * @var bool */ - private $policy; + private $executeOperations; /** - * @var RepositorySet + * @var Transaction */ - private $repositorySet; - - /** - * @var RepositoryInterface - */ - private $localRepo; - - /** - * @var Request - */ - private $request; - - /** - * @var OperationInterface[] - */ - private $operations; + private $transaction; /** * Constructor. @@ -75,24 +55,18 @@ class InstallerEvent extends Event * @param Composer $composer * @param IOInterface $io * @param bool $devMode - * @param PolicyInterface $policy - * @param RepositorySet $repositorySet - * @param RepositoryInterface $localRepo - * @param Request $request - * @param OperationInterface[] $operations + * @param bool $executeOperations + * @param Transaction $transaction */ - public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, RepositoryInterface $localRepo, Request $request, array $operations = array()) + public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, $executeOperations, Transaction $transaction) { parent::__construct($eventName); $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; - $this->policy = $policy; - $this->repositorySet = $repositorySet; - $this->localRepo = $localRepo; - $this->request = $request; - $this->operations = $operations; + $this->executeOperations = $executeOperations; + $this->transaction = $transaction; } /** @@ -120,42 +94,18 @@ class InstallerEvent extends Event } /** - * @return PolicyInterface + * @return bool */ - public function getPolicy() + public function isExecutingOperations() { - return $this->policy; + return $this->executeOperations; } /** - * @return RepositorySet + * @return Transaction|null */ - public function getRepositorySet() + public function getTransaction() { - return $this->repositorySet; - } - - /** - * @return RepositoryInterface - */ - public function getLocalRepo() - { - return $this->localRepo; - } - - /** - * @return Request - */ - public function getRequest() - { - return $this->request; - } - - /** - * @return OperationInterface[] - */ - public function getOperations() - { - return $this->operations; + return $this->transaction; } } diff --git a/src/Composer/Installer/InstallerEvents.php b/src/Composer/Installer/InstallerEvents.php index e05c92587..f75b8bdd4 100644 --- a/src/Composer/Installer/InstallerEvents.php +++ b/src/Composer/Installer/InstallerEvents.php @@ -12,32 +12,15 @@ namespace Composer\Installer; -/** - * The Installer Events. - * - * @author François Pluchino - */ class InstallerEvents { /** - * The PRE_DEPENDENCIES_SOLVING event occurs as a installer begins - * resolve operations. + * The PRE_OPERATIONS_EXEC event occurs before the lock file gets + * installed and operations are executed. * - * The event listener method receives a - * Composer\Installer\InstallerEvent instance. + * The event listener method receives an Composer\Installer\InstallerEvent instance. * * @var string */ - const PRE_DEPENDENCIES_SOLVING = 'pre-dependencies-solving'; - - /** - * The POST_DEPENDENCIES_SOLVING event occurs as a installer after - * resolve operations. - * - * The event listener method receives a - * Composer\Installer\InstallerEvent instance. - * - * @var string - */ - const POST_DEPENDENCIES_SOLVING = 'post-dependencies-solving'; + const PRE_OPERATIONS_EXEC = 'pre-operations-exec'; } diff --git a/src/Composer/Plugin/PluginEvents.php b/src/Composer/Plugin/PluginEvents.php index 1fb368baf..39cac6798 100644 --- a/src/Composer/Plugin/PluginEvents.php +++ b/src/Composer/Plugin/PluginEvents.php @@ -58,4 +58,15 @@ class PluginEvents * @var string */ const PRE_COMMAND_RUN = 'pre-command-run'; + + /** + * The PRE_POOL_CREATE event occurs before the Pool of packages is created, and lets + * you filter the list of packages which is going to enter the Solver + * + * The event listener method receives a + * Composer\Plugin\PrePoolCreateEvent instance. + * + * @var string + */ + const PRE_POOL_CREATE = 'pre-pool-create'; } diff --git a/src/Composer/Plugin/PrePoolCreateEvent.php b/src/Composer/Plugin/PrePoolCreateEvent.php new file mode 100644 index 000000000..0e6617739 --- /dev/null +++ b/src/Composer/Plugin/PrePoolCreateEvent.php @@ -0,0 +1,158 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Repository\RepositoryInterface; +use Composer\DependencyResolver\Request; +use Composer\Package\PackageInterface; + +/** + * The pre command run event. + * + * @author Jordi Boggiano + */ +class PrePoolCreateEvent extends Event +{ + /** + * @var RepositoryInterface[] + */ + private $repositories; + /** + * @var Request + */ + private $request; + /** + * @var array + */ + private $acceptableStabilities; + /** + * @var array + */ + private $stabilityFlags; + /** + * @var array + */ + private $rootAliases; + /** + * @var array + */ + private $rootReferences; + /** + * @var PackageInterface[] + */ + private $packages; + /** + * @var PackageInterface[] + */ + private $unacceptableFixedPackages; + + /** + * @param string $name The event name + * @param RepositoryInterface[] $repositories + */ + public function __construct($name, array $repositories, Request $request, array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, array $packages, array $unacceptableFixedPackages) + { + parent::__construct($name); + + $this->repositories = $repositories; + $this->request = $request; + $this->acceptableStabilities = $acceptableStabilities; + $this->stabilityFlags = $stabilityFlags; + $this->rootAliases = $rootAliases; + $this->rootReferences = $rootReferences; + $this->packages = $packages; + $this->unacceptableFixedPackages = $unacceptableFixedPackages; + } + + /** + * @return RepositoryInterface[] + */ + public function getRepositories() + { + return $this->repositories; + } + + /** + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * @return array + */ + public function getAcceptableStabilities() + { + return $this->acceptableStabilities; + } + + /** + * @return array + */ + public function getStabilityFlags() + { + return $this->stabilityFlags; + } + + /** + * @return array + */ + public function getRootAliases() + { + return $this->rootAliases; + } + + /** + * @return array + */ + public function getRootReferences() + { + return $this->rootReferences; + } + + /** + * @return PackageInterface[] + */ + public function getPackages() + { + return $this->packages; + } + + /** + * @return PackageInterface[] + */ + public function getUnacceptableFixedPackages() + { + return $this->unacceptableFixedPackages; + } + + /** + * @param PackageInterface[] $packages + */ + public function setPackages(array $packages) + { + $this->packages = $packages; + } + + /** + * @param PackageInterface[] $packages + */ + public function setUnacceptableFixedPackages(array $packages) + { + $this->unacceptableFixedPackages = $packages; + } +} diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 80df9bb1d..6cafaa6c2 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -15,6 +15,7 @@ namespace Composer\Repository; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\PoolBuilder; use Composer\DependencyResolver\Request; +use Composer\EventDispatcher\EventDispatcher; use Composer\Package\BasePackage; use Composer\Package\Version\VersionParser; use Composer\Repository\CompositeRepository; @@ -188,9 +189,9 @@ class RepositorySet * * @return Pool */ - public function createPool(Request $request) + public function createPool(Request $request, EventDispatcher $eventDispatcher = null) { - $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences); + $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $eventDispatcher); foreach ($this->repositories as $repo) { if ($repo instanceof InstalledRepositoryInterface && !$this->allowInstalledRepositories) { diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 1d6dcafe8..c038681da 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -565,13 +565,9 @@ class EventDispatcherTest extends TestCase ->method('getListeners') ->will($this->returnValue(array())); - $policy = $this->getMockBuilder('Composer\DependencyResolver\PolicyInterface')->getMock(); - $repositorySet = $this->getMockBuilder('Composer\Repository\RepositorySet')->disableOriginalConstructor()->getMock(); - $installedRepo = $this->getMockBuilder('Composer\Repository\CompositeRepository')->disableOriginalConstructor()->getMock(); - $request = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock(); + $transaction = $this->getMockBuilder('Composer\DependencyResolver\LockTransaction')->disableOriginalConstructor()->getMock(); - $dispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, true, $policy, $repositorySet, $installedRepo, $request); - $dispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, true, $policy, $repositorySet, $installedRepo, $request, array()); + $dispatcher->dispatchInstallerEvent(InstallerEvents::PRE_OPERATIONS_EXEC, true, true, $transaction); } public static function call() diff --git a/tests/Composer/Test/Installer/InstallerEventTest.php b/tests/Composer/Test/Installer/InstallerEventTest.php index 9be6c8121..6c0689844 100644 --- a/tests/Composer/Test/Installer/InstallerEventTest.php +++ b/tests/Composer/Test/Installer/InstallerEventTest.php @@ -21,21 +21,14 @@ class InstallerEventTest extends TestCase { $composer = $this->getMockBuilder('Composer\Composer')->getMock(); $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $policy = $this->getMockBuilder('Composer\DependencyResolver\PolicyInterface')->getMock(); - $repositorySet = $this->getMockBuilder('Composer\Repository\RepositorySet')->disableOriginalConstructor()->getMock(); - $localRepo = $this->getMockBuilder('Composer\Repository\CompositeRepository')->disableOriginalConstructor()->getMock(); - $request = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock(); - $operations = array($this->getMockBuilder('Composer\DependencyResolver\Operation\OperationInterface')->getMock()); - $event = new InstallerEvent('EVENT_NAME', $composer, $io, true, $policy, $repositorySet, $localRepo, $request, $operations); + $transaction = $this->getMockBuilder('Composer\DependencyResolver\LockTransaction')->disableOriginalConstructor()->getMock(); + $event = new InstallerEvent('EVENT_NAME', $composer, $io, true, true, $transaction); $this->assertSame('EVENT_NAME', $event->getName()); $this->assertInstanceOf('Composer\Composer', $event->getComposer()); $this->assertInstanceOf('Composer\IO\IOInterface', $event->getIO()); $this->assertTrue($event->isDevMode()); - $this->assertInstanceOf('Composer\DependencyResolver\PolicyInterface', $event->getPolicy()); - $this->assertInstanceOf('Composer\Repository\RepositorySet', $event->getRepositorySet()); - $this->assertInstanceOf('Composer\Repository\RepositoryInterface', $event->getLocalRepo()); - $this->assertInstanceOf('Composer\DependencyResolver\Request', $event->getRequest()); - $this->assertCount(1, $event->getOperations()); + $this->assertTrue($event->isExecutingOperations()); + $this->assertInstanceOf('Composer\DependencyResolver\Transaction', $event->getTransaction()); } }