Browse Source

Merge remote-tracking branch 'composer/master'

Mark Ingman 8 years ago
parent
commit
0a3145821f

+ 15 - 6
doc/articles/scripts.md

@@ -22,15 +22,19 @@ Composer fires the following named events during its execution process:
 
 ### Command Events
 
-- **pre-install-cmd**: occurs before the `install` command is executed with a lock file present.
-- **post-install-cmd**: occurs after the `install` command has been executed with a lock file present.
-- **pre-update-cmd**: occurs before the `update` command is executed, or before the `install` command is executed without a lock file present.
-- **post-update-cmd**: occurs after the `update` command has been executed, or after the `install` command has been executed without a lock file present.
+- **pre-install-cmd**: occurs before the `install` command is executed with a
+  lock file present.
+- **post-install-cmd**: occurs after the `install` command has been executed
+  with a lock file present.
+- **pre-update-cmd**: occurs before the `update` command is executed, or before
+  the `install` command is executed without a lock file present.
+- **post-update-cmd**: occurs after the `update` command has been executed, or
+  after the `install` command has been executed without a lock file present.
 - **post-status-cmd**: occurs after the `status` command has been executed.
 - **pre-archive-cmd**: occurs before the `archive` command is executed.
 - **post-archive-cmd**: occurs after the `archive` command has been executed.
-- **pre-autoload-dump**: occurs before the autoloader is dumped, either
-  during `install`/`update`, or via the `dump-autoload` command.
+- **pre-autoload-dump**: occurs before the autoloader is dumped, either during
+  `install`/`update`, or via the `dump-autoload` command.
 - **post-autoload-dump**: occurs after the autoloader has been dumped, either
   during `install`/`update`, or via the `dump-autoload` command.
 - **post-root-package-install**: occurs after the root package has been
@@ -150,6 +154,11 @@ class MyClass
 }
 ```
 
+**Note:** During a composer install or update process, a variable named
+`COMPOSER_DEV_MODE` will be added to the environment. If the command was run
+with the `--no-dev` flag, this variable will be set to 0, otherwise it will be
+set to 1.
+
 ## Event classes
 
 When an event is fired, your PHP callback receives as first argument a

+ 7 - 7
doc/faqs/how-to-install-composer-programmatically.md

@@ -9,21 +9,21 @@ An alternative is to use this script which only works with unix utils:
 ```bash
 #!/bin/sh
 
-EXPECTED_SIGNATURE=$(wget https://composer.github.io/installer.sig -O - -q)
+EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig)
 php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
 ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');")
 
-if [ "$EXPECTED_SIGNATURE" = "$ACTUAL_SIGNATURE" ]
+if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
 then
-    php composer-setup.php --quiet
-    RESULT=$?
-    rm composer-setup.php
-    exit $RESULT
-else
     >&2 echo 'ERROR: Invalid installer signature'
     rm composer-setup.php
     exit 1
 fi
+
+php composer-setup.php --quiet
+RESULT=$?
+rm composer-setup.php
+exit $RESULT
 ```
 
 The script will exit with 1 in case of failure, or 0 on success, and is quiet

+ 65 - 14
res/composer-schema.json

@@ -77,32 +77,44 @@
         "require": {
             "type": "object",
             "description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.",
-            "additionalProperties": true
+            "additionalProperties": {
+                "type": "string"
+            }
         },
         "replace": {
             "type": "object",
             "description": "This is a hash of package name (keys) and version constraints (values) that can be replaced by this package.",
-            "additionalProperties": true
+            "additionalProperties": {
+                "type": "string"
+            }
         },
         "conflict": {
             "type": "object",
             "description": "This is a hash of package name (keys) and version constraints (values) that conflict with this package.",
-            "additionalProperties": true
+            "additionalProperties": {
+                "type": "string"
+            }
         },
         "provide": {
             "type": "object",
             "description": "This is a hash of package name (keys) and version constraints (values) that this package provides in addition to this package's name.",
-            "additionalProperties": true
+            "additionalProperties": {
+                "type": "string"
+            }
         },
         "require-dev": {
             "type": "object",
             "description": "This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).",
-            "additionalProperties": true
+            "additionalProperties": {
+                "type": "string"
+            }
         },
         "suggest": {
             "type": "object",
             "description": "This is a hash of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).",
-            "additionalProperties": true
+            "additionalProperties": {
+                "type": "string"
+            }
         },
         "config": {
             "type": "object",
@@ -134,12 +146,16 @@
                 "github-oauth": {
                     "type": "object",
                     "description": "A hash of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": "string"
+                    }
                 },
                 "gitlab-oauth": {
                     "type": "object",
                     "description": "A hash of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":\"<token>\"}.",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": "string"
+                    }
                 },
                 "gitlab-token": {
                     "type": "object",
@@ -165,7 +181,20 @@
                 "http-basic": {
                     "type": "object",
                     "description": "A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": "object",
+                        "required": ["username", "password"],
+                        "properties": {
+                            "username": {
+                                "type": "string",
+                                "description": "The username used for HTTP Basic authentication"
+                            },
+                            "password": {
+                                "type": "string",
+                                "description": "The password used for HTTP Basic authentication"
+                            }
+                        }
+                    }
                 },
                 "store-auths": {
                     "type": ["string", "boolean"],
@@ -174,7 +203,9 @@
                 "platform": {
                     "type": "object",
                     "description": "This is a hash of package name (keys) and version (values) that will be used to mock the platform packages on this machine.",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": "string"
+                    }
                 },
                 "vendor-dir": {
                     "type": "string",
@@ -280,12 +311,22 @@
                 "psr-0": {
                     "type": "object",
                     "description": "This is a hash of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": ["string", "array"],
+                        "items": {
+                            "type": "string"
+                        }
+                    }
                 },
                 "psr-4": {
                     "type": "object",
                     "description": "This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": ["string", "array"],
+                        "items": {
+                            "type": "string"
+                        }
+                    }
                 },
                 "classmap": {
                     "type": "array",
@@ -308,12 +349,22 @@
                 "psr-0": {
                     "type": "object",
                     "description": "This is a hash of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": ["string", "array"],
+                        "items": {
+                            "type": "string"
+                        }
+                    }
                 },
                 "psr-4": {
                     "type": "object",
                     "description": "This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": ["string", "array"],
+                        "items": {
+                            "type": "string"
+                        }
+                    }
                 },
                 "classmap": {
                     "type": "array",

+ 3 - 0
src/Composer/IO/ConsoleIO.php

@@ -206,6 +206,9 @@ class ConsoleIO extends BaseIO
         // write the new message
         $this->doWrite($messages, false, $stderr, $verbosity);
 
+        // In cmd.exe on Win8.1 (possibly 10?), the line can not be cleared, so we need to
+        // track the length of previous output and fill it with spaces to make sure the line is cleared.
+        // See https://github.com/composer/composer/pull/5836 for more details
         $fill = $size - strlen(strip_tags($messages));
         if ($fill > 0) {
             // whitespace whatever has left

+ 35 - 0
src/Composer/Installer.php

@@ -292,6 +292,9 @@ class Installer
             }
 
             if ($this->runScripts) {
+                $devMode = (int) $this->devMode;
+                putenv("COMPOSER_DEV_MODE=$devMode");
+
                 // dispatch post event
                 $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
                 $this->eventDispatcher->dispatchScript($eventName, $this->devMode);
@@ -493,6 +496,38 @@ class Installer
             $devPackages = null;
         }
 
+        if ($operations) {
+            $installs = $updates = $uninstalls = array();
+            foreach ($operations as $operation) {
+                if ($operation instanceof InstallOperation) {
+                    $installs[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion();
+                } elseif ($operation instanceof UpdateOperation) {
+                    $updates[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion();
+                } elseif ($operation instanceof UninstallOperation) {
+                    $uninstalls[] = $operation->getPackage()->getPrettyName();
+                }
+            }
+
+            $this->io->writeError(
+                sprintf("<info>Package operations: %d install%s, %d update%s, %d removal%s</info>",
+                count($installs),
+                1 === count($installs) ? '' : 's',
+                count($updates),
+                1 === count($updates) ? '' : 's',
+                count($uninstalls),
+                1 === count($uninstalls) ? '' : 's')
+            );
+            if ($installs) {
+                $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE);
+            }
+            if ($updates) {
+                $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE);
+            }
+            if ($uninstalls) {
+                $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE);
+            }
+        }
+
         foreach ($operations as $operation) {
             // collect suggestions
             if ('install' === $operation->getJobType()) {

+ 1 - 1
src/Composer/Repository/Vcs/HgDriver.php

@@ -195,7 +195,7 @@ class HgDriver extends VcsDriver
      */
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
-        if (preg_match('#(^(?:https?|ssh)://(?:[^@]@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) {
+        if (preg_match('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) {
             return true;
         }
 

+ 11 - 1
src/Composer/Util/ProcessExecutor.php

@@ -99,7 +99,17 @@ class ProcessExecutor
             return;
         }
 
-        echo $buffer;
+        if (null === $this->io) {
+            echo $buffer;
+
+            return;
+        }
+
+        if (Process::ERR === $type) {
+            $this->io->writeError($buffer);
+        } else {
+            $this->io->write($buffer);
+        }
     }
 
     public static function getTimeout()

+ 5 - 3
tests/Composer/Test/EventDispatcher/EventDispatcherTest.php

@@ -340,7 +340,7 @@ class EventDispatcherTest extends TestCase
             ->setConstructorArgs(array(
                 $this->createComposerInstance(),
                 $io = $this->getMock('Composer\IO\IOInterface'),
-                new ProcessExecutor,
+                new ProcessExecutor($io),
             ))
             ->setMethods(array('getListeners'))
             ->getMock();
@@ -354,9 +354,11 @@ class EventDispatcherTest extends TestCase
             ->method('writeError')
             ->with($this->equalTo('> echo foo'));
 
-        ob_start();
+        $io->expects($this->once())
+            ->method('write')
+            ->with($this->equalTo('foo'.PHP_EOL));
+
         $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
-        $this->assertEquals('foo', trim(ob_get_clean()));
     }
 
     public function testDispatcherOutputsErrorOnFailedCommand()

+ 1 - 0
tests/Composer/Test/Fixtures/installer/abandoned-listed.test

@@ -26,6 +26,7 @@ install
 --EXPECT-OUTPUT--
 Loading composer repositories with package information
 Updating dependencies (including require-dev)
+Package operations: 2 installs, 0 updates, 0 removals
 <warning>Package a/a is abandoned, you should avoid using it. No replacement was suggested.</warning>
 <warning>Package c/c is abandoned, you should avoid using it. Use b/b instead.</warning>
 Writing lock file

+ 1 - 0
tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test

@@ -36,6 +36,7 @@ update a b --with-dependencies
 --EXPECT-OUTPUT--
 Loading composer repositories with package information
 Updating dependencies (including require-dev)
+Package operations: 0 installs, 2 updates, 0 removals
 Writing lock file
 Generating autoload files
 

+ 1 - 0
tests/Composer/Test/Fixtures/installer/suggest-installed.test

@@ -21,6 +21,7 @@ install
 --EXPECT-OUTPUT--
 Loading composer repositories with package information
 Updating dependencies (including require-dev)
+Package operations: 2 installs, 0 updates, 0 removals
 Writing lock file
 Generating autoload files
 

+ 1 - 0
tests/Composer/Test/Fixtures/installer/suggest-prod.test

@@ -19,6 +19,7 @@ install --no-dev
 --EXPECT-OUTPUT--
 Loading composer repositories with package information
 Updating dependencies
+Package operations: 1 install, 0 updates, 0 removals
 Writing lock file
 Generating autoload files
 

+ 1 - 0
tests/Composer/Test/Fixtures/installer/suggest-replaced.test

@@ -21,6 +21,7 @@ install
 --EXPECT-OUTPUT--
 Loading composer repositories with package information
 Updating dependencies (including require-dev)
+Package operations: 2 installs, 0 updates, 0 removals
 Writing lock file
 Generating autoload files
 

+ 1 - 0
tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test

@@ -19,6 +19,7 @@ install
 --EXPECT-OUTPUT--
 Loading composer repositories with package information
 Updating dependencies (including require-dev)
+Package operations: 1 install, 0 updates, 0 removals
 a/a suggests installing b/b (an obscure reason)
 Writing lock file
 Generating autoload files

+ 8 - 0
tests/Composer/Test/Json/ComposerSchemaTest.php

@@ -44,6 +44,14 @@ class ComposerSchemaTest extends \PHPUnit_Framework_TestCase
         $this->assertTrue($this->check($json));
     }
 
+    public function testRequireTypes()
+    {
+        $json = '{"name": "name", "description": "description", "require": {"a": ["b"]} }';
+        $this->assertEquals(array(
+            array('property' => 'require.a', 'message' => 'Array value found, but a string is required', 'constraint' => 'type'),
+        ), $this->check($json));
+    }
+
     public function testMinimumStabilityValues()
     {
         $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "" }';

+ 69 - 0
tests/Composer/Test/Repository/Vcs/HgDriverTest.php

@@ -0,0 +1,69 @@
+<?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\Test\Repository\Vcs;
+
+use Composer\Repository\Vcs\HgDriver;
+use Composer\TestCase;
+use Composer\Util\Filesystem;
+use Composer\Config;
+
+class HgDriverTest extends TestCase
+{
+
+    /** @type \Composer\IO\IOInterface|\PHPUnit_Framework_MockObject_MockObject */
+    private $io;
+    /** @type Config */
+    private $config;
+    /** @type string */
+    private $home;
+
+    public function setUp()
+    {
+        $this->io = $this->getMock('Composer\IO\IOInterface');
+        $this->home = $this->getUniqueTmpDirectory();
+        $this->config = new Config();
+        $this->config->merge(array(
+            'config' => array(
+                'home' => $this->home,
+            ),
+        ));
+    }
+
+    public function tearDown()
+    {
+        $fs = new Filesystem;
+        $fs->removeDirectory($this->home);
+    }
+
+    /**
+     * @dataProvider supportsDataProvider
+     */
+    public function testSupports($repositoryUrl)
+    {
+        $this->assertTrue(
+            HgDriver::supports($this->io, $this->config, $repositoryUrl)
+        );
+    }
+
+    public function supportsDataProvider()
+    {
+        return array(
+            array('ssh://bitbucket.org/user/repo'),
+            array('ssh://hg@bitbucket.org/user/repo'),
+            array('ssh://user@bitbucket.org/user/repo'),
+            array('https://bitbucket.org/user/repo'),
+            array('https://user@bitbucket.org/user/repo'),
+        );
+    }
+
+}

+ 11 - 0
tests/Composer/Test/Util/ProcessExecutorTest.php

@@ -35,6 +35,17 @@ class ProcessExecutorTest extends TestCase
         $this->assertEquals("foo".PHP_EOL, $output);
     }
 
+    public function testUseIOIsNotNullAndIfNotCaptured()
+    {
+        $io = $this->getMock('Composer\IO\IOInterface');
+        $io->expects($this->once())
+            ->method('write')
+            ->with($this->equalTo('foo'.PHP_EOL));
+
+        $process = new ProcessExecutor($io);
+        $process->execute('echo foo');
+    }
+
     public function testExecuteCapturesStderr()
     {
         $process = new ProcessExecutor;