1
0
Fork 0

Merge pull request #8424 from naderman/multi-conflict-rule

New Multi Conflict Rule for transitive conflicts, to reduce memory use by rules
pull/8444/head
Nils Adermann 2019-11-12 23:19:34 +01:00 committed by GitHub
commit 7fc0cb021e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 184 additions and 25 deletions

View File

@ -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;
}
}

View File

@ -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);

View File

@ -44,13 +44,24 @@ class RuleWatchGraph
return;
}
foreach (array($node->watch1, $node->watch2) as $literal) {
if (!isset($this->watchChains[$literal])) {
$this->watchChains[$literal] = new RuleWatchChain;
}
if (!$node->getRule() instanceof MultiConflictRule) {
foreach (array($node->watch1, $node->watch2) as $literal) {
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();
while ($chain->valid()) {
$node = $chain->current();
$otherWatch = $node->getOtherWatch($literal);
if (!$node->getRule() instanceof MultiConflictRule) {
$otherWatch = $node->getOtherWatch($literal);
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
$ruleLiterals = $node->getRule()->getLiterals();
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
$ruleLiterals = $node->getRule()->getLiterals();
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
return $literal !== $ruleLiteral &&
$otherWatch !== $ruleLiteral &&
!$decisions->conflict($ruleLiteral);
});
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
return $literal !== $ruleLiteral &&
$otherWatch !== $ruleLiteral &&
!$decisions->conflict($ruleLiteral);
});
if ($alternativeLiterals) {
reset($alternativeLiterals);
$this->moveWatch($literal, current($alternativeLiterals), $node);
continue;
if ($alternativeLiterals) {
reset($alternativeLiterals);
$this->moveWatch($literal, current($alternativeLiterals), $node);
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)) {
return $node->getRule();
$decisions->decide($otherLiteral, $level, $node->getRule());
}
}
$decisions->decide($otherWatch, $level, $node->getRule());
}
$chain->next();

View File

@ -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;
}