1
0
Fork 0

Refactor the handling of conflict rules in the solver

Conflict rules are not added in the solver based on the packages loaded in the
solver by require rules, instead of loading remote metadata for them. This has
2 benefits:

- it reduces the number of conflict rules in the solver in case of conflict
  rules targetting packages which are not required
- it fixes the behavior of replaces, which is meant to conflict with all
  versions of the replaced package, without introducing a performance
  regression (this behavior was changed when optimizing composer in the past).
pull/7454/head
Christophe Coevoet 2018-07-09 22:07:12 +02:00
parent 734735c691
commit e5b948c683
2 changed files with 62 additions and 28 deletions

View File

@ -317,12 +317,12 @@ class Pool implements \Countable
* Checks if the package matches the given constraint directly or through * Checks if the package matches the given constraint directly or through
* provided or replaced packages * provided or replaced packages
* *
* @param array|PackageInterface $candidate * @param PackageInterface $candidate
* @param string $name Name of the package to be matched * @param string $name Name of the package to be matched
* @param ConstraintInterface $constraint The constraint to verify * @param ConstraintInterface $constraint The constraint to verify
* @return int One of the MATCH* constants of this class or 0 if there is no match * @return int One of the MATCH* constants of this class or 0 if there is no match
*/ */
private function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters) public function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters)
{ {
$candidateName = $candidate->getName(); $candidateName = $candidate->getName();
$candidateVersion = $candidate->getVersion(); $candidateVersion = $candidate->getVersion();

View File

@ -28,6 +28,9 @@ class RuleSetGenerator
protected $installedMap; protected $installedMap;
protected $whitelistedMap; protected $whitelistedMap;
protected $addedMap; protected $addedMap;
protected $conflictAddedMap;
protected $addedPackages;
protected $addedPackagesByNames;
public function __construct(PolicyInterface $policy, Pool $pool) public function __construct(PolicyInterface $policy, Pool $pool)
{ {
@ -185,6 +188,7 @@ class RuleSetGenerator
$workQueue->enqueue($package); $workQueue->enqueue($package);
while (!$workQueue->isEmpty()) { while (!$workQueue->isEmpty()) {
/** @var PackageInterface $package */
$package = $workQueue->dequeue(); $package = $workQueue->dequeue();
if (isset($this->addedMap[$package->id])) { if (isset($this->addedMap[$package->id])) {
continue; continue;
@ -192,6 +196,11 @@ class RuleSetGenerator
$this->addedMap[$package->id] = true; $this->addedMap[$package->id] = true;
$this->addedPackages[] = $package;
foreach ($package->getNames() as $name) {
$this->addedPackagesByNames[$name][] = $package;
}
foreach ($package->getRequires() as $link) { foreach ($package->getRequires() as $link) {
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) { if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
continue; continue;
@ -206,32 +215,6 @@ class RuleSetGenerator
} }
} }
foreach ($package->getConflicts() as $link) {
$possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
foreach ($possibleConflicts as $conflict) {
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link));
}
}
// check obsoletes and implicit obsoletes of a package
$isInstalled = isset($this->installedMap[$package->id]);
foreach ($package->getReplaces() as $link) {
$obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
foreach ($obsoleteProviders as $provider) {
if ($provider === $package) {
continue;
}
if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
$reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
}
}
}
$packageName = $package->getName(); $packageName = $package->getName();
$obsoleteProviders = $this->pool->whatProvides($packageName, null); $obsoleteProviders = $this->pool->whatProvides($packageName, null);
@ -250,6 +233,49 @@ class RuleSetGenerator
} }
} }
protected function addConflictRules()
{
/** @var PackageInterface $package */
foreach ($this->addedPackages as $package) {
foreach ($package->getConflicts() as $link) {
if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
continue;
}
/** @var PackageInterface $possibleConflict */
foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
$conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true);
if ($conflictMatch === Pool::MATCH || $conflictMatch === Pool::MATCH_REPLACE) {
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $possibleConflict, Rule::RULE_PACKAGE_CONFLICT, $link));
}
}
}
// check obsoletes and implicit obsoletes of a package
$isInstalled = isset($this->installedMap[$package->id]);
foreach ($package->getReplaces() as $link) {
if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
continue;
}
/** @var PackageInterface $possibleConflict */
foreach ($this->addedPackagesByNames[$link->getTarget()] as $provider) {
if ($provider === $package) {
continue;
}
if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
$reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
}
}
}
}
}
protected function obsoleteImpossibleForAlias($package, $provider) protected function obsoleteImpossibleForAlias($package, $provider)
{ {
$packageIsAlias = $package instanceof AliasPackage; $packageIsAlias = $package instanceof AliasPackage;
@ -327,12 +353,20 @@ class RuleSetGenerator
$this->pool->setWhitelist($this->whitelistedMap); $this->pool->setWhitelist($this->whitelistedMap);
$this->addedMap = array(); $this->addedMap = array();
$this->conflictAddedMap = array();
$this->addedPackages = array();
$this->addedPackagesByNames = array();
foreach ($this->installedMap as $package) { foreach ($this->installedMap as $package) {
$this->addRulesForPackage($package, $ignorePlatformReqs); $this->addRulesForPackage($package, $ignorePlatformReqs);
} }
$this->addRulesForJobs($ignorePlatformReqs); $this->addRulesForJobs($ignorePlatformReqs);
$this->addConflictRules();
// Remove references to packages
$this->addedPackages = $this->addedPackagesByNames = null;
return $this->rules; return $this->rules;
} }
} }