From c0e5736ae7c9c4cd3052c97bb38656382f1c4cff Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 1 Oct 2012 13:58:01 +0200 Subject: [PATCH 01/12] Add support for one-file-per-provider composer repositories --- src/Composer/DependencyResolver/Pool.php | 45 +++++++---- src/Composer/Json/JsonFile.php | 2 +- .../Repository/ComposerRepository.php | 75 ++++++++++++++++++- 3 files changed, 107 insertions(+), 15 deletions(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 5ee39d9a9..57e1b245c 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -20,6 +20,7 @@ use Composer\Package\LinkConstraint\LinkConstraintInterface; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Repository\RepositoryInterface; use Composer\Repository\CompositeRepository; +use Composer\Repository\ComposerRepository; use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\StreamableRepositoryInterface; use Composer\Repository\PlatformRepository; @@ -39,11 +40,13 @@ class Pool const MATCH_REPLACE = 3; protected $repositories = array(); + protected $composerRepos = array(); protected $packages = array(); protected $packageByName = array(); protected $acceptableStabilities; protected $stabilityFlags; protected $versionParser; + protected $id = 1; public function __construct($minimumStability = 'stable', array $stabilityFlags = array()) { @@ -72,18 +75,20 @@ class Pool $repos = array($repo); } - $id = count($this->packages) + 1; foreach ($repos as $repo) { $this->repositories[] = $repo; $exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface; - if ($repo instanceof StreamableRepositoryInterface) { + + if ($repo instanceof ComposerRepository && $repo->hasProviders()) { + $this->composerRepos[] = $repo; + } elseif ($repo instanceof StreamableRepositoryInterface) { foreach ($repo->getMinimalPackages() as $package) { $name = $package['name']; $version = $package['version']; $stability = VersionParser::parseStability($version); if ($exempt || $this->isPackageAcceptable($name, $stability)) { - $package['id'] = $id++; + $package['id'] = $this->id++; $this->packages[] = $package; // collect names @@ -102,7 +107,7 @@ class Pool } foreach (array_keys($names) as $provided) { - $this->packageByName[$provided][] =& $this->packages[$id-2]; + $this->packageByName[$provided][] =& $this->packages[$this->id - 2]; } // handle root package aliases @@ -119,12 +124,12 @@ class Pool $alias['version'] = $rootAliasData['alias_normalized']; $alias['alias'] = $rootAliasData['alias']; $alias['alias_of'] = $package['id']; - $alias['id'] = $id++; + $alias['id'] = $this->id++; $alias['root_alias'] = true; $this->packages[] = $alias; foreach (array_keys($names) as $name) { - $this->packageByName[$name][] =& $this->packages[$id-2]; + $this->packageByName[$name][] =& $this->packages[$this->id - 2]; } } @@ -135,11 +140,11 @@ class Pool $alias['version'] = $package['alias_normalized']; $alias['alias'] = $package['alias']; $alias['alias_of'] = $package['id']; - $alias['id'] = $id++; + $alias['id'] = $this->id++; $this->packages[] = $alias; foreach (array_keys($names) as $name) { - $this->packageByName[$name][] =& $this->packages[$id-2]; + $this->packageByName[$name][] =& $this->packages[$this->id - 2]; } } } @@ -149,7 +154,7 @@ class Pool $name = $package->getName(); $stability = $package->getStability(); if ($exempt || $this->isPackageAcceptable($name, $stability)) { - $package->setId($id++); + $package->setId($this->id++); $this->packages[] = $package; foreach ($package->getNames() as $name) { @@ -163,7 +168,7 @@ class Pool $package->setPrettyAlias($alias['alias']); $package->getRepository()->addPackage($aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias'])); $aliasPackage->setRootPackageAlias(true); - $aliasPackage->setId($id++); + $aliasPackage->setId($this->id++); $this->packages[] = $aliasPackage; @@ -208,7 +213,7 @@ class Pool */ public function getMaxId() { - return count($this->packages); + return $this->id - 1; } /** @@ -221,11 +226,25 @@ class Pool */ public function whatProvides($name, LinkConstraintInterface $constraint = null) { - if (!isset($this->packageByName[$name])) { + $candidates = array(); + + foreach ($this->composerRepos as $repo) { + foreach ($repo->whatProvides($name) as $candidate) { + $candidates[] = $candidate; + if ($candidate->getId() < 1) { + $candidate->setId($this->id++); + $this->packages[$candidate->getId()] = $candidate; + } + } + } + + if (!isset($this->packageByName[$name]) && !$candidates) { return array(); } - $candidates = $this->packageByName[$name]; + if (isset($this->packageByName[$name])) { + $candidates = array_merge($candidates, $this->packageByName[$name]); + } if (null === $constraint) { foreach ($candidates as $key => $candidate) { diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index b089a8b32..00bded4d0 100755 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -85,7 +85,7 @@ class JsonFile $json = file_get_contents($this->path); } } catch (TransportException $e) { - throw new \RuntimeException($e->getMessage()); + throw new \RuntimeException($e->getMessage(), 0, $e); } catch (\Exception $e) { throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage()); } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 49f0bf7a2..6fe23e9ae 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -20,6 +20,7 @@ use Composer\Cache; use Composer\Config; use Composer\IO\IOInterface; use Composer\Util\RemoteFilesystem; +use Composer\Downloader\TransportException; /** * @author Jordi Boggiano @@ -32,10 +33,14 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository protected $io; protected $cache; protected $notifyUrl; + protected $providersUrl; + protected $providers = array(); + protected $providersByUid = array(); protected $loader; private $rawData; private $minimalPackages; private $degradedMode = false; + private $rootData; public function __construct(array $repoConfig, IOInterface $io, Config $config) { @@ -182,6 +187,55 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository return $aliasPackage; } + public function hasProviders() + { + $this->loadRootServerFile(); + + return null !== $this->providersUrl; + } + + public function whatProvides($name) + { + if (isset($this->providers[$name])) { + return $this->providers[$name]; + } + + $url = str_replace('%package%', strtolower($name), $this->providersUrl); + + try { + $json = new JsonFile($url, new RemoteFilesystem($this->io)); + $packages = $json->read(); + } catch (\RuntimeException $e) { + if (!$e->getPrevious() instanceof TransportException || $e->getPrevious()->getCode() !== 404) { + throw $e; + } + } + + $this->providers[$name] = array(); + foreach ($packages['packages'] as $name => $versions) { + foreach ($versions as $version) { + if (isset($this->providersByUid[$version['uid']])) { + $this->providers[$name][] = $this->providersByUid[$version['uid']]; + } else { + $package = $this->createPackage($version, 'Composer\Package\Package'); + $package->setRepository($this); + + $this->providers[$name][] = $package; + $this->providersByUid[$version['uid']] = $package; + + if ($package->getAlias()) { + $alias = $this->createAliasPackage($package); + + $this->providers[$name][] = $alias; + $this->providersByUid[$version['uid']] = $alias; + } + } + } + } + + return $this->providers[$name]; + } + /** * {@inheritDoc} */ @@ -196,8 +250,12 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository } } - protected function loadDataFromServer() + protected function loadRootServerFile() { + if (null !== $this->rootData) { + return $this->rootData; + } + if (!extension_loaded('openssl') && 'https' === substr($this->url, 0, 5)) { throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url); } @@ -220,6 +278,21 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository } } + if (!empty($data['providers'])) { + if ('/' === $data['providers'][0]) { + $this->providersUrl = preg_replace('{(https?://[^/]+).*}i', '$1' . $data['providers'], $this->url); + } else { + $this->providersUrl = $data['providers']; + } + } + + return $this->rootData = $data; + } + + protected function loadDataFromServer() + { + $data = $this->loadRootServerFile(); + return $this->loadIncludes($data); } From 18492a1f8471ef3b5c1ba2b5e0861fa164b8519b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 12 Oct 2012 18:33:27 +0200 Subject: [PATCH 02/12] Remove Pool::getMaxId and the solver's reliance on it --- src/Composer/DependencyResolver/Decisions.php | 32 ++++++++++--------- src/Composer/DependencyResolver/Pool.php | 10 ------ .../Test/DependencyResolver/PoolTest.php | 16 ---------- 3 files changed, 17 insertions(+), 41 deletions(-) diff --git a/src/Composer/DependencyResolver/Decisions.php b/src/Composer/DependencyResolver/Decisions.php index 451cb5ff7..a9808e60e 100644 --- a/src/Composer/DependencyResolver/Decisions.php +++ b/src/Composer/DependencyResolver/Decisions.php @@ -29,12 +29,7 @@ class Decisions implements \Iterator, \Countable public function __construct($pool) { $this->pool = $pool; - - if (version_compare(PHP_VERSION, '5.3.4', '>=')) { - $this->decisionMap = new \SplFixedArray($this->pool->getMaxId() + 1); - } else { - $this->decisionMap = array_fill(0, $this->pool->getMaxId() + 1, 0); - } + $this->decisionMap = array(); } public function decide($literal, $level, $why) @@ -51,8 +46,8 @@ class Decisions implements \Iterator, \Countable $packageId = abs($literal); return ( - $literal > 0 && $this->decisionMap[$packageId] > 0 || - $literal < 0 && $this->decisionMap[$packageId] < 0 + $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 || + $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 ); } @@ -61,29 +56,36 @@ class Decisions implements \Iterator, \Countable $packageId = abs($literal); return ( - ($this->decisionMap[$packageId] > 0 && $literal < 0) || - ($this->decisionMap[$packageId] < 0 && $literal > 0) + (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) || + (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0) ); } public function decided($literalOrPackageId) { - return $this->decisionMap[abs($literalOrPackageId)] != 0; + return !empty($this->decisionMap[abs($literalOrPackageId)]); } public function undecided($literalOrPackageId) { - return $this->decisionMap[abs($literalOrPackageId)] == 0; + return empty($this->decisionMap[abs($literalOrPackageId)]); } public function decidedInstall($literalOrPackageId) { - return $this->decisionMap[abs($literalOrPackageId)] > 0; + $packageId = abs($literalOrPackageId); + + return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0; } public function decisionLevel($literalOrPackageId) { - return abs($this->decisionMap[abs($literalOrPackageId)]); + $packageId = abs($literalOrPackageId); + if (isset($this->decisionMap[$packageId])) { + return abs($this->decisionMap[$packageId]); + } + + return 0; } public function decisionRule($literalOrPackageId) @@ -179,7 +181,7 @@ class Decisions implements \Iterator, \Countable { $packageId = abs($literal); - $previousDecision = $this->decisionMap[$packageId]; + $previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null; if ($previousDecision != 0) { $literalString = $this->pool->literalToString($literal); $package = $this->pool->literalToPackage($literal); diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 57e1b245c..2857924c4 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -206,16 +206,6 @@ class Pool return $this->packages[$id - 1]; } - /** - * Retrieves the highest id assigned to a package in this pool - * - * @return int Highest package id - */ - public function getMaxId() - { - return $this->id - 1; - } - /** * Searches all packages providing the given package name and match the constraint * diff --git a/tests/Composer/Test/DependencyResolver/PoolTest.php b/tests/Composer/Test/DependencyResolver/PoolTest.php index 7c6618582..aa38fa31d 100644 --- a/tests/Composer/Test/DependencyResolver/PoolTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolTest.php @@ -129,20 +129,4 @@ class PoolTest extends TestCase $this->assertEquals(array(), $pool->whatProvides('foo')); } - - public function testGetMaxId() - { - $pool = new Pool; - $repository = new ArrayRepository; - $firstPackage = $this->getPackage('foo', '1'); - $secondPackage = $this->getPackage('foo1', '1'); - - $this->assertEquals(0, $pool->getMaxId()); - - $repository->addPackage($firstPackage); - $repository->addPackage($secondPackage); - $pool->addRepository($repository); - - $this->assertEquals(2, $pool->getMaxId()); - } } From 9369f48dcd81e5f796772c5df461f5c9b071ef16 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 13 Oct 2012 13:01:35 +0200 Subject: [PATCH 03/12] Fix package id --- src/Composer/DependencyResolver/Pool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 2857924c4..95c49ded8 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -223,7 +223,7 @@ class Pool $candidates[] = $candidate; if ($candidate->getId() < 1) { $candidate->setId($this->id++); - $this->packages[$candidate->getId()] = $candidate; + $this->packages[$this->id - 2] = $candidate; } } } From 41c7432feffbfc3607a51fed6e4f29ae7224c639 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 13 Oct 2012 17:18:47 +0200 Subject: [PATCH 04/12] Do not fetch from repo for packages that obviously can not be there --- src/Composer/Repository/ComposerRepository.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 6fe23e9ae..f6339e32e 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -196,6 +196,10 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository public function whatProvides($name) { + if ($name === 'php' || in_array(substr($name, 0, 4), array('ext-', 'lib-'), true) || $name === '__root__') { + return array(); + } + if (isset($this->providers[$name])) { return $this->providers[$name]; } From aafc1f7857b90b3db46e046cad935d5084964c03 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 13 Oct 2012 17:19:06 +0200 Subject: [PATCH 05/12] Make sure alias package have a repo instance set --- src/Composer/Repository/ComposerRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index f6339e32e..a5024189c 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -229,6 +229,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository if ($package->getAlias()) { $alias = $this->createAliasPackage($package); + $alias->setRepository($this); $this->providers[$name][] = $alias; $this->providersByUid[$version['uid']] = $alias; From a3f9accd37852bb2a85a44e31e35fe1fc2b3b3b0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 13 Oct 2012 18:54:48 +0200 Subject: [PATCH 06/12] Fix various dumb issues --- src/Composer/DependencyResolver/Pool.php | 4 +-- .../Repository/ComposerRepository.php | 28 +++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 95c49ded8..fd34897f2 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -219,7 +219,7 @@ class Pool $candidates = array(); foreach ($this->composerRepos as $repo) { - foreach ($repo->whatProvides($name) as $candidate) { + foreach ($repo->whatProvides($this, $name) as $candidate) { $candidates[] = $candidate; if ($candidate->getId() < 1) { $candidate->setId($this->id++); @@ -307,7 +307,7 @@ class Pool return $prefix.' '.$package->getPrettyString(); } - private function isPackageAcceptable($name, $stability) + public function isPackageAcceptable($name, $stability) { // allow if package matches the global stability requirement and has no exception if (!isset($this->stabilityFlags[$name]) && isset($this->acceptableStabilities[$stability])) { diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index a5024189c..5ebb1357f 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -14,7 +14,9 @@ namespace Composer\Repository; use Composer\Package\Loader\ArrayLoader; use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; use Composer\Package\Version\VersionParser; +use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Cache; use Composer\Config; @@ -194,7 +196,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository return null !== $this->providersUrl; } - public function whatProvides($name) + public function whatProvides(Pool $pool, $name) { if ($name === 'php' || in_array(substr($name, 0, 4), array('ext-', 'lib-'), true) || $name === '__root__') { return array(); @@ -216,22 +218,38 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository } $this->providers[$name] = array(); - foreach ($packages['packages'] as $name => $versions) { + foreach ($packages['packages'] as $versions) { foreach ($versions as $version) { + // avoid loading the same objects twice if (isset($this->providersByUid[$version['uid']])) { - $this->providers[$name][] = $this->providersByUid[$version['uid']]; + // skip if already assigned + if (!isset($this->providers[$name][$version['uid']])) { + // expand alias in two packages + if ($this->providersByUid[$version['uid']] instanceof AliasPackage) { + $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]->getAliasOf(); + $this->providers[$name][$version['uid'].'-alias'] = $this->providersByUid[$version['uid']]; + } else { + $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]; + } + } } else { + if (!$pool->isPackageAcceptable($version['name'], VersionParser::parseStability($version['version']))) { + continue; + } + + // load acceptable packages in the providers $package = $this->createPackage($version, 'Composer\Package\Package'); $package->setRepository($this); - $this->providers[$name][] = $package; + $this->providers[$name][$version['uid']] = $package; $this->providersByUid[$version['uid']] = $package; if ($package->getAlias()) { $alias = $this->createAliasPackage($package); $alias->setRepository($this); - $this->providers[$name][] = $alias; + $this->providers[$name][$version['uid'].'-alias'] = $alias; + // override provider with its alias so it can be expanded in the if block above $this->providersByUid[$version['uid']] = $alias; } } From 07f72e9fb634a58ad277396c5a4854c306428127 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 14 Oct 2012 16:33:53 +0200 Subject: [PATCH 07/12] Add support for provider listings --- src/Composer/Cache.php | 8 +++ .../Repository/ComposerRepository.php | 62 ++++++++++++++----- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 837406290..9e14ceaff 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -65,4 +65,12 @@ class Cache return sha1_file($this->root . $file); } } + + public function sha256($file) + { + $file = preg_replace('{[^a-z0-9.]}i', '-', $file); + if ($this->enabled && file_exists($this->root . $file)) { + return hash_file('sha256', $this->root . $file); + } + } } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 5ebb1357f..0d8c38c48 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -32,10 +32,12 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository protected $config; protected $options; protected $url; + protected $baseUrl; protected $io; protected $cache; protected $notifyUrl; - protected $providersUrl; + protected $hasProviders = false; + protected $providerListing; protected $providers = array(); protected $providersByUid = array(); protected $loader; @@ -67,6 +69,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository $this->config = $config; $this->options = $repoConfig['options']; $this->url = $repoConfig['url']; + $this->baseUrl = rtrim(preg_replace('{^(.*)(?:/packages.json)?(?:[?#].*)?$}', '$1', $this->url), '/'); $this->io = $io; $this->cache = new Cache($io, $config->get('home').'/cache/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url)); $this->loader = new ArrayLoader(); @@ -193,11 +196,12 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository { $this->loadRootServerFile(); - return null !== $this->providersUrl; + return $this->hasProviders; } public function whatProvides(Pool $pool, $name) { + // skip platform packages if ($name === 'php' || in_array(substr($name, 0, 4), array('ext-', 'lib-'), true) || $name === '__root__') { return array(); } @@ -206,15 +210,21 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository return $this->providers[$name]; } - $url = str_replace('%package%', strtolower($name), $this->providersUrl); + if (null === $this->providerListing) { + $this->loadProviderListings($this->loadRootServerFile()); + } - try { - $json = new JsonFile($url, new RemoteFilesystem($this->io)); - $packages = $json->read(); - } catch (\RuntimeException $e) { - if (!$e->getPrevious() instanceof TransportException || $e->getPrevious()->getCode() !== 404) { - throw $e; - } + $url = 'p/'.$name.'.json'; + + // package does not exist in this repo + if (!isset($this->providerListing[$url])) { + return array(); + } + + if ($this->cache->sha256($url) === $this->providerListing[$url]['sha256']) { + $packages = json_decode($this->cache->read($url), true); + } else { + $packages = $this->fetchFile($url); } $this->providers[$name] = array(); @@ -301,12 +311,8 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository } } - if (!empty($data['providers'])) { - if ('/' === $data['providers'][0]) { - $this->providersUrl = preg_replace('{(https?://[^/]+).*}i', '$1' . $data['providers'], $this->url); - } else { - $this->providersUrl = $data['providers']; - } + if (!empty($data['providers']) || !empty($data['providers-includes'])) { + $this->hasProviders = true; } return $this->rootData = $data; @@ -319,6 +325,28 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository return $this->loadIncludes($data); } + protected function loadProviderListings($data) + { + if (isset($data['providers'])) { + if (!is_array($this->providerListing)) { + $this->providerListing = array(); + } + $this->providerListing = array_merge($this->providerListing, $data['providers']); + } + + if (isset($data['providers-includes'])) { + foreach ($data['providers-includes'] as $include => $metadata) { + if ($this->cache->sha256($include) === $metadata['sha256']) { + $includedData = json_decode($this->cache->read($include), true); + } else { + $includedData = $this->fetchFile($include); + } + + $this->loadProviderListings($includedData); + } + } + } + protected function loadIncludes($data) { $packages = array(); @@ -369,7 +397,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository { if (!$cacheKey) { $cacheKey = $filename; - $filename = $this->url.'/'.$filename; + $filename = $this->baseUrl.'/'.$filename; } $retries = 3; From 21a0ae9cad2c75fa708ce9c0ffaaff9bf780579a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 14 Oct 2012 16:34:17 +0200 Subject: [PATCH 08/12] Cache whatProvide calls --- src/Composer/DependencyResolver/Pool.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index fd34897f2..381d5ba93 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -46,6 +46,7 @@ class Pool protected $acceptableStabilities; protected $stabilityFlags; protected $versionParser; + protected $providerCache = array(); protected $id = 1; public function __construct($minimumStability = 'stable', array $stabilityFlags = array()) @@ -215,6 +216,18 @@ class Pool * @return array A set of packages */ public function whatProvides($name, LinkConstraintInterface $constraint = null) + { + if (isset($this->providerCache[$name][(string) $constraint])) { + return $this->providerCache[$name][(string) $constraint]; + } + + return $this->providerCache[$name][(string) $constraint] = $this->computeWhatProvides($name, $constraint); + } + + /** + * @see whatProvides + */ + private function computeWhatProvides($name, $constraint) { $candidates = array(); From beb9a5bd727d49bf539db676f1aa946901090257 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 14 Oct 2012 16:35:32 +0200 Subject: [PATCH 09/12] Code optimizations: avoid loops in match() --- src/Composer/DependencyResolver/Pool.php | 16 +-- .../Package/Version/VersionParser.php | 2 +- .../Test/DependencyResolver/SolverTest.php | 125 +++++++++--------- .../Test/Package/Loader/ArrayLoaderTest.php | 2 +- 4 files changed, 72 insertions(+), 73 deletions(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 381d5ba93..a69ae5154 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -241,10 +241,6 @@ class Pool } } - if (!isset($this->packageByName[$name]) && !$candidates) { - return array(); - } - if (isset($this->packageByName[$name])) { $candidates = array_merge($candidates, $this->packageByName[$name]); } @@ -390,16 +386,12 @@ class Pool $replaces = $candidate->getReplaces(); } - foreach ($provides as $link) { - if ($link->getTarget() === $name && $constraint->matches($link->getConstraint())) { - return self::MATCH_PROVIDE; - } + if (isset($provides[$name]) && $constraint->matches($provides[$name]->getConstraint())) { + return self::MATCH_PROVIDE; } - foreach ($replaces as $link) { - if ($link->getTarget() === $name && $constraint->matches($link->getConstraint())) { - return self::MATCH_REPLACE; - } + if (isset($replaces[$name]) && $constraint->matches($replaces[$name]->getConstraint())) { + return self::MATCH_REPLACE; } return self::MATCH_NONE; diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index a87184f53..bad90ad81 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -186,7 +186,7 @@ class VersionParser } else { $parsedConstraint = $this->parseConstraints($constraint); } - $res[] = new Link($source, $target, $parsedConstraint, $description, $constraint); + $res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint); } return $res; diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index e43f98c6e..9ca29bcd2 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -19,6 +19,7 @@ use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\SolverProblemsException; use Composer\Package\Link; use Composer\Test\TestCase; +use Composer\Package\LinkConstraint\MultiConstraint; class SolverTest extends TestCase { @@ -103,7 +104,7 @@ class SolverTest extends TestCase $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires'))); $this->reposComplete(); @@ -124,9 +125,11 @@ class SolverTest extends TestCase $this->repo->addPackage($newPackageB13 = $this->getPackage('B', '1.3')); $packageA->setRequires(array( - new Link('A', 'B', $this->getVersionConstraint('<=', '1.3'), 'requires'), - new Link('A', 'B', $this->getVersionConstraint('<>', '1.3'), 'requires'), - new Link('A', 'B', $this->getVersionConstraint('!=', '1.2'), 'requires'), + 'b' => new Link('A', 'B', new MultiConstraint(array( + $this->getVersionConstraint('<=', '1.3'), + $this->getVersionConstraint('<>', '1.3'), + $this->getVersionConstraint('!=', '1.2'), + )), 'requires'), )); $this->reposComplete(); @@ -146,11 +149,11 @@ class SolverTest extends TestCase $this->repo->addPackage($packageC = $this->getPackage('C', '1.0')); $packageB->setRequires(array( - new Link('B', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires'), - new Link('B', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'), + 'a' => new Link('B', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires'), + 'c' => new Link('B', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'), )); $packageC->setRequires(array( - new Link('C', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires'), + 'a' => new Link('C', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires'), )); $this->reposComplete(); @@ -216,7 +219,7 @@ class SolverTest extends TestCase $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->reposComplete(); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0.0.0'), 'requires'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0.0.0'), 'requires'))); $this->request->install('A', $this->getVersionConstraint('=', '1.0.0.0')); $this->request->install('B', $this->getVersionConstraint('=', '1.1.0.0')); @@ -249,8 +252,8 @@ class SolverTest extends TestCase $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.1')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); - $packageA->setRequires(array(new Link('A', 'B', null, 'requires'))); - $newPackageA->setRequires(array(new Link('A', 'B', null, 'requires'))); + $packageA->setRequires(array('b' => new Link('A', 'B', null, 'requires'))); + $newPackageA->setRequires(array('b' => new Link('A', 'B', null, 'requires'))); $this->reposComplete(); @@ -361,7 +364,7 @@ class SolverTest extends TestCase $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($packageC = $this->getPackage('C', '1.1')); $this->repo->addPackage($this->getPackage('D', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires'))); $this->reposComplete(); @@ -384,8 +387,8 @@ class SolverTest extends TestCase $this->repo->addPackage($middlePackageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($oldPackageB = $this->getPackage('B', '0.9')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires'))); - $packageA->setConflicts(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.0'), 'conflicts'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires'))); + $packageA->setConflicts(array('b' => new Link('A', 'B', $this->getVersionConstraint('<', '1.0'), 'conflicts'))); $this->reposComplete(); @@ -401,7 +404,7 @@ class SolverTest extends TestCase { $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); - $packageB->setReplaces(array(new Link('B', 'A', null))); + $packageB->setReplaces(array('a' => new Link('B', 'A', null))); $this->reposComplete(); @@ -430,8 +433,8 @@ class SolverTest extends TestCase { $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); - $packageQ->setProvides(array(new Link('Q', 'B', $this->getVersionConstraint('=', '1.0'), 'provides'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageQ->setProvides(array('b' => new Link('Q', 'B', $this->getVersionConstraint('=', '1.0'), 'provides'))); $this->reposComplete(); @@ -448,8 +451,8 @@ class SolverTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); - $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageQ->setReplaces(array('b' => new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'))); $this->reposComplete(); @@ -465,8 +468,8 @@ class SolverTest extends TestCase { $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); - $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageQ->setReplaces(array('b' => new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'))); $this->reposComplete(); @@ -483,8 +486,8 @@ class SolverTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); - $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageQ->setReplaces(array('b' => new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'))); $this->reposComplete(); @@ -501,24 +504,28 @@ class SolverTest extends TestCase { $this->repo->addPackage($packageX = $this->getPackage('X', '1.0')); $packageX->setRequires(array( - new Link('X', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'), - new Link('X', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'))); + 'a' => new Link('X', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'), + 'b' => new Link('X', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires') + )); $this->repo->addPackage($packageA = $this->getPackage('A', '2.0.0')); $this->repo->addPackage($newPackageA = $this->getPackage('A', '2.1.0')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '2.1.0')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'))); // new package A depends on version of package B that does not exist // => new package A is not installable - $newPackageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '2.2.0.0'), 'requires'))); + $newPackageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('>=', '2.2.0.0'), 'requires'))); // add a package S replacing both A and B, so that S and B or S and A cannot be simultaneously installed // but an alternative option for A and B both exists // this creates a more difficult so solve conflict $this->repo->addPackage($packageS = $this->getPackage('S', '2.0.0')); - $packageS->setReplaces(array(new Link('S', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces'), new Link('S', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces'))); + $packageS->setReplaces(array( + 'a' => new Link('S', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces'), + 'b' => new Link('S', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces') + )); $this->reposComplete(); @@ -536,8 +543,8 @@ class SolverTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageB1 = $this->getPackage('B', '0.9')); $this->repo->addPackage($packageB2 = $this->getPackage('B', '1.1')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); - $packageB2->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageB2->setRequires(array('a' => new Link('B', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires'))); $this->reposComplete(); @@ -555,13 +562,13 @@ class SolverTest extends TestCase $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageC = $this->getPackage('C', '1.0')); $this->repo->addPackage($packageD = $this->getPackage('D', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); - $packageB->setRequires(array(new Link('B', 'Virtual', $this->getVersionConstraint('>=', '1.0'), 'requires'))); - $packageC->setProvides(array(new Link('C', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides'))); - $packageD->setProvides(array(new Link('D', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides'))); + $packageA->setRequires(array('b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageB->setRequires(array('virtual' => new Link('B', 'Virtual', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageC->setProvides(array('virtual' => new Link('C', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides'))); + $packageD->setProvides(array('virtual' => new Link('D', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides'))); - $packageC->setRequires(array(new Link('C', 'A', $this->getVersionConstraint('==', '1.0'), 'requires'))); - $packageD->setRequires(array(new Link('D', 'A', $this->getVersionConstraint('==', '1.0'), 'requires'))); + $packageC->setRequires(array('a' => new Link('C', 'A', $this->getVersionConstraint('==', '1.0'), 'requires'))); + $packageD->setRequires(array('a' => new Link('D', 'A', $this->getVersionConstraint('==', '1.0'), 'requires'))); $this->reposComplete(); @@ -586,18 +593,18 @@ class SolverTest extends TestCase $this->repo->addPackage($packageD2 = $this->getPackage('D', '1.1')); $packageA->setRequires(array( - new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'), - new Link('A', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'), + 'b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'), + 'c' => new Link('A', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'), )); $packageD->setReplaces(array( - new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'), - new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'), + 'b' => new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'), + 'c' => new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'), )); $packageD2->setReplaces(array( - new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'), - new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'), + 'b' => new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'), + 'c' => new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'), )); $this->reposComplete(); @@ -621,19 +628,19 @@ class SolverTest extends TestCase $this->repo->addPackage($packageD = $this->getPackage('D', '2.0.9')); $packageC->setRequires(array( - new Link('C', 'A', $this->getVersionConstraint('>=', '2.0'), 'requires'), - new Link('C', 'D', $this->getVersionConstraint('>=', '2.0'), 'requires'), + 'a' => new Link('C', 'A', $this->getVersionConstraint('>=', '2.0'), 'requires'), + 'd' => new Link('C', 'D', $this->getVersionConstraint('>=', '2.0'), 'requires'), )); $packageD->setRequires(array( - new Link('D', 'A', $this->getVersionConstraint('>=', '2.1'), 'requires'), - new Link('D', 'B', $this->getVersionConstraint('>=', '2.0-dev'), 'requires'), + 'a' => new Link('D', 'A', $this->getVersionConstraint('>=', '2.1'), 'requires'), + 'b' => new Link('D', 'B', $this->getVersionConstraint('>=', '2.0-dev'), 'requires'), )); - $packageB1->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires'))); - $packageB2->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires'))); + $packageB1->setRequires(array('a' => new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires'))); + $packageB2->setRequires(array('a' => new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires'))); - $packageB2->setReplaces(array(new Link('B', 'D', $this->getVersionConstraint('==', '2.0.9.0'), 'replaces'))); + $packageB2->setReplaces(array('d' => new Link('B', 'D', $this->getVersionConstraint('==', '2.0.9.0'), 'replaces'))); $this->reposComplete(); @@ -650,7 +657,7 @@ class SolverTest extends TestCase $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));; $packageA->setConflicts(array( - new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'conflicts'), + 'b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'conflicts'), )); $this->reposComplete(); @@ -680,7 +687,7 @@ class SolverTest extends TestCase $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $packageA->setRequires(array( - new Link('A', 'B', $this->getVersionConstraint('>=', '2.0'), 'requires'), + 'b' => new Link('A', 'B', $this->getVersionConstraint('>=', '2.0'), 'requires'), )); $this->reposComplete(); @@ -717,16 +724,16 @@ class SolverTest extends TestCase $this->repo->addPackage($packageD = $this->getPackage('D', '1.0')); $packageA->setRequires(array( - new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'), + 'b' => new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'), )); $packageB->setRequires(array( - new Link('B', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'), + 'c' => new Link('B', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'), )); $packageC->setRequires(array( - new Link('C', 'D', $this->getVersionConstraint('>=', '1.0'), 'requires'), + 'd' => new Link('C', 'D', $this->getVersionConstraint('>=', '1.0'), 'requires'), )); $packageD->setRequires(array( - new Link('D', 'B', $this->getVersionConstraint('<', '1.0'), 'requires'), + 'b' => new Link('D', 'B', $this->getVersionConstraint('<', '1.0'), 'requires'), )); $this->reposComplete(); @@ -761,11 +768,11 @@ class SolverTest extends TestCase $this->repo->addPackage($packageTwigBridge = $this->getPackage('symfony/twig-bridge', '2.0')); $packageTwigBridge->setRequires(array( - new Link('symfony/twig-bridge', 'twig/twig', $this->getVersionConstraint('<', '2.0'), 'requires'), + 'twig/twig' => new Link('symfony/twig-bridge', 'twig/twig', $this->getVersionConstraint('<', '2.0'), 'requires'), )); $packageSymfony->setReplaces(array( - new Link('symfony/symfony', 'symfony/twig-bridge', $this->getVersionConstraint('==', '2.0'), 'replaces'), + 'symfony/twig-bridge' => new Link('symfony/symfony', 'symfony/twig-bridge', $this->getVersionConstraint('==', '2.0'), 'replaces'), )); $this->reposComplete(); @@ -786,10 +793,10 @@ class SolverTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0')); $packageA2->setRequires(array( - new Link('A', 'B', $this->getVersionConstraint('==', '2.0'), 'requires', '== 2.0'), + 'b' => new Link('A', 'B', $this->getVersionConstraint('==', '2.0'), 'requires', '== 2.0'), )); $packageB->setRequires(array( - new Link('B', 'A', $this->getVersionConstraint('>=', '2.0'), 'requires'), + 'a' => new Link('B', 'A', $this->getVersionConstraint('>=', '2.0'), 'requires'), )); $this->repo->addPackage($packageA2Alias = $this->getAliasPackage($packageA2, '1.1')); @@ -811,7 +818,7 @@ class SolverTest extends TestCase $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $packageB->setRequires(array( - new Link('B', 'A', $this->getVersionConstraint('<', '2.0'), 'requires'), + 'a' => new Link('B', 'A', $this->getVersionConstraint('<', '2.0'), 'requires'), )); $this->repo->addPackage($packageAAlias = $this->getAliasPackage($packageA, '1.1')); diff --git a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php index 508936d2d..ef1295ff8 100644 --- a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php @@ -34,7 +34,7 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase $package = $this->loader->load($config); $replaces = $package->getReplaces(); - $this->assertEquals('== 1.2.3.4', (string) $replaces[0]->getConstraint()); + $this->assertEquals('== 1.2.3.4', (string) $replaces['foo']->getConstraint()); } public function testTypeDefault() From 6a03e4f9ac5992987914baa92ab6d81028919552 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 14 Oct 2012 16:57:44 +0200 Subject: [PATCH 10/12] Code optimizations: Avoiding counting repeatedly --- src/Composer/DependencyResolver/Solver.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index c0a09f16b..2ad3965fe 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -53,7 +53,8 @@ class Solver { $decisionStart = count($this->decisions) - 1; - for ($ruleIndex = 0; $ruleIndex < count($this->rules); $ruleIndex++) { + $rulesCount = count($this->rules); + for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) { $rule = $this->rules->ruleById($ruleIndex); if (!$rule->isAssertion() || $rule->isDisabled()) { From 541bcabbc05fa6d94263b26640eab4130700a684 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 15 Oct 2012 14:37:27 +0200 Subject: [PATCH 11/12] Actually check the hash after downloading --- src/Composer/Repository/ComposerRepository.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 0d8c38c48..e41a505c0 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -224,7 +224,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository if ($this->cache->sha256($url) === $this->providerListing[$url]['sha256']) { $packages = json_decode($this->cache->read($url), true); } else { - $packages = $this->fetchFile($url); + $packages = $this->fetchFile($url, null, $this->providerListing[$url]['sha256']); } $this->providers[$name] = array(); @@ -339,7 +339,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository if ($this->cache->sha256($include) === $metadata['sha256']) { $includedData = json_decode($this->cache->read($include), true); } else { - $includedData = $this->fetchFile($include); + $includedData = $this->fetchFile($include, null, $metadata['sha256']); } $this->loadProviderListings($includedData); @@ -393,7 +393,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository } } - protected function fetchFile($filename, $cacheKey = null) + protected function fetchFile($filename, $cacheKey = null, $sha256 = null) { if (!$cacheKey) { $cacheKey = $filename; @@ -405,7 +405,11 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository try { $json = new JsonFile($filename, new RemoteFilesystem($this->io, $this->options)); $data = $json->read(); - $this->cache->write($cacheKey, json_encode($data)); + $encoded = json_encode($data); + if ($sha256 && $sha256 !== hash('sha256', $encoded)) { + throw new \UnexpectedValueException('The contents of '.$filename.' do not match its signature, this may be due to a temporary glitch or a man-in-the-middle attack, aborting for safety. Please try running Composer again.'); + } + $this->cache->write($cacheKey, $encoded); break; } catch (\Exception $e) { From 5978197b5d4b5c7dd51920d1cc3bc9b02e6a8c50 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 18 Oct 2012 12:57:55 +0200 Subject: [PATCH 12/12] Reset package IDs before they can be used in the pool in case there are already some in the cache --- src/Composer/DependencyResolver/Pool.php | 1 + src/Composer/Repository/ComposerRepository.php | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index a69ae5154..2216d5215 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -83,6 +83,7 @@ class Pool if ($repo instanceof ComposerRepository && $repo->hasProviders()) { $this->composerRepos[] = $repo; + $repo->resetPackageIds(); } elseif ($repo instanceof StreamableRepositoryInterface) { foreach ($repo->getMinimalPackages() as $package) { $name = $package['name']; diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index e41a505c0..03d18c3ca 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -199,6 +199,13 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository return $this->hasProviders; } + public function resetPackageIds() + { + foreach ($this->providersByUid as $package) { + $package->setId(-1); + } + } + public function whatProvides(Pool $pool, $name) { // skip platform packages