Whitelist packages with names matching those specified before generating rules

Addresses #2690 doesn't do any performance optimisations yet which we
could do now
main
Nils Adermann 11 years ago
parent 0df9c803a5
commit 3148ffd355

@ -42,11 +42,11 @@ class DefaultPolicy implements PolicyInterface
return $constraint->matchSpecific($version, true); return $constraint->matchSpecific($version, true);
} }
public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package) public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package, $mustMatchName = false)
{ {
$packages = array(); $packages = array();
foreach ($pool->whatProvides($package->getName()) as $candidate) { foreach ($pool->whatProvides($package->getName(), null, $mustMatchName) as $candidate) {
if ($candidate !== $package) { if ($candidate !== $package) {
$packages[] = $candidate; $packages[] = $candidate;
} }

@ -50,6 +50,7 @@ class Pool
protected $versionParser; protected $versionParser;
protected $providerCache = array(); protected $providerCache = array();
protected $filterRequires; protected $filterRequires;
protected $whitelist = null;
protected $id = 1; protected $id = 1;
public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array()) public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array())
@ -66,6 +67,11 @@ class Pool
$this->filterRequires = $filterRequires; $this->filterRequires = $filterRequires;
} }
public function setWhitelist($whitelist)
{
$this->whitelist = $whitelist;
}
/** /**
* Adds a repository and its packages to this package pool * Adds a repository and its packages to this package pool
* *
@ -223,21 +229,24 @@ class Pool
* @param string $name The package name to be searched for * @param string $name The package name to be searched for
* @param LinkConstraintInterface $constraint A constraint that all returned * @param LinkConstraintInterface $constraint A constraint that all returned
* packages must match or null to return all * packages must match or null to return all
* @param bool $mustMatchName Whether the name of returned packages
* must match the given name
* @return array A set of packages * @return array A set of packages
*/ */
public function whatProvides($name, LinkConstraintInterface $constraint = null) public function whatProvides($name, LinkConstraintInterface $constraint = null, $mustMatchName = false)
{ {
if (isset($this->providerCache[$name][(string) $constraint])) { $key = ((string) (int) $mustMatchName).((string) $constraint);
return $this->providerCache[$name][(string) $constraint]; if (isset($this->providerCache[$name][$key])) {
return $this->providerCache[$name][$key];
} }
return $this->providerCache[$name][(string) $constraint] = $this->computeWhatProvides($name, $constraint); return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName);
} }
/** /**
* @see whatProvides * @see whatProvides
*/ */
private function computeWhatProvides($name, $constraint) private function computeWhatProvides($name, $constraint, $mustMatchName = false)
{ {
$candidates = array(); $candidates = array();
@ -259,6 +268,9 @@ class Pool
$nameMatch = false; $nameMatch = false;
foreach ($candidates as $candidate) { foreach ($candidates as $candidate) {
if ($this->whitelist !== null && !isset($this->whitelist[$candidate->getId()])) {
continue;
}
switch ($this->match($candidate, $name, $constraint)) { switch ($this->match($candidate, $name, $constraint)) {
case self::MATCH_NONE: case self::MATCH_NONE:
break; break;
@ -289,7 +301,7 @@ class Pool
} }
// if a package with the required name exists, we ignore providers // if a package with the required name exists, we ignore providers
if ($nameMatch) { if ($nameMatch || $mustMatchName) {
return $matches; return $matches;
} }

@ -46,7 +46,7 @@ class Request
protected function addJob($packageName, $cmd, LinkConstraintInterface $constraint = null) protected function addJob($packageName, $cmd, LinkConstraintInterface $constraint = null)
{ {
$packageName = strtolower($packageName); $packageName = strtolower($packageName);
$packages = $this->pool->whatProvides($packageName, $constraint); $packages = $this->pool->whatProvides($packageName, $constraint, true);
$this->jobs[] = array( $this->jobs[] = array(
'packages' => $packages, 'packages' => $packages,

@ -25,6 +25,8 @@ class RuleSetGenerator
protected $rules; protected $rules;
protected $jobs; protected $jobs;
protected $installedMap; protected $installedMap;
protected $whitelistedMap;
protected $addedMap;
public function __construct(PolicyInterface $policy, Pool $pool) public function __construct(PolicyInterface $policy, Pool $pool)
{ {
@ -141,6 +143,41 @@ class RuleSetGenerator
$this->rules->add($newRule, $type); $this->rules->add($newRule, $type);
} }
protected function whitelistFromPackage(PackageInterface $package)
{
$workQueue = new \SplQueue;
$workQueue->enqueue($package);
while (!$workQueue->isEmpty()) {
$package = $workQueue->dequeue();
if (isset($this->whitelistedMap[$package->getId()])) {
continue;
}
$this->whitelistedMap[$package->getId()] = true;
foreach ($package->getRequires() as $link) {
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint(), true);
foreach ($possibleRequires as $require) {
$workQueue->enqueue($require);
}
}
$obsoleteProviders = $this->pool->whatProvides($package->getName(), null, true);
foreach ($obsoleteProviders as $provider) {
if ($provider === $package) {
continue;
}
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
$workQueue->enqueue($provider);
}
}
}
}
protected function addRulesForPackage(PackageInterface $package) protected function addRulesForPackage(PackageInterface $package)
{ {
$workQueue = new \SplQueue; $workQueue = new \SplQueue;
@ -236,6 +273,30 @@ class RuleSetGenerator
} }
} }
private function whitelistFromUpdatePackages(PackageInterface $package)
{
$updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package, true);
foreach ($updates as $update) {
$this->whitelistFromPackage($update);
}
}
protected function whitelistFromJobs()
{
foreach ($this->jobs as $job) {
switch ($job['cmd']) {
case 'install':
if ($job['packages']) {
foreach ($job['packages'] as $package) {
$this->whitelistFromPackage($package);
}
}
break;
}
}
}
protected function addRulesForJobs() protected function addRulesForJobs()
{ {
foreach ($this->jobs as $job) { foreach ($this->jobs as $job) {
@ -270,6 +331,16 @@ class RuleSetGenerator
$this->rules = new RuleSet; $this->rules = new RuleSet;
$this->installedMap = $installedMap; $this->installedMap = $installedMap;
$this->whitelistedNames = array();
foreach ($this->installedMap as $package) {
$this->whitelistFromPackage($package);
$this->whitelistFromUpdatePackages($package);
}
$this->whitelistFromJobs();
$this->pool->setWhitelist($this->whitelistedMap);
$this->addedMap = array();
foreach ($this->installedMap as $package) { foreach ($this->installedMap as $package) {
$this->addRulesForPackage($package); $this->addRulesForPackage($package);
$this->addRulesForUpdatePackages($package); $this->addRulesForUpdatePackages($package);

@ -441,10 +441,9 @@ class SolverTest extends TestCase
$this->request->install('A'); $this->request->install('A');
$this->checkSolverResult(array( // must explicitly pick the provider, so error in this case
array('job' => 'install', 'package' => $packageQ), $this->setExpectedException('Composer\DependencyResolver\SolverProblemsException');
array('job' => 'install', 'package' => $packageA), $this->solver->solve($this->request);
));
} }
public function testSkipReplacerOfExistingPackage() public function testSkipReplacerOfExistingPackage()
@ -574,11 +573,12 @@ class SolverTest extends TestCase
$this->reposComplete(); $this->reposComplete();
$this->request->install('A'); $this->request->install('A');
$this->request->install('C');
$this->checkSolverResult(array( $this->checkSolverResult(array(
array('job' => 'install', 'package' => $packageB),
array('job' => 'install', 'package' => $packageA), array('job' => 'install', 'package' => $packageA),
array('job' => 'install', 'package' => $packageC), array('job' => 'install', 'package' => $packageC),
array('job' => 'install', 'package' => $packageB),
)); ));
} }

@ -1,34 +0,0 @@
--TEST--
Provide only applies when no existing package has the given name
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "higher-prio-hijacker", "version": "1.1.0", "provide": { "package": "1.0.0" } },
{ "name": "provider2", "version": "1.1.0", "provide": { "package2": "1.0.0" } }
]
},
{
"type": "package",
"package": [
{ "name": "package", "version": "0.9.0" },
{ "name": "package", "version": "1.0.0" },
{ "name": "hijacker", "version": "1.1.0", "provide": { "package": "1.0.0" } },
{ "name": "provider3", "version": "1.1.0", "provide": { "package3": "1.0.0" } }
]
}
],
"require": {
"package": "1.*",
"package2": "1.*",
"provider3": "1.1.0"
}
}
--RUN--
install
--EXPECT--
Installing package (1.0.0)
Installing provider2 (1.1.0)
Installing provider3 (1.1.0)
Loading…
Cancel
Save