From d57e2b9ffb229c3828628253ee06982811f011eb Mon Sep 17 00:00:00 2001 From: Jan Prieser Date: Mon, 13 Jan 2014 17:14:12 +0100 Subject: [PATCH 01/40] added ZipArchiver to actually compress zip files --- src/Composer/Factory.php | 1 + src/Composer/Package/Archiver/ZipArchiver.php | 65 +++++++++++++++++++ .../Test/Package/Archiver/ZipArchiverTest.php | 64 ++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 src/Composer/Package/Archiver/ZipArchiver.php create mode 100644 tests/Composer/Test/Package/Archiver/ZipArchiverTest.php diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index f829cb459..1514dfbc2 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -386,6 +386,7 @@ class Factory } $am = new Archiver\ArchiveManager($dm); + $am->addArchiver(new Archiver\ZipArchiver); $am->addArchiver(new Archiver\PharArchiver); return $am; diff --git a/src/Composer/Package/Archiver/ZipArchiver.php b/src/Composer/Package/Archiver/ZipArchiver.php new file mode 100644 index 000000000..aaf42ed56 --- /dev/null +++ b/src/Composer/Package/Archiver/ZipArchiver.php @@ -0,0 +1,65 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use ZipArchive; + +/** + * @author Jan Prieser + */ +class ZipArchiver implements ArchiverInterface +{ + protected static $formats = array( + 'zip' => 1 + ); + + /** + * {@inheritdoc} + */ + public function archive($sources, $target, $format, array $excludes = array()) + { + $sources = realpath($sources); + $zip = new ZipArchive(); + $res = $zip->open($target, ZipArchive::CREATE); + if ($res === true) { + $files = new ArchivableFilesFinder($sources, $excludes); + foreach($files as $file) { + /** @var $file \SplFileInfo */ + $filepath = $file->getPath()."/".$file->getFilename(); + $localname = str_replace($sources."/", '', $filepath); + $zip->addFile($filepath, $localname); + } + if ($zip->close()) { + return $target; + } + } + $message = sprintf("Could not create archive '%s' from '%s': %s", + $target, + $sources, + $zip->getStatusString() + ); + throw new \RuntimeException($message); + } + + /** + * {@inheritdoc} + */ + public function supports($format, $sourceType) + { + return isset(static::$formats[$format]) && $this->compressionAvailable(); + } + + private function compressionAvailable() { + return class_exists('ZipArchive'); + } +} diff --git a/tests/Composer/Test/Package/Archiver/ZipArchiverTest.php b/tests/Composer/Test/Package/Archiver/ZipArchiverTest.php new file mode 100644 index 000000000..13168573a --- /dev/null +++ b/tests/Composer/Test/Package/Archiver/ZipArchiverTest.php @@ -0,0 +1,64 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Package\Archiver; + +use Composer\Package\Archiver\ZipArchiver; + +class ZipArchiverTest extends ArchiverTest +{ + + public function testZipArchive() + { + // Set up repository + $this->setupDummyRepo(); + $package = $this->setupPackage(); + $target = sys_get_temp_dir().'/composer_archiver_test.zip'; + + // Test archive + $archiver = new ZipArchiver(); + $archiver->archive($package->getSourceUrl(), $target, 'zip'); + $this->assertFileExists($target); + + unlink($target); + } + + /** + * Create a local dummy repository to run tests against! + */ + protected function setupDummyRepo() + { + $currentWorkDir = getcwd(); + chdir($this->testDir); + + $this->writeFile('file.txt', 'content', $currentWorkDir); + $this->writeFile('foo/bar/baz', 'content', $currentWorkDir); + $this->writeFile('foo/bar/ignoreme', 'content', $currentWorkDir); + $this->writeFile('x/baz', 'content', $currentWorkDir); + $this->writeFile('x/includeme', 'content', $currentWorkDir); + + chdir($currentWorkDir); + } + + protected function writeFile($path, $content, $currentWorkDir) + { + if (!file_exists(dirname($path))) { + mkdir(dirname($path), 0777, true); + } + + $result = file_put_contents($path, 'a'); + if (false === $result) { + chdir($currentWorkDir); + throw new \RuntimeException('Could not save file.'); + } + } +} From 0f099828586939274d1c20d5f1bcdbcd2b04f2eb Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Thu, 21 Jan 2016 16:18:00 +0100 Subject: [PATCH 02/40] add a test attempting to reproduce composer/composer#4795 --- .../installer/update-refs-issue-4795.test | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test diff --git a/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test b/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test new file mode 100644 index 000000000..c28fd57d5 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test @@ -0,0 +1,32 @@ +--TEST-- +Refs issue #4795 +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "A", "version": "1.0.0" }, + { "name": "A", "version": "1.1.0" }, + { "name": "B", "version": "1.0.0", "require": { "A": "~1.0" } }, + { "name": "B", "version": "1.1.0", "require": { "A": "1.1.0" } }, + { "name": "C", "version": "1.0.0", "require": { "A": "~1.0" } } + ] + } + ], + "require": { + "B": "~1.1", + "C": "~1.0" + } +} +--INSTALLED-- +[ + { "name": "A", "version": "1.0.0" }, + { "name": "B", "version": "1.0.0", "require": { "A": "~1.0" } }, + { "name": "C", "version": "1.0.0", "require": { "A": "~1.0" } } +] +--RUN-- +update B --with-dependencies +--EXPECT-- +Updating A (1.0.0) to A (1.1.0) +Updating B (1.0.0) to B (1.1.0) From 17d6b98707a6460582c25831a87163e1c05c7193 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Thu, 21 Jan 2016 18:40:33 +0100 Subject: [PATCH 03/40] add A to root requirements --- .../Test/Fixtures/installer/update-refs-issue-4795.test | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test b/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test index c28fd57d5..a359303bb 100644 --- a/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test +++ b/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test @@ -9,13 +9,14 @@ Refs issue #4795 { "name": "A", "version": "1.0.0" }, { "name": "A", "version": "1.1.0" }, { "name": "B", "version": "1.0.0", "require": { "A": "~1.0" } }, - { "name": "B", "version": "1.1.0", "require": { "A": "1.1.0" } }, + { "name": "B", "version": "1.1.0", "require": { "A": "~1.1" } }, { "name": "C", "version": "1.0.0", "require": { "A": "~1.0" } } ] } ], "require": { - "B": "~1.1", + "A": "~1.0", + "B": "~1.0", "C": "~1.0" } } From 3979abc638796b52edbf754290497a6cc58712f4 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Tue, 26 Jan 2016 22:05:35 +0000 Subject: [PATCH 04/40] Add warning if OpenSSL does not support TLSv1.2 or TLSv1.1 --- src/Composer/Command/DiagnoseCommand.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 2306b138a..c78c808e1 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -453,6 +453,10 @@ EOT $errors['openssl'] = true; } + if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) { + $warnings['openssl_version'] = true; + } + if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) { $warnings['apc_cli'] = true; } @@ -570,6 +574,16 @@ EOT $text .= " Composer works with 5.3.2+ for most people, but there might be edge case issues."; break; + case 'openssl_version': + // Attempt to parse version number out, fallback to whole string value. + $opensslVersion = trim(strstr(OPENSSL_VERSION_TEXT, ' ')); + $opensslVersion = substr($opensslVersion, 0, strpos($opensslVersion, ' ')); + $opensslVersion = $opensslVersion ? $opensslVersion : OPENSSL_VERSION_TEXT; + + $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1.".PHP_EOL; + $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above."; + break; + case 'xdebug_loaded': $text = "The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL; $text .= " Disabling it when using Composer is recommended."; From a48159b2838bcbd429ee57e31b848e5db26239a1 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Tue, 26 Jan 2016 23:39:39 +0100 Subject: [PATCH 05/40] Bail out if root package attempts to include itself. --- src/Composer/Package/Loader/RootPackageLoader.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 0fc7c49af..6f8d45f31 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -113,6 +113,9 @@ class RootPackageLoader extends ArrayLoader } } + if (isset($links[$config['name']])) + throw new \InvalidArgumentException(sprintf('Root package \'%s\' cannot require itself in its composer.json', $config['name'])); + $realPackage->setAliases($aliases); $realPackage->setStabilityFlags($stabilityFlags); $realPackage->setReferences($references); From 7b6ccde97afe1e85029dab144cc57224d2986d02 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 27 Jan 2016 09:09:29 +0100 Subject: [PATCH 06/40] Clarified error message and added braces. --- src/Composer/Package/Loader/RootPackageLoader.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 6f8d45f31..565ff39c4 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -113,8 +113,10 @@ class RootPackageLoader extends ArrayLoader } } - if (isset($links[$config['name']])) - throw new \InvalidArgumentException(sprintf('Root package \'%s\' cannot require itself in its composer.json', $config['name'])); + if (isset($links[$config['name']])) { + throw new \InvalidArgumentException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL . + 'Did you accidentally name your root package after an external package?', $config['name'])); + } $realPackage->setAliases($aliases); $realPackage->setStabilityFlags($stabilityFlags); From 0a25bb0bf39e5dc00edb62b7bae9dfa93c365a88 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 27 Jan 2016 08:57:28 +0000 Subject: [PATCH 07/40] Simplify for PHP 5.3 --- src/Composer/Command/DiagnoseCommand.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index c78c808e1..2b1927f6b 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -576,9 +576,8 @@ EOT case 'openssl_version': // Attempt to parse version number out, fallback to whole string value. - $opensslVersion = trim(strstr(OPENSSL_VERSION_TEXT, ' ')); - $opensslVersion = substr($opensslVersion, 0, strpos($opensslVersion, ' ')); - $opensslVersion = $opensslVersion ? $opensslVersion : OPENSSL_VERSION_TEXT; + $opensslVersion = strstr(trim(strstr(OPENSSL_VERSION_TEXT, ' ')), ' ', true); + $opensslVersion = $opensslVersion ?: OPENSSL_VERSION_TEXT; $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1.".PHP_EOL; $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above."; From e5fe3d8a3bf3e5048a5ec3ad4c6a7ea15792cf09 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 27 Jan 2016 10:04:45 +0100 Subject: [PATCH 08/40] Expanded InstallerTest to support expecting Exceptions by supplying "EXCEPTION" as "--EXPECT--" --- tests/Composer/Test/InstallerTest.php | 181 ++++++++++++++------------ 1 file changed, 97 insertions(+), 84 deletions(-) diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 5339b8ff3..f72c6d9d5 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -158,96 +158,109 @@ class InstallerTest extends TestCase ->method('writeError') ->will($this->returnCallback($callback)); - $composer = FactoryMock::create($io, $composerConfig); + // Prepare for exceptions + try { + $composer = FactoryMock::create($io, $composerConfig); - $jsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock(); - $jsonMock->expects($this->any()) - ->method('read') - ->will($this->returnValue($installed)); - $jsonMock->expects($this->any()) - ->method('exists') - ->will($this->returnValue(true)); + $jsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock(); + $jsonMock->expects($this->any()) + ->method('read') + ->will($this->returnValue($installed)); + $jsonMock->expects($this->any()) + ->method('exists') + ->will($this->returnValue(true)); - $repositoryManager = $composer->getRepositoryManager(); - $repositoryManager->setLocalRepository(new InstalledFilesystemRepositoryMock($jsonMock)); + $repositoryManager = $composer->getRepositoryManager(); + $repositoryManager->setLocalRepository(new InstalledFilesystemRepositoryMock($jsonMock)); - $lockJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock(); - $lockJsonMock->expects($this->any()) - ->method('read') - ->will($this->returnValue($lock)); - $lockJsonMock->expects($this->any()) - ->method('exists') - ->will($this->returnValue(true)); + $lockJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock(); + $lockJsonMock->expects($this->any()) + ->method('read') + ->will($this->returnValue($lock)); + $lockJsonMock->expects($this->any()) + ->method('exists') + ->will($this->returnValue(true)); - if ($expectLock) { - $actualLock = array(); - $lockJsonMock->expects($this->atLeastOnce()) - ->method('write') - ->will($this->returnCallback(function ($hash, $options) use (&$actualLock) { - // need to do assertion outside of mock for nice phpunit output - // so store value temporarily in reference for later assetion - $actualLock = $hash; - })); + if ($expectLock) { + $actualLock = array(); + $lockJsonMock->expects($this->atLeastOnce()) + ->method('write') + ->will($this->returnCallback(function ($hash, $options) use (&$actualLock) { + // need to do assertion outside of mock for nice phpunit output + // so store value temporarily in reference for later assetion + $actualLock = $hash; + })); + } + + $contents = json_encode($composerConfig); + $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), $contents); + $composer->setLocker($locker); + + $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); + $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator', array(), array($eventDispatcher)); + $composer->setAutoloadGenerator($autoloadGenerator); + $composer->setEventDispatcher($eventDispatcher); + + $installer = Installer::create($io, $composer); + + $application = new Application; + $application->get('install')->setCode(function ($input, $output) use ($installer) { + $installer + ->setDevMode(!$input->getOption('no-dev')) + ->setDryRun($input->getOption('dry-run')) + ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')); + + return $installer->run(); + }); + + $application->get('update')->setCode(function ($input, $output) use ($installer) { + $installer + ->setDevMode(!$input->getOption('no-dev')) + ->setUpdate(true) + ->setDryRun($input->getOption('dry-run')) + ->setUpdateWhitelist($input->getArgument('packages')) + ->setWhitelistDependencies($input->getOption('with-dependencies')) + ->setPreferStable($input->getOption('prefer-stable')) + ->setPreferLowest($input->getOption('prefer-lowest')) + ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')); + + return $installer->run(); + }); + + if (!preg_match('{^(install|update)\b}', $run)) { + throw new \UnexpectedValueException('The run command only supports install and update'); + } + + $application->setAutoExit(false); + $appOutput = fopen('php://memory', 'w+'); + $result = $application->run(new StringInput($run), new StreamOutput($appOutput)); + fseek($appOutput, 0); + $this->assertEquals($expectExitCode, $result, $output . stream_get_contents($appOutput)); + + if ($expectLock) { + unset($actualLock['hash']); + unset($actualLock['content-hash']); + unset($actualLock['_readme']); + $this->assertEquals($expectLock, $actualLock); + } + + $installationManager = $composer->getInstallationManager(); + $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace())); + + if ($expectOutput) { + $this->assertEquals(rtrim($expectOutput), rtrim($output)); + } } - - $contents = json_encode($composerConfig); - $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), $contents); - $composer->setLocker($locker); - - $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); - $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator', array(), array($eventDispatcher)); - $composer->setAutoloadGenerator($autoloadGenerator); - $composer->setEventDispatcher($eventDispatcher); - - $installer = Installer::create($io, $composer); - - $application = new Application; - $application->get('install')->setCode(function ($input, $output) use ($installer) { - $installer - ->setDevMode(!$input->getOption('no-dev')) - ->setDryRun($input->getOption('dry-run')) - ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')); - - return $installer->run(); - }); - - $application->get('update')->setCode(function ($input, $output) use ($installer) { - $installer - ->setDevMode(!$input->getOption('no-dev')) - ->setUpdate(true) - ->setDryRun($input->getOption('dry-run')) - ->setUpdateWhitelist($input->getArgument('packages')) - ->setWhitelistDependencies($input->getOption('with-dependencies')) - ->setPreferStable($input->getOption('prefer-stable')) - ->setPreferLowest($input->getOption('prefer-lowest')) - ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')); - - return $installer->run(); - }); - - if (!preg_match('{^(install|update)\b}', $run)) { - throw new \UnexpectedValueException('The run command only supports install and update'); - } - - $application->setAutoExit(false); - $appOutput = fopen('php://memory', 'w+'); - $result = $application->run(new StringInput($run), new StreamOutput($appOutput)); - fseek($appOutput, 0); - $this->assertEquals($expectExitCode, $result, $output . stream_get_contents($appOutput)); - - if ($expectLock) { - unset($actualLock['hash']); - unset($actualLock['content-hash']); - unset($actualLock['_readme']); - $this->assertEquals($expectLock, $actualLock); - } - - $installationManager = $composer->getInstallationManager(); - $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace())); - - if ($expectOutput) { - $this->assertEquals(rtrim($expectOutput), rtrim($output)); + catch(\Exception $e) { + // Exception was thrown during execution + if (!$expect || !$expectOutput) { + throw $e; + } + $this->assertEquals('EXCEPTION', rtrim($expect)); + $normalizedOutput = rtrim(str_replace("\n", PHP_EOL, $expectOutput)); + $this->assertEquals($normalizedOutput, rtrim($e->getMessage())); } + return; } public function getIntegrationTests() From bd241cb896a2205ec651e1651e942650d9ad59b8 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 27 Jan 2016 10:05:10 +0100 Subject: [PATCH 09/40] Included unit test for circular root dependencies. --- .../installer/install-self-from-root.test | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/Composer/Test/Fixtures/installer/install-self-from-root.test diff --git a/tests/Composer/Test/Fixtures/installer/install-self-from-root.test b/tests/Composer/Test/Fixtures/installer/install-self-from-root.test new file mode 100644 index 000000000..35a50754a --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/install-self-from-root.test @@ -0,0 +1,16 @@ +--TEST-- +Tries to require a package with the same name as the root package +--COMPOSER-- +{ + "name": "foo/bar", + "require": { + "foo/bar": "@dev" + } +} +--RUN-- +install +--EXPECT-OUTPUT-- +Root package 'foo/bar' cannot require itself in its composer.json +Did you accidentally name your root package after an external package? +--EXPECT-- +EXCEPTION From 639ee0701c86b7b38fe90b091358a063ce0f2d99 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 27 Jan 2016 13:19:08 +0100 Subject: [PATCH 10/40] Introduced more generic, less invasive way to test for exceptions in fixtures, more in line with how phpunit works. --- .../installer/install-self-from-root.test | 6 +- tests/Composer/Test/InstallerTest.php | 202 +++++++++--------- 2 files changed, 104 insertions(+), 104 deletions(-) diff --git a/tests/Composer/Test/Fixtures/installer/install-self-from-root.test b/tests/Composer/Test/Fixtures/installer/install-self-from-root.test index 35a50754a..ceeef8b7c 100644 --- a/tests/Composer/Test/Fixtures/installer/install-self-from-root.test +++ b/tests/Composer/Test/Fixtures/installer/install-self-from-root.test @@ -9,8 +9,8 @@ Tries to require a package with the same name as the root package } --RUN-- install ---EXPECT-OUTPUT-- +--EXPECT-EXIT-CODE-- +InvalidArgumentException +--EXPECT-- Root package 'foo/bar' cannot require itself in its composer.json Did you accidentally name your root package after an external package? ---EXPECT-- -EXCEPTION diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index f72c6d9d5..518a395e1 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -159,108 +159,108 @@ class InstallerTest extends TestCase ->will($this->returnCallback($callback)); // Prepare for exceptions - try { - $composer = FactoryMock::create($io, $composerConfig); - - $jsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock(); - $jsonMock->expects($this->any()) - ->method('read') - ->will($this->returnValue($installed)); - $jsonMock->expects($this->any()) - ->method('exists') - ->will($this->returnValue(true)); - - $repositoryManager = $composer->getRepositoryManager(); - $repositoryManager->setLocalRepository(new InstalledFilesystemRepositoryMock($jsonMock)); - - $lockJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock(); - $lockJsonMock->expects($this->any()) - ->method('read') - ->will($this->returnValue($lock)); - $lockJsonMock->expects($this->any()) - ->method('exists') - ->will($this->returnValue(true)); - - if ($expectLock) { - $actualLock = array(); - $lockJsonMock->expects($this->atLeastOnce()) - ->method('write') - ->will($this->returnCallback(function ($hash, $options) use (&$actualLock) { - // need to do assertion outside of mock for nice phpunit output - // so store value temporarily in reference for later assetion - $actualLock = $hash; - })); - } - - $contents = json_encode($composerConfig); - $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), $contents); - $composer->setLocker($locker); - - $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); - $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator', array(), array($eventDispatcher)); - $composer->setAutoloadGenerator($autoloadGenerator); - $composer->setEventDispatcher($eventDispatcher); - - $installer = Installer::create($io, $composer); - - $application = new Application; - $application->get('install')->setCode(function ($input, $output) use ($installer) { - $installer - ->setDevMode(!$input->getOption('no-dev')) - ->setDryRun($input->getOption('dry-run')) - ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')); - - return $installer->run(); - }); - - $application->get('update')->setCode(function ($input, $output) use ($installer) { - $installer - ->setDevMode(!$input->getOption('no-dev')) - ->setUpdate(true) - ->setDryRun($input->getOption('dry-run')) - ->setUpdateWhitelist($input->getArgument('packages')) - ->setWhitelistDependencies($input->getOption('with-dependencies')) - ->setPreferStable($input->getOption('prefer-stable')) - ->setPreferLowest($input->getOption('prefer-lowest')) - ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')); - - return $installer->run(); - }); - - if (!preg_match('{^(install|update)\b}', $run)) { - throw new \UnexpectedValueException('The run command only supports install and update'); - } - - $application->setAutoExit(false); - $appOutput = fopen('php://memory', 'w+'); - $result = $application->run(new StringInput($run), new StreamOutput($appOutput)); - fseek($appOutput, 0); - $this->assertEquals($expectExitCode, $result, $output . stream_get_contents($appOutput)); - - if ($expectLock) { - unset($actualLock['hash']); - unset($actualLock['content-hash']); - unset($actualLock['_readme']); - $this->assertEquals($expectLock, $actualLock); - } - - $installationManager = $composer->getInstallationManager(); - $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace())); - - if ($expectOutput) { - $this->assertEquals(rtrim($expectOutput), rtrim($output)); - } + if (is_int($expectExitCode) || ctype_digit($expectExitCode)) { + $expectExitCode = (int) $expectExitCode; + } else { + $normalizedOutput = rtrim(str_replace("\n", PHP_EOL, $expect)); + $this->setExpectedException($expectExitCode, $normalizedOutput); } - catch(\Exception $e) { - // Exception was thrown during execution - if (!$expect || !$expectOutput) { - throw $e; - } - $this->assertEquals('EXCEPTION', rtrim($expect)); - $normalizedOutput = rtrim(str_replace("\n", PHP_EOL, $expectOutput)); - $this->assertEquals($normalizedOutput, rtrim($e->getMessage())); + + // Create Composer mock object according to configuration + $composer = FactoryMock::create($io, $composerConfig); + + $jsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock(); + $jsonMock->expects($this->any()) + ->method('read') + ->will($this->returnValue($installed)); + $jsonMock->expects($this->any()) + ->method('exists') + ->will($this->returnValue(true)); + + $repositoryManager = $composer->getRepositoryManager(); + $repositoryManager->setLocalRepository(new InstalledFilesystemRepositoryMock($jsonMock)); + + $lockJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock(); + $lockJsonMock->expects($this->any()) + ->method('read') + ->will($this->returnValue($lock)); + $lockJsonMock->expects($this->any()) + ->method('exists') + ->will($this->returnValue(true)); + + if ($expectLock) { + $actualLock = array(); + $lockJsonMock->expects($this->atLeastOnce()) + ->method('write') + ->will($this->returnCallback(function ($hash, $options) use (&$actualLock) { + // need to do assertion outside of mock for nice phpunit output + // so store value temporarily in reference for later assetion + $actualLock = $hash; + })); + } + + $contents = json_encode($composerConfig); + $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), $contents); + $composer->setLocker($locker); + + $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); + $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator', array(), array($eventDispatcher)); + $composer->setAutoloadGenerator($autoloadGenerator); + $composer->setEventDispatcher($eventDispatcher); + + $installer = Installer::create($io, $composer); + + $application = new Application; + $application->get('install')->setCode(function ($input, $output) use ($installer) { + $installer + ->setDevMode(!$input->getOption('no-dev')) + ->setDryRun($input->getOption('dry-run')) + ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')); + + return $installer->run(); + }); + + $application->get('update')->setCode(function ($input, $output) use ($installer) { + $installer + ->setDevMode(!$input->getOption('no-dev')) + ->setUpdate(true) + ->setDryRun($input->getOption('dry-run')) + ->setUpdateWhitelist($input->getArgument('packages')) + ->setWhitelistDependencies($input->getOption('with-dependencies')) + ->setPreferStable($input->getOption('prefer-stable')) + ->setPreferLowest($input->getOption('prefer-lowest')) + ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')); + + return $installer->run(); + }); + + if (!preg_match('{^(install|update)\b}', $run)) { + throw new \UnexpectedValueException('The run command only supports install and update'); + } + + $application->setAutoExit(false); + $appOutput = fopen('php://memory', 'w+'); + $result = $application->run(new StringInput($run), new StreamOutput($appOutput)); + fseek($appOutput, 0); + if (!is_int($expectExitCode)) { + // Shouldn't check output and results if an exception was expected by this point + return; + } + + $this->assertEquals($expectExitCode, $result, $output . stream_get_contents($appOutput)); + if ($expectLock) { + unset($actualLock['hash']); + unset($actualLock['content-hash']); + unset($actualLock['_readme']); + $this->assertEquals($expectLock, $actualLock); + } + + $installationManager = $composer->getInstallationManager(); + $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace())); + + if ($expectOutput) { + $this->assertEquals(rtrim($expectOutput), rtrim($output)); } - return; } public function getIntegrationTests() @@ -316,7 +316,7 @@ class InstallerTest extends TestCase } $expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null; $expect = $testData['EXPECT']; - $expectExitCode = isset($testData['EXPECT-EXIT-CODE']) ? (int) $testData['EXPECT-EXIT-CODE'] : 0; + $expectExitCode = isset($testData['EXPECT-EXIT-CODE']) ? $testData['EXPECT-EXIT-CODE'] : 0; } catch (\Exception $e) { die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file))); } From 523362c7c5a5058174de6389baebf1d82078c73f Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 27 Jan 2016 13:46:14 +0100 Subject: [PATCH 11/40] Cleaner notation for expected exceptions in fixtures. --- .../installer/install-self-from-root.test | 2 +- tests/Composer/Test/InstallerTest.php | 28 +++++++++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/Composer/Test/Fixtures/installer/install-self-from-root.test b/tests/Composer/Test/Fixtures/installer/install-self-from-root.test index ceeef8b7c..82092c77f 100644 --- a/tests/Composer/Test/Fixtures/installer/install-self-from-root.test +++ b/tests/Composer/Test/Fixtures/installer/install-self-from-root.test @@ -9,7 +9,7 @@ Tries to require a package with the same name as the root package } --RUN-- install ---EXPECT-EXIT-CODE-- +--EXPECT-EXCEPTION-- InvalidArgumentException --EXPECT-- Root package 'foo/bar' cannot require itself in its composer.json diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 518a395e1..a50563280 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -137,7 +137,7 @@ class InstallerTest extends TestCase /** * @dataProvider getIntegrationTests */ - public function testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode) + public function testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectResult) { if ($condition) { eval('$res = '.$condition.';'); @@ -159,11 +159,11 @@ class InstallerTest extends TestCase ->will($this->returnCallback($callback)); // Prepare for exceptions - if (is_int($expectExitCode) || ctype_digit($expectExitCode)) { - $expectExitCode = (int) $expectExitCode; + if (is_int($expectResult) || ctype_digit($expectResult)) { + $expectResult = (int) $expectResult; } else { $normalizedOutput = rtrim(str_replace("\n", PHP_EOL, $expect)); - $this->setExpectedException($expectExitCode, $normalizedOutput); + $this->setExpectedException($expectResult, $normalizedOutput); } // Create Composer mock object according to configuration @@ -242,12 +242,12 @@ class InstallerTest extends TestCase $appOutput = fopen('php://memory', 'w+'); $result = $application->run(new StringInput($run), new StreamOutput($appOutput)); fseek($appOutput, 0); - if (!is_int($expectExitCode)) { + if (!is_int($expectResult)) { // Shouldn't check output and results if an exception was expected by this point return; } - $this->assertEquals($expectExitCode, $result, $output . stream_get_contents($appOutput)); + $this->assertEquals($expectResult, $result, $output . stream_get_contents($appOutput)); if ($expectLock) { unset($actualLock['hash']); unset($actualLock['content-hash']); @@ -279,7 +279,7 @@ class InstallerTest extends TestCase $installedDev = array(); $lock = array(); $expectLock = array(); - $expectExitCode = 0; + $expectResult = 0; try { $message = $testData['TEST']; @@ -316,12 +316,21 @@ class InstallerTest extends TestCase } $expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null; $expect = $testData['EXPECT']; - $expectExitCode = isset($testData['EXPECT-EXIT-CODE']) ? $testData['EXPECT-EXIT-CODE'] : 0; + if (!empty($testData['EXPECT-EXCEPTION'])) { + $expectResult = $testData['EXPECT-EXCEPTION']; + if (!empty($testData['EXPECT-EXIT-CODE'])) { + throw new \LogicException('EXPECT-EXCEPTION and EXPECT-EXIT-CODE are mutually exclusive'); + } + } elseif (!empty($testData['EXPECT-EXIT-CODE'])) { + $expectResult = (int) $testData['EXPECT-EXIT-CODE']; + } else { + $expectResult = 0; + } } catch (\Exception $e) { die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file))); } - $tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode); + $tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectResult); } return $tests; @@ -341,6 +350,7 @@ class InstallerTest extends TestCase 'EXPECT-LOCK' => false, 'EXPECT-OUTPUT' => false, 'EXPECT-EXIT-CODE' => false, + 'EXPECT-EXCEPTION' => false, 'EXPECT' => true, ); From 3e06c801f4a2c0da9d70dad55e05e9fe28eebc8d Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 27 Jan 2016 13:49:52 +0100 Subject: [PATCH 12/40] Cleaned up check+conversion that was no longer required. --- tests/Composer/Test/InstallerTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index a50563280..4f599bf21 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -159,9 +159,7 @@ class InstallerTest extends TestCase ->will($this->returnCallback($callback)); // Prepare for exceptions - if (is_int($expectResult) || ctype_digit($expectResult)) { - $expectResult = (int) $expectResult; - } else { + if (!is_int($expectResult)) { $normalizedOutput = rtrim(str_replace("\n", PHP_EOL, $expect)); $this->setExpectedException($expectResult, $normalizedOutput); } From 8d57c3e743b61f3aec017687085291acc146d647 Mon Sep 17 00:00:00 2001 From: Jefferson Carpenter Date: Thu, 28 Jan 2016 19:56:25 -0600 Subject: [PATCH 13/40] Update SolverProblemsException.php --- src/Composer/DependencyResolver/SolverProblemsException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/SolverProblemsException.php b/src/Composer/DependencyResolver/SolverProblemsException.php index a457c5c63..c6092c28c 100644 --- a/src/Composer/DependencyResolver/SolverProblemsException.php +++ b/src/Composer/DependencyResolver/SolverProblemsException.php @@ -41,7 +41,7 @@ class SolverProblemsException extends \RuntimeException } if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) { - $text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n\nRead for further common problems."; + $text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n\nRead for further common problems."; } if ($hasExtensionProblems) { From 087b901545194efdbb17544e6a3458479aca3657 Mon Sep 17 00:00:00 2001 From: Jefferson Carpenter Date: Thu, 28 Jan 2016 20:12:51 -0600 Subject: [PATCH 14/40] Update broken-deps-do-not-replace.test --- .../Test/Fixtures/installer/broken-deps-do-not-replace.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test b/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test index 19bd8f914..d8b09c4a1 100644 --- a/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test +++ b/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test @@ -33,7 +33,7 @@ install Potential causes: - A typo in the package name - The package is not available in a stable-enough version according to your minimum-stability setting - see for more details. + see for more details. Read for further common problems. From b7845bb6c0f0d263dee75e3edf565cbc897ac5ea Mon Sep 17 00:00:00 2001 From: Jefferson Carpenter Date: Thu, 28 Jan 2016 20:13:44 -0600 Subject: [PATCH 15/40] Update SolverTest.php --- tests/Composer/Test/DependencyResolver/SolverTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index ed62e3d79..63de2973f 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -709,7 +709,7 @@ class SolverTest extends TestCase $msg .= "Potential causes:\n"; $msg .= " - A typo in the package name\n"; $msg .= " - The package is not available in a stable-enough version according to your minimum-stability setting\n"; - $msg .= " see for more details.\n\n"; + $msg .= " see for more details.\n\n"; $msg .= "Read for further common problems."; $this->assertEquals($msg, $e->getMessage()); } From 49d7d65933c78e163354c2c580883926f1c8daee Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 28 Jan 2016 13:41:19 +0000 Subject: [PATCH 16/40] Add verbosity input support to IOInterface --- src/Composer/Cache.php | 16 ++---- src/Composer/Command/ShowCommand.php | 18 +++--- src/Composer/Console/Application.php | 14 ++--- src/Composer/Downloader/ArchiveDownloader.php | 5 +- src/Composer/Downloader/FileDownloader.php | 4 +- .../EventDispatcher/EventDispatcher.php | 4 +- src/Composer/Factory.php | 12 +--- src/Composer/IO/BufferIO.php | 2 +- src/Composer/IO/ConsoleIO.php | 49 ++++++++++------ src/Composer/IO/IOInterface.php | 38 ++++++++----- src/Composer/IO/NullIO.php | 8 +-- src/Composer/Installer.php | 12 ++-- src/Composer/Installer/PearInstaller.php | 4 +- src/Composer/Plugin/PluginManager.php | 4 +- .../Repository/ArtifactRepository.php | 10 +--- src/Composer/Repository/PearRepository.php | 4 +- .../Repository/Vcs/GitBitbucketDriver.php | 4 +- src/Composer/Repository/Vcs/GitHubDriver.php | 4 +- src/Composer/Repository/Vcs/GitLabDriver.php | 4 +- .../Repository/Vcs/HgBitbucketDriver.php | 4 +- src/Composer/Util/RemoteFilesystem.php | 24 +++----- tests/Composer/Test/ApplicationTest.php | 9 +++ .../EventDispatcher/EventDispatcherTest.php | 57 +++++-------------- .../Fixtures/installer/abandoned-listed.test | 8 +-- .../installer/broken-deps-do-not-replace.test | 6 +- .../Fixtures/installer/suggest-installed.test | 8 +-- .../Test/Fixtures/installer/suggest-prod.test | 8 +-- .../Fixtures/installer/suggest-replaced.test | 8 +-- .../installer/suggest-uninstalled.test | 8 +-- tests/Composer/Test/IO/ConsoleIOTest.php | 37 ++++++++---- tests/Composer/Test/InstallerTest.php | 17 ++---- 31 files changed, 186 insertions(+), 224 deletions(-) diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 8c5bce4ee..7090a8a0d 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -67,9 +67,7 @@ class Cache { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled && file_exists($this->root . $file)) { - if ($this->io->isDebug()) { - $this->io->writeError('Reading '.$this->root . $file.' from cache'); - } + $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); return file_get_contents($this->root . $file); } @@ -82,16 +80,12 @@ class Cache if ($this->enabled) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); - if ($this->io->isDebug()) { - $this->io->writeError('Writing '.$this->root . $file.' into cache'); - } + $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); try { return file_put_contents($this->root . $file, $contents); } catch (\ErrorException $e) { - if ($this->io->isDebug()) { - $this->io->writeError('Failed to write into cache: '.$e->getMessage().''); - } + $this->io->writeError('Failed to write into cache: '.$e->getMessage().'', true, IOInterface::DEBUG); if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { // Remove partial file. unlink($this->root . $file); @@ -152,9 +146,7 @@ class Cache touch($this->root . $file); } - if ($this->io->isDebug()) { - $this->io->writeError('Reading '.$this->root . $file.' from cache'); - } + $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); return copy($this->root . $file, $target); } diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 7f0c77537..e3e1ffd71 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -246,10 +246,10 @@ EOT $writeDescription = !$input->getOption('name-only') && !$input->getOption('path') && ($nameLength + ($showVersion ? $versionLength : 0) + 24 <= $width); foreach ($packages[$type] as $package) { if (is_object($package)) { - $output->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false); + $io->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false); if ($writeVersion) { - $output->write(' ' . str_pad($package->getFullPrettyVersion(), $versionLength, ' '), false); + $io->write(' ' . str_pad($package->getFullPrettyVersion(), $versionLength, ' '), false); } if ($writeDescription) { @@ -258,15 +258,15 @@ EOT if (strlen($description) > $remaining) { $description = substr($description, 0, $remaining - 3) . '...'; } - $output->write(' ' . $description); + $io->write(' ' . $description, false); } if ($writePath) { $path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"); - $output->write(' ' . $path); + $io->write(' ' . $path, false); } } else { - $output->write($indent . $package); + $io->write($indent . $package, false); } $io->write(''); } @@ -489,10 +489,10 @@ EOT $packagesInTree = array(); $packagesInTree[] = $package; - $output->write(sprintf('%s', $package->getPrettyName())); - $output->write(' ' . $package->getPrettyVersion()); - $output->write(' ' . strtok($package->getDescription(), "\r\n")); - $output->writeln(''); + $io = $this->getIO(); + $io->write(sprintf('%s', $package->getPrettyName()), false); + $io->write(' ' . $package->getPrettyVersion(), false); + $io->write(' ' . strtok($package->getDescription(), "\r\n")); if (is_object($package)) { $requires = $package->getRequires(); diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 632741586..f77876100 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -136,9 +136,7 @@ class Application extends BaseApplication if ($newWorkDir = $this->getNewWorkingDir($input)) { $oldWorkingDir = getcwd(); chdir($newWorkDir); - if ($io->isDebug() >= 4) { - $io->writeError('Changed CWD to ' . getcwd()); - } + $io->writeError('Changed CWD to ' . getcwd(), true, IOInterface::DEBUG); } // add non-standard scripts as own commands @@ -214,7 +212,7 @@ class Application extends BaseApplication || (($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) || (($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree) ) { - $io->writeError('The disk hosting '.$dir.' is full, this may be the cause of the following exception'); + $io->writeError('The disk hosting '.$dir.' is full, this may be the cause of the following exception', true, IOInterface::QUIET); } } } catch (\Exception $e) { @@ -222,13 +220,13 @@ class Application extends BaseApplication Silencer::restore(); if (defined('PHP_WINDOWS_VERSION_BUILD') && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { - $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun'); - $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details'); + $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details', true, IOInterface::QUIET); } if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) { - $io->writeError('The following exception is caused by a lack of memory and not having swap configured'); - $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details'); + $io->writeError('The following exception is caused by a lack of memory and not having swap configured', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details', true, IOInterface::QUIET); } } diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index f42b82872..9fa6a3338 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -14,6 +14,7 @@ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Symfony\Component\Finder\Finder; +use Composer\IO\IOInterface; /** * Base downloader for archives @@ -34,9 +35,7 @@ abstract class ArchiveDownloader extends FileDownloader while ($retries--) { $fileName = parent::download($package, $path); - if ($this->io->isVerbose()) { - $this->io->writeError(' Extracting archive'); - } + $this->io->writeError(' Extracting archive', true, IOInterface::VERBOSE); try { $this->filesystem->ensureDirectoryExists($temporaryDir); diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index d3a57500c..ece495dba 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -141,9 +141,7 @@ class FileDownloader implements DownloaderInterface if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) { throw $e; } - if ($this->io->isVerbose()) { - $this->io->writeError(' Download failed, retrying...'); - } + $this->io->writeError(' Download failed, retrying...', true, IOInterface::VERBOSE); usleep(500000); } } diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index c669b9dab..b9a879a01 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -155,9 +155,7 @@ class EventDispatcher $event = $this->checkListenerExpectedEvent($callable, $event); $return = false === call_user_func($callable, $event) ? 1 : 0; } elseif ($this->isComposerScript($callable)) { - if ($this->io->isVerbose()) { - $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable)); - } + $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE); $scriptName = substr($callable, 1); $args = $event->getArguments(); $flags = $event->getFlags(); diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index e11677b56..989975fde 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -293,14 +293,10 @@ class Factory $config = static::createConfig($io, $cwd); $config->merge($localConfig); if (isset($composerFile)) { - if ($io && $io->isDebug()) { - $io->writeError('Loading config file ' . $composerFile); - } + $io->writeError('Loading config file ' . $composerFile, true, IOInterface::DEBUG); $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json'); if ($localAuthFile->exists()) { - if ($io && $io->isDebug()) { - $io->writeError('Loading config file ' . $localAuthFile->getPath()); - } + $io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG); $config->merge(array('config' => $localAuthFile->read())); $config->setAuthConfigSource(new JsonConfigSource($localAuthFile, true)); } @@ -435,9 +431,7 @@ class Factory try { $composer = self::createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), false); } catch (\Exception $e) { - if ($io->isDebug()) { - $io->writeError('Failed to initialize global composer: '.$e->getMessage()); - } + $io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG); } return $composer; diff --git a/src/Composer/IO/BufferIO.php b/src/Composer/IO/BufferIO.php index db3fb634b..1069c0d9a 100644 --- a/src/Composer/IO/BufferIO.php +++ b/src/Composer/IO/BufferIO.php @@ -35,7 +35,7 @@ class BufferIO extends ConsoleIO $input = new StringInput($input); $input->setInteractive(false); - $output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, !empty($formatter), $formatter); + $output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, $formatter ? $formatter->isDecorated() : false, $formatter); parent::__construct($input, $output, new HelperSet(array())); } diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index 3867695f1..f97af2e8a 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -33,6 +33,7 @@ class ConsoleIO extends BaseIO protected $lastMessage; protected $lastMessageErr; private $startTime; + private $verbosityMap; /** * Constructor. @@ -46,6 +47,13 @@ class ConsoleIO extends BaseIO $this->input = $input; $this->output = $output; $this->helperSet = $helperSet; + $this->verbosityMap = array( + self::QUIET => OutputInterface::VERBOSITY_QUIET, + self::NORMAL => OutputInterface::VERBOSITY_NORMAL, + self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE, + self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE, + self::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ); } public function enableDebugging($startTime) @@ -96,26 +104,32 @@ class ConsoleIO extends BaseIO /** * {@inheritDoc} */ - public function write($messages, $newline = true) + public function write($messages, $newline = true, $verbosity = self::NORMAL) { - $this->doWrite($messages, $newline, false); + $this->doWrite($messages, $newline, false, $verbosity); } /** * {@inheritDoc} */ - public function writeError($messages, $newline = true) + public function writeError($messages, $newline = true, $verbosity = self::NORMAL) { - $this->doWrite($messages, $newline, true); + $this->doWrite($messages, $newline, true, $verbosity); } /** * @param array|string $messages * @param bool $newline * @param bool $stderr + * @param int $verbosity */ - private function doWrite($messages, $newline, $stderr) + private function doWrite($messages, $newline, $stderr, $verbosity) { + $sfVerbosity = $this->verbosityMap[$verbosity]; + if ($sfVerbosity > $this->output->getVerbosity()) { + return; + } + if (null !== $this->startTime) { $memoryUsage = memory_get_usage() / 1024 / 1024; $timeSpent = microtime(true) - $this->startTime; @@ -125,30 +139,30 @@ class ConsoleIO extends BaseIO } if (true === $stderr && $this->output instanceof ConsoleOutputInterface) { - $this->output->getErrorOutput()->write($messages, $newline); + $this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity); $this->lastMessageErr = join($newline ? "\n" : '', (array) $messages); return; } - $this->output->write($messages, $newline); + $this->output->write($messages, $newline, $sfVerbosity); $this->lastMessage = join($newline ? "\n" : '', (array) $messages); } /** * {@inheritDoc} */ - public function overwrite($messages, $newline = true, $size = null) + public function overwrite($messages, $newline = true, $size = null, $verbosity = self::NORMAL) { - $this->doOverwrite($messages, $newline, $size, false); + $this->doOverwrite($messages, $newline, $size, false, $verbosity); } /** * {@inheritDoc} */ - public function overwriteError($messages, $newline = true, $size = null) + public function overwriteError($messages, $newline = true, $size = null, $verbosity = self::NORMAL) { - $this->doOverwrite($messages, $newline, $size, true); + $this->doOverwrite($messages, $newline, $size, true, $verbosity); } /** @@ -156,8 +170,9 @@ class ConsoleIO extends BaseIO * @param bool $newline * @param int|null $size * @param bool $stderr + * @param int $verbosity */ - private function doOverwrite($messages, $newline, $size, $stderr) + private function doOverwrite($messages, $newline, $size, $stderr, $verbosity) { // messages can be an array, let's convert it to string anyway $messages = join($newline ? "\n" : '', (array) $messages); @@ -168,21 +183,21 @@ class ConsoleIO extends BaseIO $size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage)); } // ...let's fill its length with backspaces - $this->doWrite(str_repeat("\x08", $size), false, $stderr); + $this->doWrite(str_repeat("\x08", $size), false, $stderr, $verbosity); // write the new message - $this->doWrite($messages, false, $stderr); + $this->doWrite($messages, false, $stderr, $verbosity); $fill = $size - strlen(strip_tags($messages)); if ($fill > 0) { // whitespace whatever has left - $this->doWrite(str_repeat(' ', $fill), false, $stderr); + $this->doWrite(str_repeat(' ', $fill), false, $stderr, $verbosity); // move the cursor back - $this->doWrite(str_repeat("\x08", $fill), false, $stderr); + $this->doWrite(str_repeat("\x08", $fill), false, $stderr, $verbosity); } if ($newline) { - $this->doWrite('', true, $stderr); + $this->doWrite('', true, $stderr, $verbosity); } if ($stderr) { diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php index 3165b0d24..ff20a591d 100644 --- a/src/Composer/IO/IOInterface.php +++ b/src/Composer/IO/IOInterface.php @@ -21,6 +21,12 @@ use Composer\Config; */ interface IOInterface { + const QUIET = 1; + const NORMAL = 2; + const VERBOSE = 4; + const VERY_VERBOSE = 8; + const DEBUG = 16; + /** * Is this input means interactive? * @@ -59,36 +65,40 @@ interface IOInterface /** * Writes a message to the output. * - * @param string|array $messages The message as an array of lines or a single string - * @param bool $newline Whether to add a newline or not + * @param string|array $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants */ - public function write($messages, $newline = true); + public function write($messages, $newline = true, $verbosity = self::NORMAL); /** * Writes a message to the error output. * - * @param string|array $messages The message as an array of lines or a single string - * @param bool $newline Whether to add a newline or not + * @param string|array $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants */ - public function writeError($messages, $newline = true); + public function writeError($messages, $newline = true, $verbosity = self::NORMAL); /** * Overwrites a previous message to the output. * - * @param string|array $messages The message as an array of lines or a single string - * @param bool $newline Whether to add a newline or not - * @param int $size The size of line + * @param string|array $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $size The size of line + * @param int $verbosity Verbosity level from the VERBOSITY_* constants */ - public function overwrite($messages, $newline = true, $size = null); + public function overwrite($messages, $newline = true, $size = null, $verbosity = self::NORMAL); /** * Overwrites a previous message to the error output. * - * @param string|array $messages The message as an array of lines or a single string - * @param bool $newline Whether to add a newline or not - * @param int $size The size of line + * @param string|array $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $size The size of line + * @param int $verbosity Verbosity level from the VERBOSITY_* constants */ - public function overwriteError($messages, $newline = true, $size = null); + public function overwriteError($messages, $newline = true, $size = null, $verbosity = self::NORMAL); /** * Asks a question to the user. diff --git a/src/Composer/IO/NullIO.php b/src/Composer/IO/NullIO.php index 1a88395d3..587168677 100644 --- a/src/Composer/IO/NullIO.php +++ b/src/Composer/IO/NullIO.php @@ -62,28 +62,28 @@ class NullIO extends BaseIO /** * {@inheritDoc} */ - public function write($messages, $newline = true) + public function write($messages, $newline = true, $verbosity = self::NORMAL) { } /** * {@inheritDoc} */ - public function writeError($messages, $newline = true) + public function writeError($messages, $newline = true, $verbosity = self::NORMAL) { } /** * {@inheritDoc} */ - public function overwrite($messages, $newline = true, $size = 80) + public function overwrite($messages, $newline = true, $size = 80, $verbosity = self::NORMAL) { } /** * {@inheritDoc} */ - public function overwriteError($messages, $newline = true, $size = 80) + public function overwriteError($messages, $newline = true, $size = 80, $verbosity = self::NORMAL) { } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index f59eac7d3..3172e596f 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -529,10 +529,8 @@ class Installer return max(1, $e->getCode()); } - if ($this->io->isVerbose()) { - $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies"); - $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies"); - } + $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); + $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE); // force dev packages to be updated if we update or install from a (potentially new) lock $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, 'force-updates', $operations); @@ -578,10 +576,8 @@ class Installer && (!$operation->getTargetPackage()->getSourceReference() || $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference()) && (!$operation->getTargetPackage()->getDistReference() || $operation->getTargetPackage()->getDistReference() === $operation->getInitialPackage()->getDistReference()) ) { - if ($this->io->isDebug()) { - $this->io->writeError(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version'); - $this->io->writeError(''); - } + $this->io->writeError(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version', true, IOInterface::DEBUG); + $this->io->writeError('', true, IOInterface::DEBUG); continue; } diff --git a/src/Composer/Installer/PearInstaller.php b/src/Composer/Installer/PearInstaller.php index 146e68b95..35f7855de 100644 --- a/src/Composer/Installer/PearInstaller.php +++ b/src/Composer/Installer/PearInstaller.php @@ -75,9 +75,7 @@ class PearInstaller extends LibraryInstaller $pearExtractor = new PearPackageExtractor($packageArchive); $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin', 'data' => '/data'), $vars); - if ($this->io->isVerbose()) { - $this->io->writeError(' Cleaning up'); - } + $this->io->writeError(' Cleaning up', true, IOInterface::VERBOSE); $this->filesystem->unlink($packageArchive); } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index bfb0d9427..175a5e05b 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -206,9 +206,7 @@ class PluginManager */ private function addPlugin(PluginInterface $plugin) { - if ($this->io->isDebug()) { - $this->io->writeError('Loading plugin '.get_class($plugin)); - } + $this->io->writeError('Loading plugin '.get_class($plugin), true, IOInterface::DEBUG); $this->plugins[] = $plugin; $plugin->activate($this->composer, $this->io); diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 81335ef7e..ece78bb1c 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -67,16 +67,12 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito $package = $this->getComposerInformation($file); if (!$package) { - if ($io->isVerbose()) { - $io->writeError("File {$file->getBasename()} doesn't seem to hold a package"); - } + $io->writeError("File {$file->getBasename()} doesn't seem to hold a package", true, IOInterface::VERBOSE); continue; } - if ($io->isVerbose()) { - $template = 'Found package %s (%s) in file %s'; - $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename())); - } + $template = 'Found package %s (%s) in file %s'; + $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE); $this->addPackage($package); } diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 1d6d4710a..2c3e64bbe 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -105,9 +105,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn try { $normalizedVersion = $versionParser->normalize($version); } catch (\UnexpectedValueException $e) { - if ($this->io->isVerbose()) { - $this->io->writeError('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage()); - } + $this->io->writeError('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage(), true, IOInterface::VERBOSE); continue; } diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 0f0a57c47..7a2781ecb 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -160,9 +160,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface } if (!extension_loaded('openssl')) { - if ($io->isVerbose()) { - $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.'); - } + $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index cdd9df89f..4a4e1fcea 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -268,9 +268,7 @@ class GitHubDriver extends VcsDriver } if (!extension_loaded('openssl')) { - if ($io->isVerbose()) { - $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.'); - } + $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index fa13f952c..8642c2d42 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -367,9 +367,7 @@ class GitLabDriver extends VcsDriver } if ('https' === $scheme && !extension_loaded('openssl')) { - if ($io->isVerbose()) { - $io->write('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.'); - } + $io->writeError('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index 3beeee440..eb6808601 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -170,9 +170,7 @@ class HgBitbucketDriver extends VcsDriver } if (!extension_loaded('openssl')) { - if ($io->isVerbose()) { - $io->writeError('Skipping Bitbucket hg driver for '.$url.' because the OpenSSL PHP extension is missing.'); - } + $io->writeError('Skipping Bitbucket hg driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 2d94e5909..f4c5f3150 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -227,9 +227,7 @@ class RemoteFilesystem unset($tempAdditionalOptions); $userlandFollow = isset($options['http']['follow_location']) && !$options['http']['follow_location']; - if ($this->io->isDebug()) { - $this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl); - } + $this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl, true, IOInterface::DEBUG); if (isset($options['github-token'])) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token']; @@ -609,13 +607,11 @@ class RemoteFilesystem // Handle subjectAltName on lesser PHP's. $certMap = $this->peerCertificateMap[$urlAuthority]; - if ($this->io->isDebug()) { - $this->io->writeError(sprintf( - 'Using %s as CN for subjectAltName enabled host %s', - $certMap['cn'], - $urlAuthority - )); - } + $this->io->writeError(sprintf( + 'Using %s as CN for subjectAltName enabled host %s', + $certMap['cn'], + $urlAuthority + ), true, IOInterface::DEBUG); $tlsOptions['ssl']['CN_match'] = $certMap['cn']; $tlsOptions['ssl']['peer_fingerprint'] = $certMap['fp']; @@ -689,9 +685,7 @@ class RemoteFilesystem if (!empty($targetUrl)) { $this->redirects++; - if ($this->io->isDebug()) { - $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $targetUrl)); - } + $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $targetUrl), true, IOInterface::DEBUG); $additionalOptions['redirects'] = $this->redirects; @@ -914,9 +908,7 @@ class RemoteFilesystem return $files[$filename]; } - if ($this->io->isDebug()) { - $this->io->writeError('Checking CA file '.realpath($filename)); - } + $this->io->writeError('Checking CA file '.realpath($filename), true, IOInterface::DEBUG); $contents = file_get_contents($filename); // assume the CA is valid if php is vulnerable to diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 68d17d3f6..e7ab59c78 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -14,6 +14,7 @@ namespace Composer\Test; use Composer\Console\Application; use Composer\TestCase; +use Symfony\Component\Console\Output\OutputInterface; class ApplicationTest extends TestCase { @@ -30,11 +31,19 @@ class ApplicationTest extends TestCase $index = 0; if (extension_loaded('xdebug')) { + $outputMock->expects($this->at($index++)) + ->method("getVerbosity") + ->willReturn(OutputInterface::VERBOSITY_NORMAL); + $outputMock->expects($this->at($index++)) ->method("write") ->with($this->equalTo('You are running composer with xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug')); } + $outputMock->expects($this->at($index++)) + ->method("getVerbosity") + ->willReturn(OutputInterface::VERBOSITY_NORMAL); + $outputMock->expects($this->at($index++)) ->method("write") ->with($this->equalTo(sprintf('Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF']))); diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 2dd9f8a4a..e0c1fa45e 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -15,9 +15,11 @@ namespace Composer\Test\EventDispatcher; use Composer\EventDispatcher\Event; use Composer\Installer\InstallerEvents; use Composer\TestCase; +use Composer\IO\BufferIO; use Composer\Script\ScriptEvents; use Composer\Script\CommandEvent; use Composer\Util\ProcessExecutor; +use Symfony\Component\Console\Output\OutputInterface; class EventDispatcherTest extends TestCase { @@ -101,7 +103,7 @@ class EventDispatcherTest extends TestCase $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->getMock('Composer\Composer'), - $io = $this->getMock('Composer\IO\IOInterface'), + $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE), $process, )) ->setMethods(array( @@ -123,23 +125,12 @@ class EventDispatcherTest extends TestCase ->method('getListeners') ->will($this->returnValue($listeners)); - $io->expects($this->any()) - ->method('isVerbose') - ->willReturn(1); - - $io->expects($this->at(1)) - ->method('writeError') - ->with($this->equalTo('> post-install-cmd: echo -n foo')); - - $io->expects($this->at(3)) - ->method('writeError') - ->with($this->equalTo('> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod')); - - $io->expects($this->at(5)) - ->method('writeError') - ->with($this->equalTo('> post-install-cmd: echo -n bar')); - $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); + + $expected = '> post-install-cmd: echo -n foo'.PHP_EOL. + '> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL. + '> post-install-cmd: echo -n bar'.PHP_EOL; + $this->assertEquals($expected, $io->getOutput()); } public function testDispatcherCanExecuteComposerScriptGroups() @@ -148,7 +139,7 @@ class EventDispatcherTest extends TestCase $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $composer = $this->getMock('Composer\Composer'), - $io = $this->getMock('Composer\IO\IOInterface'), + $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE), $process, )) ->setMethods(array( @@ -174,31 +165,13 @@ class EventDispatcherTest extends TestCase return array(); })); - $io->expects($this->any()) - ->method('isVerbose') - ->willReturn(1); - - $io->expects($this->at(1)) - ->method('writeError') - ->with($this->equalTo('> root: @group')); - - $io->expects($this->at(3)) - ->method('writeError') - ->with($this->equalTo('> group: echo -n foo')); - - $io->expects($this->at(5)) - ->method('writeError') - ->with($this->equalTo('> group: @subgroup')); - - $io->expects($this->at(7)) - ->method('writeError') - ->with($this->equalTo('> subgroup: echo -n baz')); - - $io->expects($this->at(9)) - ->method('writeError') - ->with($this->equalTo('> group: echo -n bar')); - $dispatcher->dispatch('root', new CommandEvent('root', $composer, $io)); + $expected = '> root: @group'.PHP_EOL. + '> group: echo -n foo'.PHP_EOL. + '> group: @subgroup'.PHP_EOL. + '> subgroup: echo -n baz'.PHP_EOL. + '> group: echo -n bar'.PHP_EOL; + $this->assertEquals($expected, $io->getOutput()); } /** diff --git a/tests/Composer/Test/Fixtures/installer/abandoned-listed.test b/tests/Composer/Test/Fixtures/installer/abandoned-listed.test index 26861b1c6..1e0b9ff2c 100644 --- a/tests/Composer/Test/Fixtures/installer/abandoned-listed.test +++ b/tests/Composer/Test/Fixtures/installer/abandoned-listed.test @@ -24,12 +24,12 @@ Abandoned packages are flagged --RUN-- install --EXPECT-OUTPUT-- -Loading composer repositories with package information -Installing dependencies (including require-dev) +Loading composer repositories with package information +Installing dependencies (including require-dev) Package a/a is abandoned, you should avoid using it. No replacement was suggested. Package c/c is abandoned, you should avoid using it. Use b/b instead. -Writing lock file -Generating autoload files +Writing lock file +Generating autoload files --EXPECT-- Installing a/a (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test b/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test index d8b09c4a1..e7c6cd984 100644 --- a/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test +++ b/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test @@ -21,9 +21,9 @@ Broken dependencies should not lead to a replacer being installed which is not m --RUN-- install --EXPECT-OUTPUT-- -Loading composer repositories with package information -Installing dependencies (including require-dev) -Your requirements could not be resolved to an installable set of packages. +Loading composer repositories with package information +Installing dependencies (including require-dev) +Your requirements could not be resolved to an installable set of packages. Problem 1 - c/c 1.0.0 requires x/x 1.0 -> no matching package found. diff --git a/tests/Composer/Test/Fixtures/installer/suggest-installed.test b/tests/Composer/Test/Fixtures/installer/suggest-installed.test index 94f6c2016..4929f972e 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-installed.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-installed.test @@ -19,10 +19,10 @@ Suggestions are not displayed for installed packages --RUN-- install --EXPECT-OUTPUT-- -Loading composer repositories with package information -Installing dependencies (including require-dev) -Writing lock file -Generating autoload files +Loading composer repositories with package information +Installing dependencies (including require-dev) +Writing lock file +Generating autoload files --EXPECT-- Installing a/a (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/suggest-prod.test b/tests/Composer/Test/Fixtures/installer/suggest-prod.test index 290ccf4bb..c89bb0c20 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-prod.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-prod.test @@ -17,10 +17,10 @@ Suggestions are not displayed in non-dev mode --RUN-- install --no-dev --EXPECT-OUTPUT-- -Loading composer repositories with package information -Installing dependencies -Writing lock file -Generating autoload files +Loading composer repositories with package information +Installing dependencies +Writing lock file +Generating autoload files --EXPECT-- Installing a/a (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/suggest-replaced.test b/tests/Composer/Test/Fixtures/installer/suggest-replaced.test index 99d13a720..5d64d2176 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-replaced.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-replaced.test @@ -19,10 +19,10 @@ Suggestions are not displayed for packages if they are replaced --RUN-- install --EXPECT-OUTPUT-- -Loading composer repositories with package information -Installing dependencies (including require-dev) -Writing lock file -Generating autoload files +Loading composer repositories with package information +Installing dependencies (including require-dev) +Writing lock file +Generating autoload files --EXPECT-- Installing c/c (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test b/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test index d7e026e98..d04b6c8d5 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test @@ -17,11 +17,11 @@ Suggestions are displayed --RUN-- install --EXPECT-OUTPUT-- -Loading composer repositories with package information -Installing dependencies (including require-dev) +Loading composer repositories with package information +Installing dependencies (including require-dev) a/a suggests installing b/b (an obscure reason) -Writing lock file -Generating autoload files +Writing lock file +Generating autoload files --EXPECT-- Installing a/a (1.0.0) diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index a300350b9..ca7d420c9 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\IO; use Composer\IO\ConsoleIO; use Composer\TestCase; +use Symfony\Component\Console\Output\OutputInterface; class ConsoleIOTest extends TestCase { @@ -40,6 +41,9 @@ class ConsoleIOTest extends TestCase { $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $outputMock->expects($this->once()) + ->method('getVerbosity') + ->willReturn(OutputInterface::VERBOSITY_NORMAL); $outputMock->expects($this->once()) ->method('write') ->with($this->equalTo('some information about something'), $this->equalTo(false)); @@ -53,6 +57,9 @@ class ConsoleIOTest extends TestCase { $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); $outputMock = $this->getMock('Symfony\Component\Console\Output\ConsoleOutputInterface'); + $outputMock->expects($this->once()) + ->method('getVerbosity') + ->willReturn(OutputInterface::VERBOSITY_NORMAL); $outputMock->expects($this->once()) ->method('getErrorOutput') ->willReturn($outputMock); @@ -69,6 +76,9 @@ class ConsoleIOTest extends TestCase { $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $outputMock->expects($this->once()) + ->method('getVerbosity') + ->willReturn(OutputInterface::VERBOSITY_NORMAL); $outputMock->expects($this->once()) ->method('write') ->with( @@ -95,25 +105,28 @@ class ConsoleIOTest extends TestCase $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $outputMock->expects($this->at(0)) - ->method('write') - ->with($this->equalTo('something (strlen = 23)')); + $outputMock->expects($this->any()) + ->method('getVerbosity') + ->willReturn(OutputInterface::VERBOSITY_NORMAL); $outputMock->expects($this->at(1)) ->method('write') - ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false)); - $outputMock->expects($this->at(2)) - ->method('write') - ->with($this->equalTo('shorter (12)'), $this->equalTo(false)); + ->with($this->equalTo('something (strlen = 23)')); $outputMock->expects($this->at(3)) ->method('write') - ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false)); - $outputMock->expects($this->at(4)) - ->method('write') - ->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false)); + ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false)); $outputMock->expects($this->at(5)) + ->method('write') + ->with($this->equalTo('shorter (12)'), $this->equalTo(false)); + $outputMock->expects($this->at(7)) + ->method('write') + ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false)); + $outputMock->expects($this->at(9)) + ->method('write') + ->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false)); + $outputMock->expects($this->at(11)) ->method('write') ->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false)); - $outputMock->expects($this->at(6)) + $outputMock->expects($this->at(13)) ->method('write') ->with($this->equalTo('something longer than initial (34)')); diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 5339b8ff3..b0a51fea7 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -26,7 +26,10 @@ use Composer\Test\Mock\InstalledFilesystemRepositoryMock; use Composer\Test\Mock\InstallationManagerMock; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Formatter\OutputFormatter; use Composer\TestCase; +use Composer\IO\BufferIO; class InstallerTest extends TestCase { @@ -146,18 +149,7 @@ class InstallerTest extends TestCase } } - $output = null; - $io = $this->getMock('Composer\IO\IOInterface'); - $callback = function ($text, $newline) use (&$output) { - $output .= $text . ($newline ? "\n" : ""); - }; - $io->expects($this->any()) - ->method('write') - ->will($this->returnCallback($callback)); - $io->expects($this->any()) - ->method('writeError') - ->will($this->returnCallback($callback)); - + $io = new BufferIO('', OutputInterface::VERBOSITY_NORMAL, new OutputFormatter(false)); $composer = FactoryMock::create($io, $composerConfig); $jsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock(); @@ -233,6 +225,7 @@ class InstallerTest extends TestCase $appOutput = fopen('php://memory', 'w+'); $result = $application->run(new StringInput($run), new StreamOutput($appOutput)); fseek($appOutput, 0); + $output = str_replace("\r", '', $io->getOutput()); $this->assertEquals($expectExitCode, $result, $output . stream_get_contents($appOutput)); if ($expectLock) { From 2617ec5d28f90bbb41892588baa4da8c2f627d98 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 29 Jan 2016 12:51:23 +0000 Subject: [PATCH 17/40] Use proper defaults for IO authentications --- src/Composer/IO/BaseIO.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php index 3a5a1d701..a2ed28874 100644 --- a/src/Composer/IO/BaseIO.php +++ b/src/Composer/IO/BaseIO.php @@ -60,9 +60,9 @@ abstract class BaseIO implements IOInterface */ public function loadConfiguration(Config $config) { - $githubOauth = $config->get('github-oauth'); - $gitlabOauth = $config->get('gitlab-oauth'); - $httpBasic = $config->get('http-basic'); + $githubOauth = $config->get('github-oauth') ?: array(); + $gitlabOauth = $config->get('gitlab-oauth') ?: array(); + $httpBasic = $config->get('http-basic') ?: array(); // Use COMPOSER_AUTH environment variable if set if ($composerAuthEnv = getenv('COMPOSER_AUTH')) { From 7c3e621102106fd28af0128fdfd750af926fc569 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 29 Jan 2016 12:58:17 +0000 Subject: [PATCH 18/40] Make sure COMPOSER_AUTH is also loaded in Config, refs #4546 --- src/Composer/Factory.php | 14 +++++++++++++ src/Composer/IO/BaseIO.php | 41 ++++++++------------------------------ 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 989975fde..a7610242f 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -190,6 +190,20 @@ class Factory } $config->setAuthConfigSource(new JsonConfigSource($file, true)); + // load COMPOSER_AUTH environment variable if set + if ($composerAuthEnv = getenv('COMPOSER_AUTH')) { + $authData = json_decode($composerAuthEnv, true); + + if (is_null($authData)) { + throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object'); + } + + if ($io && $io->isDebug()) { + $io->writeError('Loading auth config from COMPOESR_AUTH'); + } + $config->merge(array('config' => $authData)); + } + return $config; } diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php index a2ed28874..ad7e32df4 100644 --- a/src/Composer/IO/BaseIO.php +++ b/src/Composer/IO/BaseIO.php @@ -64,46 +64,21 @@ abstract class BaseIO implements IOInterface $gitlabOauth = $config->get('gitlab-oauth') ?: array(); $httpBasic = $config->get('http-basic') ?: array(); - // Use COMPOSER_AUTH environment variable if set - if ($composerAuthEnv = getenv('COMPOSER_AUTH')) { - $authData = json_decode($composerAuthEnv, true); - - if (is_null($authData)) { - throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed'); - } - - if (isset($authData['github-oauth'])) { - $githubOauth = array_merge($githubOauth, $authData['github-oauth']); - } - if (isset($authData['gitlab-oauth'])) { - $gitlabOauth = array_merge($gitlabOauth, $authData['gitlab-oauth']); - } - if (isset($authData['http-basic'])) { - $httpBasic = array_merge($httpBasic, $authData['http-basic']); - } - } - // reload oauth token from config if available - if ($githubOauth) { - foreach ($githubOauth as $domain => $token) { - if (!preg_match('{^[a-z0-9]+$}', $token)) { - throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"'); - } - $this->setAuthentication($domain, $token, 'x-oauth-basic'); + foreach ($githubOauth as $domain => $token) { + if (!preg_match('{^[a-z0-9]+$}', $token)) { + throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"'); } + $this->setAuthentication($domain, $token, 'x-oauth-basic'); } - if ($gitlabOauth) { - foreach ($gitlabOauth as $domain => $token) { - $this->setAuthentication($domain, $token, 'oauth2'); - } + foreach ($gitlabOauth as $domain => $token) { + $this->setAuthentication($domain, $token, 'oauth2'); } // reload http basic credentials from config if available - if ($httpBasic) { - foreach ($httpBasic as $domain => $cred) { - $this->setAuthentication($domain, $cred['username'], $cred['password']); - } + foreach ($httpBasic as $domain => $cred) { + $this->setAuthentication($domain, $cred['username'], $cred['password']); } // setup process timeout From cc75946ef23ffcc8f14122dc62306d747fd3ccd5 Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Fri, 29 Jan 2016 17:13:44 +0100 Subject: [PATCH 19/40] typos --- CHANGELOG.md | 2 +- src/Composer/Factory.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65e7018f6..6700114fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ * Added --strict to the `validate` command to treat any warning as an error that then returns a non-zero exit code * Added a dependency on composer/semver, which is the externalized lib for all the version constraints parsing and handling * Added support for classmap autoloading to load plugin classes and script handlers - * Added `bin-compat` config option that if set to `full` will create .bat proxy for binaries even if Compoesr runs in a linux VM + * Added `bin-compat` config option that if set to `full` will create .bat proxy for binaries even if Composer runs in a linux VM * Added SPDX 2.0 support, and externalized that in a composer/spdx-licenses lib * Added warnings when the classmap autoloader finds duplicate classes * Added --file to the `archive` command to choose the filename diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index a7610242f..058709275 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -199,7 +199,7 @@ class Factory } if ($io && $io->isDebug()) { - $io->writeError('Loading auth config from COMPOESR_AUTH'); + $io->writeError('Loading auth config from COMPOSER_AUTH'); } $config->merge(array('config' => $authData)); } From 0e584aa9808c82d72bb7e3d54fccd70865951783 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Tue, 2 Feb 2016 10:45:57 +0100 Subject: [PATCH 20/40] resolve issue --- src/Composer/Installer.php | 1 + .../installer/github-issues-4795.test | 47 +++++++++++++++++++ .../installer/update-refs-issue-4795.test | 33 ------------- 3 files changed, 48 insertions(+), 33 deletions(-) create mode 100644 tests/Composer/Test/Fixtures/installer/github-issues-4795.test delete mode 100644 tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index f59eac7d3..adc2dc67b 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1199,6 +1199,7 @@ class Installer foreach ($requirePackages as $requirePackage) { if (isset($skipPackages[$requirePackage->getName()])) { + $this->io->writeError('Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.'); continue; } $packageQueue->enqueue($requirePackage); diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test new file mode 100644 index 000000000..cf0a3ae6a --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test @@ -0,0 +1,47 @@ +--TEST-- + +See Github issue #4795 ( github.com/composer/composer/issues/4795 ). + +Composer\Installer::whitelistUpdateDependencies intentionally ignores root requirements even if said package is also a +dependency of one the requirements that is whitelisted for update. + +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "a", "version": "1.0.0" }, + { "name": "a", "version": "1.1.0" }, + { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }, + { "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } }, + { "name": "c", "version": "1.0.0", "require": { "a": "~1.0" } } + ] + } + ], + "require": { + "a": "~1.0", + "b": "~1.0", + "c": "~1.0" + } +} + +--INSTALLED-- +[ + { "name": "a", "version": "1.0.0" }, + { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }, + { "name": "c", "version": "1.0.0", "require": { "a": "~1.0" } } +] + +--RUN-- +update B --with-dependencies + +--EXPECT-OUTPUT-- +Dependency "a" is also a root requirement, but is not explicitly whitelisted. Ignoring. +Loading composer repositories with package information +Updating dependencies (including require-dev) +Nothing to install or update +Writing lock file +Generating autoload files + +--EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test b/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test deleted file mode 100644 index a359303bb..000000000 --- a/tests/Composer/Test/Fixtures/installer/update-refs-issue-4795.test +++ /dev/null @@ -1,33 +0,0 @@ ---TEST-- -Refs issue #4795 ---COMPOSER-- -{ - "repositories": [ - { - "type": "package", - "package": [ - { "name": "A", "version": "1.0.0" }, - { "name": "A", "version": "1.1.0" }, - { "name": "B", "version": "1.0.0", "require": { "A": "~1.0" } }, - { "name": "B", "version": "1.1.0", "require": { "A": "~1.1" } }, - { "name": "C", "version": "1.0.0", "require": { "A": "~1.0" } } - ] - } - ], - "require": { - "A": "~1.0", - "B": "~1.0", - "C": "~1.0" - } -} ---INSTALLED-- -[ - { "name": "A", "version": "1.0.0" }, - { "name": "B", "version": "1.0.0", "require": { "A": "~1.0" } }, - { "name": "C", "version": "1.0.0", "require": { "A": "~1.0" } } -] ---RUN-- -update B --with-dependencies ---EXPECT-- -Updating A (1.0.0) to A (1.1.0) -Updating B (1.0.0) to B (1.1.0) From 47aa87ea971cdbcac06e4bb9dce4ae60aa7779de Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Tue, 2 Feb 2016 10:26:43 +0100 Subject: [PATCH 21/40] use full json content to determine reference, closes #4859 --- src/Composer/Repository/PathRepository.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index c3266543b..7fa004eb5 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -125,17 +125,16 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn $package['dist'] = array( 'type' => 'path', 'url' => $url, - 'reference' => '', + 'reference' => sha1($json), ); if (!isset($package['version'])) { $package['version'] = $this->versionGuesser->guessVersion($package, $path) ?: 'dev-master'; } + $output = ''; if (is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute('git log -n1 --pretty=%H', $output, $path)) { $package['dist']['reference'] = trim($output); - } else { - $package['dist']['reference'] = Locker::getContentHash($json); } $package = $this->loader->load($package); From 8b4761ff14a74871df8f6d5739158e68f0b5562e Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Wed, 3 Feb 2016 15:46:15 +0100 Subject: [PATCH 22/40] [doc] add -H flag to sudo commands --- doc/00-intro.md | 2 +- doc/03-cli.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/00-intro.md b/doc/00-intro.md index 1d09f2339..872bdff2c 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -109,7 +109,7 @@ mv composer.phar /usr/local/bin/composer A quick copy-paste version including sudo: ```sh -curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer +curl -sS https://getcomposer.org/installer | sudo -H php -- --install-dir=/usr/local/bin --filename=composer ``` > **Note:** On some versions of OSX the `/usr` directory does not exist by diff --git a/doc/03-cli.md b/doc/03-cli.md index 94a7a963c..b35d8f805 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -413,7 +413,7 @@ If you have installed Composer for your entire system (see [global installation] you may have to run the command with `root` privileges ```sh -sudo composer self-update +sudo -H composer self-update ``` ### Options From d93f7b8a10a01ecd608a4cb324c6451245279fb9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 3 Feb 2016 14:57:32 +0000 Subject: [PATCH 23/40] Remove warnings for non-writable dirs, refs #3588 --- src/Composer/Command/SelfUpdateCommand.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index fff159158..16b98020a 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -88,9 +88,6 @@ EOT if (!is_writable($tmpDir)) { throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written'); } - if (!is_writable($localFilename)) { - throw new FilesystemException('Composer update failed: the "'.$localFilename.'" file could not be written'); - } if ($input->getOption('rollback')) { return $this->rollback($output, $rollbackDir, $localFilename); @@ -271,10 +268,6 @@ TAGSPUBKEY throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"'); } - if (!is_writable($rollbackDir)) { - throw new FilesystemException('Composer rollback failed: the "'.$rollbackDir.'" dir could not be written to'); - } - $old = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT; if (!is_file($old)) { From f2a2b1836754de7f33a99f7fd73a472f4717106f Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 3 Feb 2016 22:25:43 +0100 Subject: [PATCH 24/40] Added Platform utility and unit test for it. --- src/Composer/Util/Platform.php | 28 ++++++++++++++++++++++ tests/Composer/Test/Util/PlatformTest.php | 29 +++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/Composer/Util/Platform.php create mode 100644 tests/Composer/Test/Util/PlatformTest.php diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php new file mode 100644 index 000000000..eafb88b7a --- /dev/null +++ b/src/Composer/Util/Platform.php @@ -0,0 +1,28 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * Platform helper for uniform platform-specific tests. + * + * @author Niels Keurentjes + */ +class Platform +{ + /** + * @return bool Whether the host machine is running a Windows OS + */ + public static function isWindows() + { + return defined('PHP_WINDOWS_VERSION_BUILD'); + } +} diff --git a/tests/Composer/Test/Util/PlatformTest.php b/tests/Composer/Test/Util/PlatformTest.php new file mode 100644 index 000000000..3d82fb96f --- /dev/null +++ b/tests/Composer/Test/Util/PlatformTest.php @@ -0,0 +1,29 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\Util\Platform; + +/** + * PlatformTest + * + * @author Niels Keurentjes + */ +class PlatformTest extends \PHPUnit_Framework_TestCase +{ + public function testWindows() + { + // Compare 2 common tests for Windows to the built-in Windows test + $this->assertEquals(('\\' === DIRECTORY_SEPARATOR), Platform::isWindows()); + $this->assertEquals(defined('PHP_WINDOWS_VERSION_MAJOR'), Platform::isWindows()); + } +} From 0dab63e0507f548149c85a2371b044b628cf845c Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 3 Feb 2016 22:39:16 +0100 Subject: [PATCH 25/40] Unified all Windows tests throughout the code. --- src/Composer/Command/ConfigCommand.php | 5 +++-- src/Composer/Command/HomeCommand.php | 3 ++- src/Composer/Command/ShowCommand.php | 3 ++- src/Composer/Console/Application.php | 3 ++- src/Composer/Downloader/GitDownloader.php | 5 +++-- src/Composer/Downloader/GzipDownloader.php | 3 ++- src/Composer/Downloader/RarDownloader.php | 5 +++-- src/Composer/Downloader/ZipDownloader.php | 5 +++-- src/Composer/Factory.php | 7 ++++--- src/Composer/Installer/LibraryInstaller.php | 3 ++- src/Composer/Installer/PearInstaller.php | 3 ++- src/Composer/Util/Filesystem.php | 16 ++++++++-------- src/Composer/Util/Perforce.php | 5 +---- src/Composer/Util/ProcessExecutor.php | 2 +- src/Composer/Util/TlsHelper.php | 2 +- .../Test/Downloader/GitDownloaderTest.php | 3 ++- .../Test/Downloader/HgDownloaderTest.php | 7 ++----- .../Test/Downloader/XzDownloaderTest.php | 3 ++- .../Test/Repository/Vcs/SvnDriverTest.php | 3 ++- tests/Composer/Test/Util/SvnTest.php | 7 ++----- 20 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 94fccc2e5..722bc94cc 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -12,6 +12,7 @@ namespace Composer\Command; +use Composer\Util\Platform; use Composer\Util\Silencer; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; @@ -184,7 +185,7 @@ EOT if ($input->getOption('editor')) { $editor = escapeshellcmd(getenv('EDITOR')); if (!$editor) { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { $editor = 'notepad'; } else { foreach (array('vim', 'vi', 'nano', 'pico', 'ed') as $candidate) { @@ -197,7 +198,7 @@ EOT } $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath(); - system($editor . ' ' . $file . (defined('PHP_WINDOWS_VERSION_BUILD') ? '' : ' > `tty`')); + system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`')); return 0; } diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php index fff1f86eb..150b8c71b 100644 --- a/src/Composer/Command/HomeCommand.php +++ b/src/Composer/Command/HomeCommand.php @@ -16,6 +16,7 @@ use Composer\Factory; use Composer\Package\CompletePackageInterface; use Composer\Repository\RepositoryInterface; use Composer\Repository\ArrayRepository; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -117,7 +118,7 @@ EOT { $url = ProcessExecutor::escape($url); - if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + if (Platform::isWindows()) { return passthru('start "web" explorer "' . $url . '"'); } diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index e3e1ffd71..5ddce2954 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -20,6 +20,7 @@ use Composer\Semver\VersionParser; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Package\PackageInterface; +use Composer\Util\Platform; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; @@ -232,7 +233,7 @@ EOT // outside of a real terminal, use space without a limit $width = PHP_INT_MAX; } - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { $width--; } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index f77876100..07d517eae 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -12,6 +12,7 @@ namespace Composer\Console; +use Composer\Util\Platform; use Composer\Util\Silencer; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Input\InputInterface; @@ -219,7 +220,7 @@ class Application extends BaseApplication } Silencer::restore(); - if (defined('PHP_WINDOWS_VERSION_BUILD') && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { + if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details', true, IOInterface::QUIET); } diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 7db561889..48e0d4b39 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -14,6 +14,7 @@ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Util\Git as GitUtil; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\IO\IOInterface; use Composer\Util\Filesystem; @@ -43,7 +44,7 @@ class GitDownloader extends VcsDownloader $path = $this->normalizePath($path); $ref = $package->getSourceReference(); - $flag = defined('PHP_WINDOWS_VERSION_MAJOR') ? '/D ' : ''; + $flag = Platform::isWindows() ? '/D ' : ''; $command = 'git clone --no-checkout %s %s && cd '.$flag.'%2$s && git remote add composer %1$s && git fetch composer'; $this->io->writeError(" Cloning ".$ref); @@ -353,7 +354,7 @@ class GitDownloader extends VcsDownloader protected function normalizePath($path) { - if (defined('PHP_WINDOWS_VERSION_MAJOR') && strlen($path) > 0) { + if (Platform::isWindows() && strlen($path) > 0) { $basePath = $path; $removed = array(); diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index ae7d7f17a..819af79f0 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -16,6 +16,7 @@ use Composer\Config; use Composer\Cache; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; @@ -40,7 +41,7 @@ class GzipDownloader extends ArchiveDownloader $targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3)); // Try to use gunzip on *nix - if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + if (!Platform::isWindows()) { $command = 'gzip -cd ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath); if (0 === $this->process->execute($command, $ignoredOutput)) { diff --git a/src/Composer/Downloader/RarDownloader.php b/src/Composer/Downloader/RarDownloader.php index 81e11785e..2a0c98cf9 100644 --- a/src/Composer/Downloader/RarDownloader.php +++ b/src/Composer/Downloader/RarDownloader.php @@ -15,6 +15,7 @@ namespace Composer\Downloader; use Composer\Config; use Composer\Cache; use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; @@ -42,7 +43,7 @@ class RarDownloader extends ArchiveDownloader $processError = null; // Try to use unrar on *nix - if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + if (!Platform::isWindows()) { $command = 'unrar x ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path); if (0 === $this->process->execute($command, $ignoredOutput)) { @@ -65,7 +66,7 @@ class RarDownloader extends ArchiveDownloader $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n" . $iniMessage . "\n" . $processError; - if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + if (!Platform::isWindows()) { $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage; } diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 6faaaaa4f..5f483975c 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -15,6 +15,7 @@ namespace Composer\Downloader; use Composer\Config; use Composer\Cache; use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; @@ -38,7 +39,7 @@ class ZipDownloader extends ArchiveDownloader $processError = null; // try to use unzip on *nix - if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + if (!Platform::isWindows()) { $command = 'unzip '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path); try { if (0 === $this->process->execute($command, $ignoredOutput)) { @@ -64,7 +65,7 @@ class ZipDownloader extends ArchiveDownloader $error = "Could not decompress the archive, enable the PHP zip extension or install unzip.\n" . $iniMessage . "\n" . $processError; - if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + if (!Platform::isWindows()) { $error = "Could not decompress the archive, enable the PHP zip extension.\n" . $iniMessage; } diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 058709275..2f3491fd4 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -20,6 +20,7 @@ use Composer\Package\Version\VersionGuesser; use Composer\Repository\RepositoryManager; use Composer\Repository\WritableRepositoryInterface; use Composer\Util\Filesystem; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\Util\Silencer; @@ -51,7 +52,7 @@ class Factory return $home; } - if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + if (Platform::isWindows()) { if (!getenv('APPDATA')) { throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly'); } @@ -90,7 +91,7 @@ class Factory return $homeEnv . '/cache'; } - if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + if (Platform::isWindows()) { if ($cacheDir = getenv('LOCALAPPDATA')) { $cacheDir .= '/Composer'; } else { @@ -125,7 +126,7 @@ class Factory return $homeEnv; } - if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + if (Platform::isWindows()) { return strtr($home, '\\', '/'); } diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index b14659f7b..31090de00 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -17,6 +17,7 @@ use Composer\IO\IOInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\Silencer; @@ -241,7 +242,7 @@ class LibraryInstaller implements InstallerInterface } if ($this->binCompat === "auto") { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { $this->installFullBinaries($binPath, $link, $bin, $package); } else { $this->installSymlinkBinaries($binPath, $link); diff --git a/src/Composer/Installer/PearInstaller.php b/src/Composer/Installer/PearInstaller.php index 35f7855de..0e16fcb32 100644 --- a/src/Composer/Installer/PearInstaller.php +++ b/src/Composer/Installer/PearInstaller.php @@ -17,6 +17,7 @@ use Composer\Composer; use Composer\Downloader\PearPackageExtractor; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; /** @@ -53,7 +54,7 @@ class PearInstaller extends LibraryInstaller parent::installCode($package); parent::initializeBinDir(); - $isWindows = defined('PHP_WINDOWS_VERSION_BUILD'); + $isWindows = Platform::isWindows(); $php_bin = $this->binDir . ($isWindows ? '/composer-php.bat' : '/composer-php'); if (!$isWindows) { diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index f7d812de8..e29a60673 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -110,7 +110,7 @@ class Filesystem return $this->removeDirectoryPhp($directory); } - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape(realpath($directory))); } else { $cmd = sprintf('rm -rf %s', ProcessExecutor::escape($directory)); @@ -181,10 +181,10 @@ class Filesystem { if (!@$this->unlinkImplementation($path)) { // retry after a bit on windows since it tends to be touchy with mass removals - if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(350000) && !@$this->unlinkImplementation($path))) { + if (!Platform::isWindows() || (usleep(350000) && !@$this->unlinkImplementation($path))) { $error = error_get_last(); $message = 'Could not delete '.$path.': ' . @$error['message']; - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } @@ -206,10 +206,10 @@ class Filesystem { if (!@rmdir($path)) { // retry after a bit on windows since it tends to be touchy with mass removals - if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(350000) && !@rmdir($path))) { + if (!Platform::isWindows() || (usleep(350000) && !@rmdir($path))) { $error = error_get_last(); $message = 'Could not delete '.$path.': ' . @$error['message']; - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } @@ -264,7 +264,7 @@ class Filesystem return $this->copyThenRemove($source, $target); } - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { // Try to copy & delete - this is a workaround for random "Access denied" errors. $command = sprintf('xcopy %s %s /E /I /Q /Y', ProcessExecutor::escape($source), ProcessExecutor::escape($target)); $result = $this->processExecutor->execute($command, $output); @@ -460,7 +460,7 @@ class Filesystem public static function getPlatformPath($path) { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { $path = preg_replace('{^(?:file:///([a-z])/)}i', 'file://$1:/', $path); } @@ -498,7 +498,7 @@ class Filesystem */ private function unlinkImplementation($path) { - if (defined('PHP_WINDOWS_VERSION_BUILD') && is_dir($path) && is_link($path)) { + if (Platform::isWindows() && is_dir($path) && is_link($path)) { return rmdir($path); } diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index c1eaeebe9..b57a64a1f 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -51,10 +51,7 @@ class Perforce public static function create($repoConfig, $port, $path, ProcessExecutor $process, IOInterface $io) { - $isWindows = defined('PHP_WINDOWS_VERSION_BUILD'); - $perforce = new Perforce($repoConfig, $port, $path, $process, $isWindows, $io); - - return $perforce; + return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io); } public static function checkServerExists($url, ProcessExecutor $processExecutor) diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index f9499f09f..6b778e5eb 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -50,7 +50,7 @@ class ProcessExecutor // make sure that null translate to the proper directory in case the dir is a symlink // and we call a git command, because msysgit does not handle symlinks properly - if (null === $cwd && defined('PHP_WINDOWS_VERSION_BUILD') && false !== strpos($command, 'git') && getcwd()) { + if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) { $cwd = realpath(getcwd()); } diff --git a/src/Composer/Util/TlsHelper.php b/src/Composer/Util/TlsHelper.php index 6ea5cf591..721e93825 100644 --- a/src/Composer/Util/TlsHelper.php +++ b/src/Composer/Util/TlsHelper.php @@ -175,7 +175,7 @@ final class TlsHelper return self::$useOpensslParse = true; } - if ('\\' === DIRECTORY_SEPARATOR) { + if (Platform::isWindows()) { // Windows is probably insecure in this case. return self::$useOpensslParse = false; } diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 26437ada5..9c851fe9a 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -16,6 +16,7 @@ use Composer\Downloader\GitDownloader; use Composer\Config; use Composer\TestCase; use Composer\Util\Filesystem; +use Composer\Util\Platform; class GitDownloaderTest extends TestCase { @@ -353,7 +354,7 @@ class GitDownloaderTest extends TestCase private function winCompat($cmd) { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { $cmd = str_replace('cd ', 'cd /D ', $cmd); $cmd = str_replace('composerPath', getcwd().'/composerPath', $cmd); diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index 75dfa84aa..6b660e383 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -15,6 +15,7 @@ namespace Composer\Test\Downloader; use Composer\Downloader\HgDownloader; use Composer\TestCase; use Composer\Util\Filesystem; +use Composer\Util\Platform; class HgDownloaderTest extends TestCase { @@ -156,10 +157,6 @@ class HgDownloaderTest extends TestCase private function getCmd($cmd) { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - return strtr($cmd, "'", '"'); - } - - return $cmd; + return Platform::isWindows() ? strtr($cmd, "'", '"') : $cmd; } } diff --git a/tests/Composer/Test/Downloader/XzDownloaderTest.php b/tests/Composer/Test/Downloader/XzDownloaderTest.php index 418776d75..d8e77a2cb 100644 --- a/tests/Composer/Test/Downloader/XzDownloaderTest.php +++ b/tests/Composer/Test/Downloader/XzDownloaderTest.php @@ -15,6 +15,7 @@ namespace Composer\Test\Downloader; use Composer\Downloader\XzDownloader; use Composer\TestCase; use Composer\Util\Filesystem; +use Composer\Util\Platform; use Composer\Util\RemoteFilesystem; class XzDownloaderTest extends TestCase @@ -31,7 +32,7 @@ class XzDownloaderTest extends TestCase public function setUp() { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { $this->markTestSkipped('Skip test on Windows'); } $this->testDir = $this->getUniqueTmpDirectory(); diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index c2ae497ca..881b86ea2 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -16,6 +16,7 @@ use Composer\Repository\Vcs\SvnDriver; use Composer\Config; use Composer\TestCase; use Composer\Util\Filesystem; +use Composer\Util\Platform; class SvnDriverTest extends TestCase { @@ -71,7 +72,7 @@ class SvnDriverTest extends TestCase private function getCmd($cmd) { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (Platform::isWindows()) { return strtr($cmd, "'", '"'); } diff --git a/tests/Composer/Test/Util/SvnTest.php b/tests/Composer/Test/Util/SvnTest.php index 55a116376..c16b0e6ce 100644 --- a/tests/Composer/Test/Util/SvnTest.php +++ b/tests/Composer/Test/Util/SvnTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Util; use Composer\Config; use Composer\IO\NullIO; +use Composer\Util\Platform; use Composer\Util\Svn; class SvnTest extends \PHPUnit_Framework_TestCase @@ -131,10 +132,6 @@ class SvnTest extends \PHPUnit_Framework_TestCase private function getCmd($cmd) { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - return strtr($cmd, "'", '"'); - } - - return $cmd; + return Platform::isWindows() ? strtr($cmd, "'", '"') : $cmd; } } From e9e2514b5e2734b7bb34b7933cf8ea082fb06643 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 4 Feb 2016 00:01:42 +0000 Subject: [PATCH 26/40] Handle OpenSSL version after 26 patch releases e.g. https://github.com/openssl/openssl/blob/OpenSSL_0_9_8zh/crypto/opensslv.h#L33 --- src/Composer/Repository/PlatformRepository.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 833c82c20..1082c5404 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -146,8 +146,18 @@ class PlatformRepository extends ArrayRepository break; case 'openssl': - $prettyVersion = preg_replace_callback('{^(?:OpenSSL\s*)?([0-9.]+)([a-z]?).*}', function ($match) { - return $match[1] . (empty($match[2]) ? '' : '.'.(ord($match[2]) - 96)); + $prettyVersion = preg_replace_callback('{^(?:OpenSSL\s*)?([0-9.]+)([a-z]+).*}', function ($match) { + if (empty($match[2])) { + return $match[1]; + } + + // OpenSSL versions add another letter when they reach Z. + // e.g. OpenSSL 0.9.8zh 3 Dec 2015 + $patchVersion = array_sum(array_map(function ($letter) { + return ord($letter) - 96; + }, str_split($match[2]))); + + return $match[1].'.'.$patchVersion; }, OPENSSL_VERSION_TEXT); break; From f6f273c4b6df4cedc1a644578a82ff107067d1b8 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 4 Feb 2016 00:10:34 +0000 Subject: [PATCH 27/40] Improve OpenSSL library description --- src/Composer/Repository/PlatformRepository.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 1082c5404..aa1335153 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -114,6 +114,7 @@ class PlatformRepository extends ArrayRepository // relying on them. foreach ($loadedExtensions as $name) { $prettyVersion = null; + $description = 'The '.$name.' PHP library'; switch ($name) { case 'curl': $curlVersion = curl_version(); @@ -159,6 +160,8 @@ class PlatformRepository extends ArrayRepository return $match[1].'.'.$patchVersion; }, OPENSSL_VERSION_TEXT); + + $description = OPENSSL_VERSION_TEXT; break; case 'pcre': @@ -185,7 +188,7 @@ class PlatformRepository extends ArrayRepository } $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); - $lib->setDescription('The '.$name.' PHP library'); + $lib->setDescription($description); $this->addPackage($lib); } From 0818a6ed5467f5cc76f67b71eb33f2c3eb366358 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 4 Feb 2016 00:27:20 +0000 Subject: [PATCH 28/40] Previous attempt would cause 0.9.8aa == 0.9.8b --- src/Composer/Repository/PlatformRepository.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index aa1335153..27f52aa65 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -147,16 +147,22 @@ class PlatformRepository extends ArrayRepository break; case 'openssl': - $prettyVersion = preg_replace_callback('{^(?:OpenSSL\s*)?([0-9.]+)([a-z]+).*}', function ($match) { + $prettyVersion = preg_replace_callback('{^(?:OpenSSL\s*)?([0-9.]+)([a-z]*).*}', function ($match) { if (empty($match[2])) { return $match[1]; } // OpenSSL versions add another letter when they reach Z. // e.g. OpenSSL 0.9.8zh 3 Dec 2015 - $patchVersion = array_sum(array_map(function ($letter) { - return ord($letter) - 96; - }, str_split($match[2]))); + + if (!preg_match('{^z*[a-z]$}', $match[2])) { + // 0.9.8abc is garbage + return 0; + } + + $len = strlen($match[2]); + $patchVersion = ($len - 1) * 26; // All Z + $patchVersion += ord($match[2][$len - 1]) - 96; return $match[1].'.'.$patchVersion; }, OPENSSL_VERSION_TEXT); From 8debdf8764a629e32180e77e0bef59ed70ff2d34 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 4 Feb 2016 00:34:01 +0000 Subject: [PATCH 29/40] Zip extension does not provide zlib support --- composer.json | 3 ++- composer.lock | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8fcd1ea93..f45775853 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,8 @@ } }, "suggest": { - "ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests", "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages" }, "autoload": { diff --git a/composer.lock b/composer.lock index caeba6397..31543e65c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "fdf4b487fa59607376721ebec4ff4783", + "hash": "31b3c13c89f8d6c810637ca1fe8fc6ae", "content-hash": "454148e20b837d9755dee7862f9c7a5d", "packages": [ { From e4877473cf4d47d22952418452b5798276bd1558 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 4 Feb 2016 00:39:31 +0000 Subject: [PATCH 30/40] Fallback to zlib extension to unpack gzip on non Windows systems --- src/Composer/Downloader/GzipDownloader.php | 26 ++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index ae7d7f17a..11f2f7f33 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -47,18 +47,19 @@ class GzipDownloader extends ArchiveDownloader return; } + if (extension_loaded('zlib')) { + // Fallback to using the PHP extension. + $this->extractUsingExt($file, $targetFilepath); + + return; + } + $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } // Windows version of PHP has built-in support of gzip functions - $archiveFile = gzopen($file, 'rb'); - $targetFile = fopen($targetFilepath, 'wb'); - while ($string = gzread($archiveFile, 4096)) { - fwrite($targetFile, $string, strlen($string)); - } - gzclose($archiveFile); - fclose($targetFile); + $this->extractUsingExt($file, $targetFilepath); } /** @@ -68,4 +69,15 @@ class GzipDownloader extends ArchiveDownloader { return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); } + + private function extractUsingExt($file, $targetFilepath) + { + $archiveFile = gzopen($file, 'rb'); + $targetFile = fopen($targetFilepath, 'wb'); + while ($string = gzread($archiveFile, 4096)) { + fwrite($targetFile, $string, strlen($string)); + } + gzclose($archiveFile); + fclose($targetFile); + } } From df23153932af03d75d2bb86a45bb989ddb6cc438 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Thu, 4 Feb 2016 12:45:55 +0100 Subject: [PATCH 31/40] fix output --- .../Test/Fixtures/installer/github-issues-4795.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test index cf0a3ae6a..6dc3ced3d 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4795.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test @@ -38,10 +38,10 @@ update B --with-dependencies --EXPECT-OUTPUT-- Dependency "a" is also a root requirement, but is not explicitly whitelisted. Ignoring. -Loading composer repositories with package information -Updating dependencies (including require-dev) +Loading composer repositories with package information +Updating dependencies (including require-dev) Nothing to install or update -Writing lock file -Generating autoload files +Writing lock file +Generating autoload files --EXPECT-- From 94daeca57b87da6762116bd85861adc722bfa0df Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Fri, 5 Feb 2016 11:39:14 +0100 Subject: [PATCH 32/40] add test and adjust rule error message --- src/Composer/DependencyResolver/Rule.php | 4 +- .../installer/github-issues-4319.test | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Fixtures/installer/github-issues-4319.test diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 13bbb7e09..32546fd27 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -207,7 +207,9 @@ class Rule } elseif ($targetName === 'hhvm') { $text .= ' -> you are running this with PHP and not HHVM.'; } else { - $text .= ' -> your PHP version ('. phpversion() .') or value of "config.platform.php" in composer.json does not satisfy that requirement.'; + $available = $pool->whatProvides($targetName); + $version = count($available) ? $available[0]->getPrettyVersion() : phpversion(); + $text .= ' -> your PHP version or "config.platform.php" value ('.$version.') does not satisfy that requirement.'; } } elseif (0 === strpos($targetName, 'ext-')) { // handle php extensions diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test new file mode 100644 index 000000000..5bfc17af6 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test @@ -0,0 +1,45 @@ +--TEST-- + +See Github issue #4319 ( github.com/composer/composer/issues/4319 ). + +Present a clear error message when config.platform.php version results in a conflict rule. + +--CONDITION-- +!defined('HHVM_VERSION') + +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "a", "version": "1.0.0", "require": { "php": "5.5" } } + ] + } + ], + "require": { + "a": "~1.0" + }, + "config": { + "platform": { + "php": "5.3" + } + } +} + +--RUN-- +install + +--EXPECT-OUTPUT-- +Loading composer repositories with package information +Installing dependencies (including require-dev) +Your requirements could not be resolved to an installable set of packages. + + Problem 1 + - Installation request for a ~1.0 -> satisfiable by a[1.0.0]. + - a 1.0.0 requires php 5.5 -> your PHP version or "config.platform.php" value (5.3) does not satisfy that requirement. + +--EXPECT-- + +--EXPECT-EXIT-CODE-- +2 From baabc612f672b45eb74d6884e835360bbaabcc2e Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Fri, 5 Feb 2016 13:21:30 +0100 Subject: [PATCH 33/40] adjust message, skip test currently we have no way to put dynamic values or wildcards in EXPECT-OUTPUT --- src/Composer/DependencyResolver/Rule.php | 31 ++++++++++++++----- .../Repository/PlatformRepository.php | 1 + ...9.test => github-issues-4319.test.skipped} | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) rename tests/Composer/Test/Fixtures/installer/{github-issues-4319.test => github-issues-4319.test.skipped} (85%) diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 32546fd27..54a002203 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -12,6 +12,8 @@ namespace Composer\DependencyResolver; +use Composer\Package\CompletePackage; + /** * @author Nils Adermann */ @@ -203,27 +205,40 @@ class Rule if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') { // handle php/hhvm if (defined('HHVM_VERSION')) { - $text .= ' -> your HHVM version does not satisfy that requirement.'; + return $text . ' -> your HHVM version does not satisfy that requirement.'; } elseif ($targetName === 'hhvm') { - $text .= ' -> you are running this with PHP and not HHVM.'; + return $text . ' -> you are running this with PHP and not HHVM.'; } else { - $available = $pool->whatProvides($targetName); - $version = count($available) ? $available[0]->getPrettyVersion() : phpversion(); - $text .= ' -> your PHP version or "config.platform.php" value ('.$version.') does not satisfy that requirement.'; + $packages = $pool->whatProvides($targetName); + $package = count($packages) ? current($packages) : phpversion(); + + if (!($package instanceof CompletePackage)) { + return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.'; + } + + $extra = $package->getExtra(); + + if (!empty($extra['config.platform'])) { + $text .= ' -> your PHP version ('.phpversion().') overriden by "config.platform.php" version ('.$package->getPrettyVersion().') does not satisfy that requirement.'; + } else { + $text .= ' -> your PHP version ('.$package->getPrettyVersion().') does not satisfy that requirement.'; + } + + return $text; } } elseif (0 === strpos($targetName, 'ext-')) { // handle php extensions $ext = substr($targetName, 4); $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system'; - $text .= ' -> the requested PHP extension '.$ext.' '.$error.'.'; + return $text . ' -> the requested PHP extension '.$ext.' '.$error.'.'; } elseif (0 === strpos($targetName, 'lib-')) { // handle linked libs $lib = substr($targetName, 4); - $text .= ' -> the requested linked library '.$lib.' has the wrong version installed or is missing from your system, make sure to have the extension providing it.'; + return $text . ' -> the requested linked library '.$lib.' has the wrong version installed or is missing from your system, make sure to have the extension providing it.'; } else { - $text .= ' -> no matching package found.'; + return $text . ' -> no matching package found.'; } } diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 27f52aa65..5016335b0 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -59,6 +59,7 @@ class PlatformRepository extends ArrayRepository $version = $versionParser->normalize($override['version']); $package = new CompletePackage($override['name'], $version, $override['version']); $package->setDescription('Package overridden via config.platform'); + $package->setExtra(array('config.platform' => true)); parent::addPackage($package); } diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test.skipped similarity index 85% rename from tests/Composer/Test/Fixtures/installer/github-issues-4319.test rename to tests/Composer/Test/Fixtures/installer/github-issues-4319.test.skipped index 5bfc17af6..3833bcf9b 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test.skipped @@ -37,7 +37,7 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - Installation request for a ~1.0 -> satisfiable by a[1.0.0]. - - a 1.0.0 requires php 5.5 -> your PHP version or "config.platform.php" value (5.3) does not satisfy that requirement. + - a 1.0.0 requires php 5.5 -> your PHP version (5.6.17) overriden by "config.platform.php" version (5.3) does not satisfy that requirement. --EXPECT-- From baa84d9be1477b2db7d964a6b52aae8302fbfc1d Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Fri, 5 Feb 2016 13:34:21 +0100 Subject: [PATCH 34/40] adjust test and assertion to be more flexible --- .../Test/Fixtures/installer/github-issues-4319.test.skipped | 2 +- tests/Composer/Test/InstallerTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test.skipped b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test.skipped index 3833bcf9b..56536ed72 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test.skipped +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test.skipped @@ -37,7 +37,7 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - Installation request for a ~1.0 -> satisfiable by a[1.0.0]. - - a 1.0.0 requires php 5.5 -> your PHP version (5.6.17) overriden by "config.platform.php" version (5.3) does not satisfy that requirement. + - a 1.0.0 requires php 5.5 -> your PHP version (%s) overriden by "config.platform.php" version (5.3) does not satisfy that requirement. --EXPECT-- diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index eaf6caa03..5aa9f862e 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -252,7 +252,7 @@ class InstallerTest extends TestCase $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace())); if ($expectOutput) { - $this->assertEquals(rtrim($expectOutput), rtrim($output)); + $this->assertStringMatchesFormat(rtrim($expectOutput), rtrim($output)); } } From 5db0f623b01d802b20dc49ad9d430a43b668cbf7 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Fri, 5 Feb 2016 13:36:53 +0100 Subject: [PATCH 35/40] enable test again --- .../{github-issues-4319.test.skipped => github-issues-4319.test} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/Composer/Test/Fixtures/installer/{github-issues-4319.test.skipped => github-issues-4319.test} (100%) diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test.skipped b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test similarity index 100% rename from tests/Composer/Test/Fixtures/installer/github-issues-4319.test.skipped rename to tests/Composer/Test/Fixtures/installer/github-issues-4319.test From c2d99608988e7892a4c67f0c57c0fdb68de2d4b1 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sat, 6 Feb 2016 02:00:54 +0100 Subject: [PATCH 36/40] Ensure exception is thrown when classmaps are requested for corrupted or binary files. Refs #4885 --- src/Composer/Autoload/ClassMapGenerator.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 3f1243ade..539eb77a0 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -123,14 +123,14 @@ class ClassMapGenerator } try { - $contents = Silencer::call('php_strip_whitespace', $path); + $contents = @php_strip_whitespace($path); if (!$contents) { if (!file_exists($path)) { - throw new \Exception('File does not exist'); - } - if (!is_readable($path)) { - throw new \Exception('File is not readable'); + throw new \RuntimeException(sprintf('File at "%s" does not exist, check your classmap definitions', $path)); + } elseif (!is_readable($path)) { + throw new \RuntimeException(sprintf('File at "%s" is not readable, check its permissions', $path)); } + throw new \RuntimeException(sprintf('File at "%s" could not be parsed as PHP - it may be binary or corrupted', $path)); } } catch (\Exception $e) { throw new \RuntimeException('Could not scan for classes inside '.$path.": \n".$e->getMessage(), 0, $e); From 86fc85fe56c0cb8336c9628fd719c0134360f27b Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sat, 6 Feb 2016 02:04:48 +0100 Subject: [PATCH 37/40] Add a comment explaining the use of @ instead of Silencer in this specific situation. --- src/Composer/Autoload/ClassMapGenerator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 539eb77a0..3ad26f7f2 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -123,6 +123,8 @@ class ClassMapGenerator } try { + // Use @ here instead of Silencer to actively suppress 'unhelpful' output + // @link https://github.com/composer/composer/pull/4886 $contents = @php_strip_whitespace($path); if (!$contents) { if (!file_exists($path)) { From bb08f76ad99142e4253b7ac90294fe1dbc266579 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sat, 6 Feb 2016 02:32:08 +0100 Subject: [PATCH 38/40] Use error_get_last to verify why php_strip_whitespace would return an empty string. --- src/Composer/Autoload/ClassMapGenerator.php | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 3ad26f7f2..9bacee580 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -122,20 +122,20 @@ class ClassMapGenerator $extraTypes .= '|enum'; } - try { - // Use @ here instead of Silencer to actively suppress 'unhelpful' output - // @link https://github.com/composer/composer/pull/4886 - $contents = @php_strip_whitespace($path); - if (!$contents) { - if (!file_exists($path)) { - throw new \RuntimeException(sprintf('File at "%s" does not exist, check your classmap definitions', $path)); - } elseif (!is_readable($path)) { - throw new \RuntimeException(sprintf('File at "%s" is not readable, check its permissions', $path)); - } - throw new \RuntimeException(sprintf('File at "%s" could not be parsed as PHP - it may be binary or corrupted', $path)); + // Use @ here instead of Silencer to actively suppress 'unhelpful' output + // @link https://github.com/composer/composer/pull/4886 + error_clear_last(); + $contents = @php_strip_whitespace($path); + if (!$contents) { + if (is_null(error_get_last())) { + // No error, so the input file was really empty or contained only comments + return array(); + } elseif (!file_exists($path)) { + throw new \RuntimeException(sprintf('File at "%s" does not exist, check your classmap definitions', $path)); + } elseif (!is_readable($path)) { + throw new \RuntimeException(sprintf('File at "%s" is not readable, check its permissions', $path)); } - } catch (\Exception $e) { - throw new \RuntimeException('Could not scan for classes inside '.$path.": \n".$e->getMessage(), 0, $e); + throw new \RuntimeException(sprintf('File at "%s" could not be parsed as PHP - it may be binary or corrupted', $path)); } // return early if there is no chance of matching anything in this file From 6a53b1df42e124a95d12a5e6c953fa28ae0c8235 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sat, 6 Feb 2016 02:40:16 +0100 Subject: [PATCH 39/40] Further reorganized messy checking code. --- src/Composer/Autoload/ClassMapGenerator.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 9bacee580..818367c98 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -127,15 +127,21 @@ class ClassMapGenerator error_clear_last(); $contents = @php_strip_whitespace($path); if (!$contents) { - if (is_null(error_get_last())) { - // No error, so the input file was really empty or contained only comments + $error = error_get_last(); + if (is_null($error)) { + // No error, so the input file was really empty and thus contains no classes return array(); } elseif (!file_exists($path)) { - throw new \RuntimeException(sprintf('File at "%s" does not exist, check your classmap definitions', $path)); + $message = 'File at "%s" does not exist, check your classmap definitions'; } elseif (!is_readable($path)) { - throw new \RuntimeException(sprintf('File at "%s" is not readable, check its permissions', $path)); + $message = 'File at "%s" is not readable, check its permissions'; + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; } - throw new \RuntimeException(sprintf('File at "%s" could not be parsed as PHP - it may be binary or corrupted', $path)); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + throw new \RuntimeException(sprintf($message, $path)); } // return early if there is no chance of matching anything in this file From 0b55a0ca9122abfd7ede51d67f5defe4dfd64daf Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sat, 6 Feb 2016 02:58:36 +0100 Subject: [PATCH 40/40] Can't use error_clear_last as it was introduced in PHP7. --- src/Composer/Autoload/ClassMapGenerator.php | 11 +++++------ .../Composer/Test/Autoload/ClassMapGeneratorTest.php | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 818367c98..59542a4a7 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -124,20 +124,19 @@ class ClassMapGenerator // Use @ here instead of Silencer to actively suppress 'unhelpful' output // @link https://github.com/composer/composer/pull/4886 - error_clear_last(); $contents = @php_strip_whitespace($path); if (!$contents) { - $error = error_get_last(); - if (is_null($error)) { - // No error, so the input file was really empty and thus contains no classes - return array(); - } elseif (!file_exists($path)) { + if (!file_exists($path)) { $message = 'File at "%s" does not exist, check your classmap definitions'; } elseif (!is_readable($path)) { $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim(file_get_contents($path))) { + // The input file was really empty and thus contains no classes + return array(); } else { $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; } + $error = error_get_last(); if (isset($error['message'])) { $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; } diff --git a/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php index cd3d43260..13cf7cd83 100644 --- a/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php +++ b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php @@ -113,7 +113,7 @@ class ClassMapGeneratorTest extends TestCase /** * @expectedException \RuntimeException - * @expectedExceptionMessage Could not scan for classes inside + * @expectedExceptionMessage does not exist */ public function testFindClassesThrowsWhenFileDoesNotExist() {