Merge pull request #8424 from naderman/multi-conflict-rule
New Multi Conflict Rule for transitive conflicts, to reduce memory use by rulespull/8444/head
commit
7fc0cb021e
|
@ -0,0 +1,106 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\DependencyResolver;
|
||||||
|
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Package\Link;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*
|
||||||
|
* MultiConflictRule([A, B, C]) acts as Rule([-A, -B]), Rule([-A, -C]), Rule([-B, -C])
|
||||||
|
*/
|
||||||
|
class MultiConflictRule extends Rule
|
||||||
|
{
|
||||||
|
protected $literals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $literals
|
||||||
|
* @param int $reason A RULE_* constant describing the reason for generating this rule
|
||||||
|
* @param Link|PackageInterface $reasonData
|
||||||
|
* @param array $job The job this rule was created from
|
||||||
|
*/
|
||||||
|
public function __construct(array $literals, $reason, $reasonData, $job = null)
|
||||||
|
{
|
||||||
|
parent::__construct($reason, $reasonData, $job);
|
||||||
|
|
||||||
|
if (count($literals) < 3) {
|
||||||
|
throw new \RuntimeException("multi conflict rule requires at least 3 literals");
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort all packages ascending by id
|
||||||
|
sort($literals);
|
||||||
|
|
||||||
|
$this->literals = $literals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLiterals()
|
||||||
|
{
|
||||||
|
return $this->literals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHash()
|
||||||
|
{
|
||||||
|
$data = unpack('ihash', md5('c:'.implode(',', $this->literals), true));
|
||||||
|
|
||||||
|
return $data['hash'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this rule is equal to another one
|
||||||
|
*
|
||||||
|
* Ignores whether either of the rules is disabled.
|
||||||
|
*
|
||||||
|
* @param Rule $rule The rule to check against
|
||||||
|
* @return bool Whether the rules are equal
|
||||||
|
*/
|
||||||
|
public function equals(Rule $rule)
|
||||||
|
{
|
||||||
|
if ($rule instanceof MultiConflictRule) {
|
||||||
|
return $this->literals === $rule->getLiterals();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAssertion()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
throw new \RuntimeException("Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a rule as a string of the format (Literal1|Literal2|...)
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
// TODO multi conflict?
|
||||||
|
$result = $this->isDisabled() ? 'disabled(multi(' : '(multi(';
|
||||||
|
|
||||||
|
foreach ($this->literals as $i => $literal) {
|
||||||
|
if ($i != 0) {
|
||||||
|
$result .= '|';
|
||||||
|
}
|
||||||
|
$result .= $literal;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result .= '))';
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ class RuleSetGenerator
|
||||||
protected $conflictAddedMap;
|
protected $conflictAddedMap;
|
||||||
protected $addedPackages;
|
protected $addedPackages;
|
||||||
protected $addedPackagesByNames;
|
protected $addedPackagesByNames;
|
||||||
|
protected $conflictsForName;
|
||||||
|
|
||||||
public function __construct(PolicyInterface $policy, Pool $pool)
|
public function __construct(PolicyInterface $policy, Pool $pool)
|
||||||
{
|
{
|
||||||
|
@ -128,6 +129,20 @@ class RuleSetGenerator
|
||||||
return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData);
|
return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function createMultiConflictRule(array $packages, $reason, $reasonData = null)
|
||||||
|
{
|
||||||
|
$literals = array();
|
||||||
|
foreach ($packages as $package) {
|
||||||
|
$literals[] = -$package->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($literals) == 2) {
|
||||||
|
return new Rule2Literals($literals[0], $literals[1], $reason, $reasonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MultiConflictRule($literals, $reason, $reasonData);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a rule unless it duplicates an existing one of any type
|
* Adds a rule unless it duplicates an existing one of any type
|
||||||
*
|
*
|
||||||
|
@ -189,9 +204,16 @@ class RuleSetGenerator
|
||||||
|
|
||||||
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
|
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
|
||||||
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
|
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
|
||||||
} elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
|
} else {
|
||||||
$reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
|
if (!isset($this->conflictsForName[$packageName])) {
|
||||||
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $package));
|
$this->conflictsForName[$packageName] = array();
|
||||||
|
}
|
||||||
|
if (!$package instanceof AliasPackage) {
|
||||||
|
$this->conflictsForName[$packageName][$package->id] = $package;
|
||||||
|
}
|
||||||
|
if (!$provider instanceof AliasPackage) {
|
||||||
|
$this->conflictsForName[$packageName][$provider->id] = $provider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,6 +262,13 @@ class RuleSetGenerator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->conflictsForName as $name => $packages) {
|
||||||
|
if (count($packages) > 1) {
|
||||||
|
$reason = Rule::RULE_PACKAGE_SAME_NAME;
|
||||||
|
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createMultiConflictRule($packages, $reason, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function obsoleteImpossibleForAlias($package, $provider)
|
protected function obsoleteImpossibleForAlias($package, $provider)
|
||||||
|
@ -316,6 +345,7 @@ class RuleSetGenerator
|
||||||
$this->conflictAddedMap = array();
|
$this->conflictAddedMap = array();
|
||||||
$this->addedPackages = array();
|
$this->addedPackages = array();
|
||||||
$this->addedPackagesByNames = array();
|
$this->addedPackagesByNames = array();
|
||||||
|
$this->conflictsForName = array();
|
||||||
|
|
||||||
$this->addRulesForRequest($request, $ignorePlatformReqs);
|
$this->addRulesForRequest($request, $ignorePlatformReqs);
|
||||||
|
|
||||||
|
|
|
@ -44,13 +44,24 @@ class RuleWatchGraph
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (array($node->watch1, $node->watch2) as $literal) {
|
if (!$node->getRule() instanceof MultiConflictRule) {
|
||||||
if (!isset($this->watchChains[$literal])) {
|
foreach (array($node->watch1, $node->watch2) as $literal) {
|
||||||
$this->watchChains[$literal] = new RuleWatchChain;
|
if (!isset($this->watchChains[$literal])) {
|
||||||
}
|
$this->watchChains[$literal] = new RuleWatchChain;
|
||||||
|
}
|
||||||
|
|
||||||
$this->watchChains[$literal]->unshift($node);
|
$this->watchChains[$literal]->unshift($node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($node->getRule()->getLiterals() as $literal) {
|
||||||
|
if (!isset($this->watchChains[$literal])) {
|
||||||
|
$this->watchChains[$literal] = new RuleWatchChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->watchChains[$literal]->unshift($node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,28 +103,40 @@ class RuleWatchGraph
|
||||||
$chain->rewind();
|
$chain->rewind();
|
||||||
while ($chain->valid()) {
|
while ($chain->valid()) {
|
||||||
$node = $chain->current();
|
$node = $chain->current();
|
||||||
$otherWatch = $node->getOtherWatch($literal);
|
if (!$node->getRule() instanceof MultiConflictRule) {
|
||||||
|
$otherWatch = $node->getOtherWatch($literal);
|
||||||
|
|
||||||
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
|
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
|
||||||
$ruleLiterals = $node->getRule()->getLiterals();
|
$ruleLiterals = $node->getRule()->getLiterals();
|
||||||
|
|
||||||
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
|
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
|
||||||
return $literal !== $ruleLiteral &&
|
return $literal !== $ruleLiteral &&
|
||||||
$otherWatch !== $ruleLiteral &&
|
$otherWatch !== $ruleLiteral &&
|
||||||
!$decisions->conflict($ruleLiteral);
|
!$decisions->conflict($ruleLiteral);
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($alternativeLiterals) {
|
if ($alternativeLiterals) {
|
||||||
reset($alternativeLiterals);
|
reset($alternativeLiterals);
|
||||||
$this->moveWatch($literal, current($alternativeLiterals), $node);
|
$this->moveWatch($literal, current($alternativeLiterals), $node);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($decisions->conflict($otherWatch)) {
|
||||||
|
return $node->getRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
$decisions->decide($otherWatch, $level, $node->getRule());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($node->getRule()->getLiterals() as $otherLiteral) {
|
||||||
|
if ($literal !== $otherLiteral && !$decisions->satisfy($otherLiteral)) {
|
||||||
|
if ($decisions->conflict($otherLiteral)) {
|
||||||
|
return $node->getRule();
|
||||||
|
}
|
||||||
|
|
||||||
if ($decisions->conflict($otherWatch)) {
|
$decisions->decide($otherLiteral, $level, $node->getRule());
|
||||||
return $node->getRule();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$decisions->decide($otherWatch, $level, $node->getRule());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$chain->next();
|
$chain->next();
|
||||||
|
|
|
@ -55,7 +55,7 @@ class RuleWatchNode
|
||||||
$literals = $this->rule->getLiterals();
|
$literals = $this->rule->getLiterals();
|
||||||
|
|
||||||
// if there are only 2 elements, both are being watched anyway
|
// if there are only 2 elements, both are being watched anyway
|
||||||
if (count($literals) < 3) {
|
if (count($literals) < 3 || $this->rule instanceof MultiConflictRule) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue