瀏覽代碼

Merge branch 'master' of https://github.com/composer/composer

digitalkaoz 13 年之前
父節點
當前提交
7b982a1409

+ 4 - 5
bin/compile

@@ -1,11 +1,10 @@
 #!/usr/bin/env php
 <?php
 
-if (!@include __DIR__.'/../vendor/.composer/autoload.php') {
-    die('You must set up the project dependencies, run the following commands:
-wget http://getcomposer.org/composer.phar
-php composer.phar install
-');
+if ((!@include __DIR__.'/../../../.composer/autoload.php') && (!@include __DIR__.'/../vendor/.composer/autoload.php')) {
+    die('You must set up the project dependencies, run the following commands:'.PHP_EOL.
+        'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
+        'php composer.phar install'.PHP_EOL);
 }
 
 use Composer\Compiler;

+ 4 - 5
bin/composer

@@ -1,11 +1,10 @@
 #!/usr/bin/env php
 <?php
 
-if (!@include __DIR__.'/../vendor/.composer/autoload.php') {
-    die('You must set up the project dependencies, run the following commands:
-wget http://getcomposer.org/composer.phar
-php composer.phar install
-');
+if ((!@include __DIR__.'/../../../.composer/autoload.php') && (!@include __DIR__.'/../vendor/.composer/autoload.php')) {
+    die('You must set up the project dependencies, run the following commands:'.PHP_EOL.
+        'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
+        'php composer.phar install'.PHP_EOL);
 }
 
 use Composer\Console\Application;

+ 66 - 0
doc/faqs/scripts.md

@@ -0,0 +1,66 @@
+# Scripts
+
+## What is a script?
+
+A script is a callback (defined as a static method) that will be called
+when the event it listens on is triggered.
+
+**Scripts are only executed on the root package, not on the dependencies
+that are installed.**
+
+
+## Event types
+
+- **pre-install-cmd**: occurs before the install command is executed.
+- **post-install-cmd**: occurs after the install command is executed.
+- **pre-update-cmd**: occurs before the update command is executed.
+- **post-update-cmd**: occurs after the update command is executed.
+- **pre-package-install**: occurs before a package is installed.
+- **post-package-install**: occurs after a package is installed.
+- **pre-package-update**: occurs before a package is updated.
+- **post-package-update**: occurs after a package is updated.
+- **pre-package-uninstall**: occurs before a package has been uninstalled.
+- **post-package-uninstall**: occurs after a package has been uninstalled.
+
+
+## Defining scripts
+
+Scripts are defined by adding the `scripts` key to a project's `composer.json`.
+
+They are specified as an array of classes and static method names.
+
+The classes used as scripts must be autoloadable via Composer's autoload
+functionality.
+
+Script definition example:
+
+```json
+{
+    "scripts": {
+        "post-update-cmd": "MyVendor\\MyClass::postUpdate",
+        "post-package-install": ["MyVendor\\MyClass::postPackageInstall"]
+    }
+}
+```
+
+Script listener example:
+
+```php
+<?php
+
+namespace MyVendor;
+
+class MyClass
+{
+    public static function postUpdate($event)
+    {
+        // do stuff
+    }
+
+    public static function postPackageInstall($event)
+    {
+        $installedPackage = $event->getOperation()->getPackage();
+        // do stuff
+    }
+}
+```

+ 22 - 1
src/Composer/Command/InstallCommand.php

@@ -12,6 +12,8 @@
 
 namespace Composer\Command;
 
+use Composer\Script\ScriptEvents;
+use Composer\Script\EventDispatcher;
 use Composer\Autoload\AutoloadGenerator;
 use Composer\DependencyResolver;
 use Composer\DependencyResolver\Pool;
@@ -23,6 +25,8 @@ use Composer\Repository\PlatformRepository;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
+use Composer\DependencyResolver\Operation\InstallOperation;
+use Composer\DependencyResolver\Solver;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
@@ -65,6 +69,8 @@ EOT
         $dryRun = (Boolean) $input->getOption('dry-run');
         $verbose = $dryRun || $input->getOption('verbose');
         $composer = $this->getComposer();
+        $io = $this->getApplication()->getIO();
+        $dispatcher = new EventDispatcher($this->getComposer(), $io);
 
         if ($preferSource) {
             $composer->getDownloadManager()->setPreferSource(true);
@@ -82,6 +88,12 @@ EOT
             $pool->addRepository($repository);
         }
 
+        // dispatch pre event
+        if (!$dryRun) {
+            $eventName = $update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;
+            $dispatcher->dispatchCommandEvent($eventName);
+        }
+
         // creating requirements request
         $request = new Request($pool);
         if ($update) {
@@ -132,7 +144,10 @@ EOT
         // TODO this belongs in the solver, but this will do for now to report top-level deps missing at least
         foreach ($request->getJobs() as $job) {
             if ('install' === $job['cmd']) {
-                foreach ($installedRepo->getPackages() as $package) {
+                foreach ($installedRepo->getPackages() as $package ) {
+                    if ($installedRepo->hasPackage($package) && !$package->isPlatform() && !$installationManager->isPackageInstalled($package)) {
+                        $operations[$job['packageName']] = new InstallOperation($package, Solver::RULE_PACKAGE_NOT_EXIST);
+                    }
                     if (in_array($job['packageName'], $package->getNames())) {
                         continue 2;
                     }
@@ -162,7 +177,9 @@ EOT
                 $output->writeln((string) $operation);
             }
             if (!$dryRun) {
+                $dispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
                 $installationManager->execute($operation);
+                $dispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
             }
         }
 
@@ -177,6 +194,10 @@ EOT
             $output->writeln('<info>Generating autoload files</info>');
             $generator = new AutoloadGenerator;
             $generator->dump($localRepo, $composer->getPackage(), $installationManager, $installationManager->getVendorPath().'/.composer');
+
+            // dispatch post event
+            $eventName = $update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
+            $dispatcher->dispatchCommandEvent($eventName);
         }
     }
 

+ 1 - 1
src/Composer/Command/SearchCommand.php

@@ -26,7 +26,7 @@ class SearchCommand extends Command
     {
         $this
             ->setName('search')
-            ->setDescription('search for packages')
+            ->setDescription('Search for packages')
             ->setDefinition(array(
                 new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'),
             ))

+ 1 - 1
src/Composer/Command/ShowCommand.php

@@ -28,7 +28,7 @@ class ShowCommand extends Command
     {
         $this
             ->setName('show')
-            ->setDescription('show package details')
+            ->setDescription('Show package details')
             ->setDefinition(array(
                 new InputArgument('package', InputArgument::REQUIRED, 'the package to inspect'),
                 new InputArgument('version', InputArgument::OPTIONAL, 'the version'),

+ 1 - 1
src/Composer/Command/ValidateCommand.php

@@ -28,7 +28,7 @@ class ValidateCommand extends Command
     {
         $this
             ->setName('validate')
-            ->setDescription('validates a composer.json')
+            ->setDescription('Validates a composer.json')
             ->setDefinition(array(
                 new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file', './composer.json')
             ))

+ 9 - 2
src/Composer/Installer/LibraryInstaller.php

@@ -72,7 +72,7 @@ class LibraryInstaller implements InstallerInterface
      */
     public function isInstalled(PackageInterface $package)
     {
-        return $this->repository->hasPackage($package);
+        return $this->repository->hasPackage($package) && is_readable($this->getInstallPath($package));
     }
 
     /**
@@ -82,9 +82,16 @@ class LibraryInstaller implements InstallerInterface
     {
         $downloadPath = $this->getInstallPath($package);
 
+        // remove the binaries if it appears the package files are missing
+        if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) {
+            $this->removeBinaries($package);
+        }
+
         $this->downloadManager->download($package, $downloadPath);
         $this->installBinaries($package);
-        $this->repository->addPackage(clone $package);
+        if (!$this->repository->hasPackage($package)) {
+            $this->repository->addPackage(clone $package);
+        }
     }
 
     /**

+ 11 - 0
src/Composer/Package/BasePackage.php

@@ -15,6 +15,7 @@ namespace Composer\Package;
 use Composer\Package\LinkConstraint\LinkConstraintInterface;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Repository\RepositoryInterface;
+use Composer\Repository\PlatformRepository;
 
 /**
  * Base class for packages providing name storage and default match implementation
@@ -134,6 +135,16 @@ abstract class BasePackage implements PackageInterface
         $this->repository = $repository;
     }
 
+    /**
+     * checks if this package is a platform package
+     *
+     * @return boolean
+     */
+    public function isPlatform()
+    {
+        return $this->getRepository() instanceof PlatformRepository;
+    }
+
     /**
      * Returns package unique name, constructed from name, version and release type.
      *

+ 7 - 0
src/Composer/Package/Loader/ArrayLoader.php

@@ -74,6 +74,13 @@ class ArrayLoader
             $package->setBinaries($config['bin']);
         }
 
+        if (isset($config['scripts']) && is_array($config['scripts'])) {
+            foreach ($config['scripts'] as $event => $listeners) {
+                $config['scripts'][$event]= (array) $listeners;
+            }
+            $package->setScripts($config['scripts']);
+        }
+
         if (!empty($config['description']) && is_string($config['description'])) {
             $package->setDescription($config['description']);
         }

+ 17 - 0
src/Composer/Package/MemoryPackage.php

@@ -40,6 +40,7 @@ class MemoryPackage extends BasePackage
     protected $homepage;
     protected $extra = array();
     protected $binaries = array();
+    protected $scripts = array();
 
     protected $requires = array();
     protected $conflicts = array();
@@ -128,6 +129,22 @@ class MemoryPackage extends BasePackage
         return $this->binaries;
     }
 
+    /**
+     * @param array $scripts
+     */
+    public function setScripts(array $scripts)
+    {
+        $this->scripts = $scripts;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getScripts()
+    {
+        return $this->scripts;
+    }
+
     /**
      * {@inheritDoc}
      */

+ 7 - 0
src/Composer/Package/PackageInterface.php

@@ -152,6 +152,13 @@ interface PackageInterface
      */
     function getDistSha1Checksum();
 
+    /**
+     * Returns the scripts of this package
+     *
+     * @return array array('script name' => array('listeners'))
+     */
+    function getScripts();
+
     /**
      * Returns the version of this package
      *

+ 1 - 0
src/Composer/Repository/Vcs/VcsDriver.php

@@ -79,6 +79,7 @@ abstract class VcsDriver
         } else if (null !== $this->io->getLastUsername()) {
             $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword());
             $params['http'] = array('header' => "Authorization: Basic $authStr\r\n");
+            $this->io->setAuthorization($this->url, $this->io->getLastUsername(), $this->io->getLastPassword());
         }
 
         $ctx = stream_context_create($params);

+ 26 - 0
src/Composer/Script/CommandEvent.php

@@ -0,0 +1,26 @@
+<?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\Script;
+
+use Composer\Composer;
+use Composer\IO\IOInterface;
+use Composer\Package\PackageInterface;
+
+/**
+ * The Command Event.
+ *
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
+ */
+class CommandEvent extends Event
+{
+}

+ 83 - 0
src/Composer/Script/Event.php

@@ -0,0 +1,83 @@
+<?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\Script;
+
+use Composer\Composer;
+use Composer\IO\IOInterface;
+
+/**
+ * The base event class
+ *
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
+ */
+class Event
+{
+    /**
+     * @var string This event's name
+     */
+    private $name;
+
+    /**
+     * @var Composer The composer instance
+     */
+    private $composer;
+
+    /**
+     * @var IOInterface The IO instance
+     */
+    private $io;
+
+    /**
+     * Constructor.
+     *
+     * @param string      $name     The event name
+     * @param Composer    $composer The composer objet
+     * @param IOInterface $io       The IOInterface object
+     */
+    public function __construct($name, Composer $composer, IOInterface $io)
+    {
+        $this->name = $name;
+        $this->composer = $composer;
+        $this->io = $io;
+    }
+
+    /**
+     * Returns the event's name.
+     *
+     * @return string The event name
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Returns the composer instance.
+     *
+     * @return Composer
+     */
+    public function getComposer()
+    {
+        return $this->composer;
+    }
+
+    /**
+     * Returns the IO instance.
+     *
+     * @return IOInterface
+     */
+    public function getIO()
+    {
+        return $this->io;
+    }
+}

+ 120 - 0
src/Composer/Script/EventDispatcher.php

@@ -0,0 +1,120 @@
+<?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\Script;
+
+use Composer\Json\JsonFile;
+use Composer\Repository\FilesystemRepository;
+use Composer\Autoload\ClassLoader;
+use Composer\Package\PackageInterface;
+use Composer\IO\IOInterface;
+use Composer\Composer;
+use Composer\DependencyResolver\Operation\OperationInterface;
+
+/**
+ * The Event Dispatcher.
+ *
+ * Example in command:
+ *     $dispatcher = new EventDispatcher($this->getComposer(), $this->getApplication()->getIO());
+ *     // ...
+ *     $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD);
+ *     // ...
+ *
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class EventDispatcher
+{
+    protected $composer;
+    protected $io;
+    protected $loader;
+
+    /**
+     * Constructor.
+     *
+     * @param Composer    $composer The composer instance
+     * @param IOInterface $io       The IOInterface instance
+     */
+    public function __construct(Composer $composer, IOInterface $io)
+    {
+        $this->composer = $composer;
+        $this->io = $io;
+        $this->loader = new ClassLoader();
+        $this->loader->register();
+    }
+
+    /**
+     * Dispatch a package event.
+     *
+     * @param string $eventName The constant in ScriptEvents
+     * @param OperationInterface $operation The package being installed/updated/removed
+     */
+    public function dispatchPackageEvent($eventName, OperationInterface $operation)
+    {
+        $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $operation));
+    }
+
+    /**
+     * Dispatch a command event.
+     *
+     * @param string $eventName The constant in ScriptEvents
+     */
+    public function dispatchCommandEvent($eventName)
+    {
+        $this->doDispatch(new CommandEvent($eventName, $this->composer, $this->io));
+    }
+
+    /**
+     * Triggers the listeners of an event.
+     *
+     * @param Event $event The event object to pass to the event handlers/listeners.
+     */
+    protected function doDispatch(Event $event)
+    {
+        $listeners = $this->getListeners($event);
+
+        foreach ($listeners as $callable) {
+            $className = substr($callable, 0, strpos($callable, '::'));
+            $methodName = substr($callable, strpos($callable, '::') + 2);
+
+            if (!class_exists($className)) {
+                throw new \UnexpectedValueException('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script');
+            }
+            if (!is_callable($callable)) {
+                throw new \UnexpectedValueException('Method '.$callable.' is not callable, can not call '.$event->getName().' script');
+            }
+
+            $className::$methodName($event);
+        }
+    }
+
+    /**
+     * @param Event $event Event object
+     * @return array Listeners
+     */
+    protected function getListeners(Event $event)
+    {
+        $package = $this->composer->getPackage();
+        $scripts = $package->getScripts();
+        $autoload = $package->getAutoload();
+
+        // get namespaces in composer.json project
+        if (!$this->loader->getPrefixes() && isset($autoload['psr-0'])) {
+            krsort($autoload['psr-0']);
+            foreach ($autoload['psr-0'] as $ns => $path) {
+                $this->loader->add($ns, rtrim(getcwd().'/'.$path, '/'));
+            }
+        }
+
+        return isset($scripts[$event->getName()]) ? $scripts[$event->getName()] : array();
+    }
+}

+ 54 - 0
src/Composer/Script/PackageEvent.php

@@ -0,0 +1,54 @@
+<?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\Script;
+
+use Composer\Composer;
+use Composer\IO\IOInterface;
+use Composer\DependencyResolver\Operation\OperationInterface;
+
+/**
+ * The Package Event.
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class PackageEvent extends Event
+{
+    /**
+     * @var OperationInterface The package instance
+     */
+    private $operation;
+
+    /**
+     * Constructor.
+     *
+     * @param string      $name     The event name
+     * @param Composer    $composer The composer objet
+     * @param IOInterface $io       The IOInterface object
+     * @param OperationInterface $operation The operation object
+     */
+    public function __construct($name, Composer $composer, IOInterface $io, OperationInterface $operation)
+    {
+        parent::__construct($name, $composer, $io);
+        $this->operation = $operation;
+    }
+
+    /**
+     * Returns the package instance.
+     *
+     * @return OperationInterface
+     */
+    public function getOperation()
+    {
+        return $this->operation;
+    }
+}

+ 112 - 0
src/Composer/Script/ScriptEvents.php

@@ -0,0 +1,112 @@
+<?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\Script;
+
+/**
+ * The Script Events.
+ *
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class ScriptEvents
+{
+    /**
+     * The PRE_INSTALL_CMD event occurs before the install command is executed.
+     *
+     * The event listener method receives a Composer\Script\CommandEvent instance.
+     *
+     * @var string
+     */
+    const PRE_INSTALL_CMD = 'pre-install-cmd';
+
+    /**
+     * The POST_INSTALL_CMD event occurs after the install command is executed.
+     *
+     * The event listener method receives a Composer\Script\CommandEvent instance.
+     *
+     * @var string
+     */
+    const POST_INSTALL_CMD = 'post-install-cmd';
+
+    /**
+     * The PRE_UPDATE_CMD event occurs before the update command is executed.
+     *
+     * The event listener method receives a Composer\Script\CommandEvent instance.
+     *
+     * @var string
+     */
+    const PRE_UPDATE_CMD = 'pre-update-cmd';
+
+    /**
+     * The POST_UPDATE_CMD event occurs after the update command is executed.
+     *
+     * The event listener method receives a Composer\Script\CommandEvent instance.
+     *
+     * @var string
+     */
+    const POST_UPDATE_CMD = 'post-update-cmd';
+
+    /**
+     * The PRE_PACKAGE_INSTALL event occurs before a package is installed.
+     *
+     * The event listener method receives a Composer\Script\PackageEvent instance.
+     *
+     * @var string
+     */
+    const PRE_PACKAGE_INSTALL = 'pre-package-install';
+
+    /**
+     * The POST_PACKAGE_INSTALL event occurs after a package is installed.
+     *
+     * The event listener method receives a Composer\Script\PackageEvent instance.
+     *
+     * @var string
+     */
+    const POST_PACKAGE_INSTALL = 'post-package-install';
+
+    /**
+     * The PRE_PACKAGE_UPDATE event occurs before a package is updated.
+     *
+     * The event listener method receives a Composer\Script\PackageEvent instance.
+     *
+     * @var string
+     */
+    const PRE_PACKAGE_UPDATE = 'pre-package-update';
+
+    /**
+     * The POST_PACKAGE_UPDATE event occurs after a package is updated.
+     *
+     * The event listener method receives a Composer\Script\PackageEvent instance.
+     *
+     * @var string
+     */
+    const POST_PACKAGE_UPDATE = 'post-package-update';
+
+    /**
+     * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled.
+     *
+     * The event listener method receives a Composer\Script\PackageEvent instance.
+     *
+     * @var string
+     */
+    const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall';
+
+    /**
+     * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled.
+     *
+     * The event listener method receives a Composer\Script\PackageEvent instance.
+     *
+     * @var string
+     */
+    const POST_PACKAGE_UNINSTALL = 'post-package-uninstall';
+}

+ 6 - 0
tests/bootstrap.php

@@ -10,6 +10,12 @@
  * file that was distributed with this source code.
  */
 
+if ((!$loader = @include __DIR__.'/../../../.composer/autoload.php') && (!$loader = @include __DIR__.'/../vendor/.composer/autoload.php')) {
+    die('You must set up the project dependencies, run the following commands:'.PHP_EOL.
+        'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
+        'php composer.phar install'.PHP_EOL);
+}
+
 $loader = require __DIR__.'/../vendor/.composer/autoload.php';
 $loader->add('Composer\Test', __DIR__);
 $loader->register();