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 $addedPackages;
|
||||
protected $addedPackagesByNames;
|
||||
protected $conflictsForName;
|
||||
|
||||
public function __construct(PolicyInterface $policy, Pool $pool)
|
||||
{
|
||||
|
@ -128,6 +129,20 @@ class RuleSetGenerator
|
|||
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
|
||||
*
|
||||
|
@ -189,9 +204,16 @@ class RuleSetGenerator
|
|||
|
||||
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
|
||||
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
|
||||
} elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
|
||||
$reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
|
||||
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $package));
|
||||
} else {
|
||||
if (!isset($this->conflictsForName[$packageName])) {
|
||||
$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)
|
||||
|
@ -316,6 +345,7 @@ class RuleSetGenerator
|
|||
$this->conflictAddedMap = array();
|
||||
$this->addedPackages = array();
|
||||
$this->addedPackagesByNames = array();
|
||||
$this->conflictsForName = array();
|
||||
|
||||
$this->addRulesForRequest($request, $ignorePlatformReqs);
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ class RuleWatchGraph
|
|||
return;
|
||||
}
|
||||
|
||||
if (!$node->getRule() instanceof MultiConflictRule) {
|
||||
foreach (array($node->watch1, $node->watch2) as $literal) {
|
||||
if (!isset($this->watchChains[$literal])) {
|
||||
$this->watchChains[$literal] = new RuleWatchChain;
|
||||
|
@ -51,6 +52,16 @@ class RuleWatchGraph
|
|||
|
||||
$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,6 +103,7 @@ class RuleWatchGraph
|
|||
$chain->rewind();
|
||||
while ($chain->valid()) {
|
||||
$node = $chain->current();
|
||||
if (!$node->getRule() instanceof MultiConflictRule) {
|
||||
$otherWatch = $node->getOtherWatch($literal);
|
||||
|
||||
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
|
||||
|
@ -115,6 +127,17 @@ class RuleWatchGraph
|
|||
|
||||
$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();
|
||||
}
|
||||
|
||||
$decisions->decide($otherLiteral, $level, $node->getRule());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$chain->next();
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class RuleWatchNode
|
|||
$literals = $this->rule->getLiterals();
|
||||
|
||||
// if there are only 2 elements, both are being watched anyway
|
||||
if (count($literals) < 3) {
|
||||
if (count($literals) < 3 || $this->rule instanceof MultiConflictRule) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue