diff --git a/src/Composer/DependencyResolver/MultiConflictRule.php b/src/Composer/DependencyResolver/MultiConflictRule.php new file mode 100644 index 000000000..f2c27c406 --- /dev/null +++ b/src/Composer/DependencyResolver/MultiConflictRule.php @@ -0,0 +1,106 @@ + + * Jordi Boggiano + * + * 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 + * + * 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; + } +} diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 76f9270be..442be135c 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -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); diff --git a/src/Composer/DependencyResolver/RuleWatchGraph.php b/src/Composer/DependencyResolver/RuleWatchGraph.php index 31a22414d..e7fae0c61 100644 --- a/src/Composer/DependencyResolver/RuleWatchGraph.php +++ b/src/Composer/DependencyResolver/RuleWatchGraph.php @@ -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(); diff --git a/src/Composer/DependencyResolver/RuleWatchNode.php b/src/Composer/DependencyResolver/RuleWatchNode.php index eeaa54162..926c144b4 100644 --- a/src/Composer/DependencyResolver/RuleWatchNode.php +++ b/src/Composer/DependencyResolver/RuleWatchNode.php @@ -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; }