diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 96c3ff821..3f4cc6341 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -271,7 +271,7 @@ class Application extends BaseApplication } if (isset($startTime)) { - $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MB), time: '.round(microtime(true) - $startTime, 2).'s'); + $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); } restore_error_handler(); diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index c0f235659..8b29177d5 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -153,7 +153,7 @@ class ConsoleIO extends BaseIO $memoryUsage = memory_get_usage() / 1024 / 1024; $timeSpent = microtime(true) - $this->startTime; $messages = array_map(function ($message) use ($memoryUsage, $timeSpent) { - return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message); + return sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message); }, (array) $messages); } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index bd0d22e3c..f38086611 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1301,11 +1301,6 @@ class Installer $rootRequires = array_merge($rootRequires, $rootDevRequires); - $requiredPackageNames = array(); - foreach ($rootRequires as $require) { - $requiredPackageNames[] = $require->getTarget(); - } - $skipPackages = array(); if (!$this->whitelistAllDependencies) { foreach ($rootRequires as $require) { @@ -1324,11 +1319,16 @@ class Installer $packageQueue = new \SplQueue; $depPackages = $pool->whatProvides($packageName); - - $nameMatchesRequiredPackage = in_array($packageName, $requiredPackageNames, true); - + $matchesByPattern = array(); // check if the name is a glob pattern that did not match directly - if (!$nameMatchesRequiredPackage) { + if (empty($depPackages)) { + // add any installed package matching the whitelisted name/pattern + $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$'); + foreach ($localOrLockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) { + $matchesByPattern[] = $pool->whatProvides($installedPackage['name']); + } + + // add root requirements which match the whitelisted name/pattern $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName); foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { @@ -1338,6 +1338,10 @@ class Installer } } + if (!empty($matchesByPattern)) { + $depPackages = array_merge($depPackages, call_user_func_array('array_merge', $matchesByPattern)); + } + if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock', 'mirrors'))) { $this->io->writeError('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); } @@ -1369,7 +1373,7 @@ class Installer continue; } - if (isset($skipPackages[$requirePackage->getName()])) { + if (isset($skipPackages[$requirePackage->getName()]) && !preg_match(BasePackage::packageNameToRegexp($packageName), $requirePackage->getName())) { $this->io->writeError('Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.'); continue; } diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index 65ea6860f..f2f5be707 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -239,12 +239,13 @@ abstract class BasePackage implements PackageInterface * Build a regexp from a package name, expanding * globs as required * * @param string $whiteListedPattern + * @param bool $wrap Wrap the cleaned string by the given string * @return string */ - public static function packageNameToRegexp($whiteListedPattern) + public static function packageNameToRegexp($whiteListedPattern, $wrap = '{^%s$}i') { $cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern)); - return "{^" . $cleanedWhiteListedPattern . "$}i"; + return sprintf($wrap, $cleanedWhiteListedPattern); } } diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 615a613f9..c1605bf97 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -453,6 +453,39 @@ class AutoloadGeneratorTest extends TestCase ); } + public function testNonDevAutoloadExclusionWithRecursionReplace() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + )); + + $packages = array(); + $packages[] = $a = new Package('a/a', '1.0', '1.0'); + $packages[] = $b = new Package('b/b', '1.0', '1.0'); + $a->setAutoload(array('psr-0' => array('A' => 'src/', 'A\\B' => 'lib/'))); + $a->setRequires(array( + new Link('a/a', 'c/c'), + )); + $b->setAutoload(array('psr-0' => array('B\\Sub\\Name' => 'src/'))); + $b->setReplaces(array( + new Link('b/b', 'c/c'), + )); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue($packages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/composer'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); + + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); + $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + } + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test new file mode 100644 index 000000000..8ea177cad --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test @@ -0,0 +1,46 @@ +--TEST-- +Update with a package whitelist pattern and all-dependencies flag updates packages and their dependencies, even if defined as root dependency, matching the pattern +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.*" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.*" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "dependency": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* --with-all-dependencies +--EXPECT-- +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) +Updating dependency (1.0.0) to dependency (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test new file mode 100644 index 000000000..c685f14ce --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test @@ -0,0 +1,49 @@ +--TEST-- +Update with a package whitelist only updates those packages and their dependencies matching the pattern but no dependencies defined as roo package +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.*", "root-dependency": "1.*" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.*", "root-dependency": "1.*" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "root-dependency", "version": "1.1.0" }, + { "name": "root-dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "root-dependency": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "root-dependency", "version": "1.0.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* --with-dependencies +--EXPECT-- +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) +Updating dependency (1.0.0) to dependency (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test new file mode 100644 index 000000000..a24bafb91 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test @@ -0,0 +1,55 @@ +--TEST-- +Update with a package whitelist only updates those packages and their dependencies matching the pattern +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0", "require": { "whitelisted-component2": "1.1.0" } }, + { "name": "whitelisted-component1", "version": "1.0.0", "require": { "whitelisted-component2": "1.0.0" } }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.1.0", "whitelisted-component5": "1.0.0" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "whitelisted-component3", "version": "1.1.0", "require": { "whitelisted-component4": "1.1.0" } }, + { "name": "whitelisted-component3", "version": "1.0.0", "require": { "whitelisted-component4": "1.0.0" } }, + { "name": "whitelisted-component4", "version": "1.1.0" }, + { "name": "whitelisted-component4", "version": "1.0.0" }, + { "name": "whitelisted-component5", "version": "1.1.0" }, + { "name": "whitelisted-component5", "version": "1.0.0" }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "whitelisted-component3": "1.0.0", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0", "require": { "whitelisted-component2": "1.0.0" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "whitelisted-component3", "version": "1.0.0", "require": { "whitelisted-component4": "1.0.0" } }, + { "name": "whitelisted-component4", "version": "1.0.0" }, + { "name": "whitelisted-component5", "version": "1.0.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* --with-dependencies +--EXPECT-- +Updating dependency (1.0.0) to dependency (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test new file mode 100644 index 000000000..e5551b43f --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test @@ -0,0 +1,44 @@ +--TEST-- +Update with a package whitelist only updates those packages matching the pattern +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.*" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.*" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* +--EXPECT-- +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0)