Browse Source

Merge pull request #8566 from Seldaek/installer-events

Restore installer events and add a PRE_POOL_CREATE hook for plugins
Nils Adermann 5 years ago
parent
commit
4bb314e4af

+ 4 - 2
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

+ 23 - 1
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 <naderman@naderman.de>
@@ -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();

+ 8 - 9
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));
     }
 
     /**

+ 13 - 25
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('<error>Unable to find a compatible set of packages based on your non-dev requirements alone.</error>', 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('<info>Installing dependencies from lock file'.($this->devMode ? ' (including require-dev)' : '').'</info>');
 
+        $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('<info>Verifying lock file contents can be installed on current platform.</info>');
 
+            $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');

+ 17 - 67
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 <francois.pluchino@gmail.com>
- */
 class InstallerEvent extends Event
 {
     /**
@@ -44,29 +39,14 @@ class InstallerEvent extends Event
     private $devMode;
 
     /**
-     * @var PolicyInterface
-     */
-    private $policy;
-
-    /**
-     * @var RepositorySet
-     */
-    private $repositorySet;
-
-    /**
-     * @var RepositoryInterface
-     */
-    private $localRepo;
-
-    /**
-     * @var Request
+     * @var bool
      */
-    private $request;
+    private $executeOperations;
 
     /**
-     * @var OperationInterface[]
+     * @var Transaction
      */
-    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
-     */
-    public function getPolicy()
-    {
-        return $this->policy;
-    }
-
-    /**
-     * @return RepositorySet
-     */
-    public function getRepositorySet()
-    {
-        return $this->repositorySet;
-    }
-
-    /**
-     * @return RepositoryInterface
-     */
-    public function getLocalRepo()
-    {
-        return $this->localRepo;
-    }
-
-    /**
-     * @return Request
+     * @return bool
      */
-    public function getRequest()
+    public function isExecutingOperations()
     {
-        return $this->request;
+        return $this->executeOperations;
     }
 
     /**
-     * @return OperationInterface[]
+     * @return Transaction|null
      */
-    public function getOperations()
+    public function getTransaction()
     {
-        return $this->operations;
+        return $this->transaction;
     }
 }

+ 4 - 21
src/Composer/Installer/InstallerEvents.php

@@ -12,32 +12,15 @@
 
 namespace Composer\Installer;
 
-/**
- * The Installer Events.
- *
- * @author François Pluchino <francois.pluchino@gmail.com>
- */
 class InstallerEvents
 {
     /**
-     * The PRE_DEPENDENCIES_SOLVING event occurs as a installer begins
-     * resolve operations.
-     *
-     * The event listener method receives a
-     * 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 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 POST_DEPENDENCIES_SOLVING = 'post-dependencies-solving';
+    const PRE_OPERATIONS_EXEC = 'pre-operations-exec';
 }

+ 11 - 0
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';
 }

+ 158 - 0
src/Composer/Plugin/PrePoolCreateEvent.php

@@ -0,0 +1,158 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <j.boggiano@seld.be>
+ */
+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;
+    }
+}

+ 3 - 2
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) {

+ 2 - 6
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()

+ 4 - 11
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());
     }
 }