Move handling of watch graph to separate classes
parent
71ee5c8f4c
commit
731a451dfe
|
@ -0,0 +1,134 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class RuleWatchGraph
|
||||||
|
{
|
||||||
|
protected $watches = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alters watch chains for a rule.
|
||||||
|
*
|
||||||
|
* Next1/2 always points to the next rule that is watching the same package.
|
||||||
|
* The watches array contains rules to start from for each package
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function insert(RuleWatchNode $node)
|
||||||
|
{
|
||||||
|
// skip simple assertions of the form (A) or (-A)
|
||||||
|
if ($node->getRule()->isAssertion()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->watches[$node->watch1])) {
|
||||||
|
$this->watches[$node->watch1] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$node->next1 = $this->watches[$node->watch1];
|
||||||
|
$this->watches[$node->watch1] = $node;
|
||||||
|
|
||||||
|
if (!isset($this->watches[$node->watch2])) {
|
||||||
|
$this->watches[$node->watch2] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$node->next2 = $this->watches[$node->watch2];
|
||||||
|
$this->watches[$node->watch2] = $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contains($literalId)
|
||||||
|
{
|
||||||
|
return isset($this->watches[$literalId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function walkLiteral($literalId, $level, $skipCallback, $conflictCallback, $decideCallback)
|
||||||
|
{
|
||||||
|
if (!isset($this->watches[$literalId])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$prevNode = null;
|
||||||
|
for ($node = $this->watches[$literalId]; $node !== null; $prevNode = $node, $node = $nextNode) {
|
||||||
|
$nextNode = $node->getNext($literalId);
|
||||||
|
|
||||||
|
if ($node->getRule()->isDisabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$otherWatch = $node->getOtherWatch($literalId);
|
||||||
|
|
||||||
|
if (call_user_func($skipCallback, $otherWatch)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ruleLiterals = $node->getRule()->getLiterals();
|
||||||
|
|
||||||
|
if (sizeof($ruleLiterals) > 2) {
|
||||||
|
foreach ($ruleLiterals as $ruleLiteral) {
|
||||||
|
if ($otherWatch !== $ruleLiteral->getId() &&
|
||||||
|
!call_user_func($conflictCallback, $ruleLiteral->getId())) {
|
||||||
|
|
||||||
|
$node = $this->moveWatch($literalId, $ruleLiteral->getId(), $prevNode, $node, $nextNode);
|
||||||
|
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// yay, we found a unit clause! try setting it to true
|
||||||
|
if (call_user_func($conflictCallback, $otherWatch)) {
|
||||||
|
return $node->getRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
call_user_func($decideCallback, $otherWatch, $level, $node->getRule());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function moveWatch($fromLiteral, $toLiteral, $prevNode, $node, $nextNode) {
|
||||||
|
if ($fromLiteral == $node->watch1) {
|
||||||
|
$node->watch1 = $toLiteral;
|
||||||
|
$node->next1 = (isset($this->watches[$toLiteral])) ? $this->watches[$toLiteral] : null;
|
||||||
|
} else {
|
||||||
|
$node->watch2 = $toLiteral;
|
||||||
|
$node->next2 = (isset($this->watches[$toLiteral])) ? $this->watches[$toLiteral] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($prevNode) {
|
||||||
|
if ($prevNode->next1 === $node) {
|
||||||
|
$prevNode->next1 = $nextNode;
|
||||||
|
} else {
|
||||||
|
$prevNode->next2 = $nextNode;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->watches[$fromLiteral] = $nextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->watches[$toLiteral] = $node;
|
||||||
|
|
||||||
|
if ($prevNode) {
|
||||||
|
return $prevNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmpNode = new RuleWatchNode(new Rule(array(), null, null));
|
||||||
|
$tmpNode->watch1 = $fromLiteral;
|
||||||
|
$tmpNode->next1 = $nextNode;
|
||||||
|
$tmpNode->watch2 = $fromLiteral;
|
||||||
|
$tmpNode->next2 = $nextNode;
|
||||||
|
|
||||||
|
return $tmpNode;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class RuleWatchNode
|
||||||
|
{
|
||||||
|
protected $rule;
|
||||||
|
|
||||||
|
public $watch1;
|
||||||
|
public $watch2;
|
||||||
|
|
||||||
|
public $next1;
|
||||||
|
public $next2;
|
||||||
|
|
||||||
|
public function __construct($rule)
|
||||||
|
{
|
||||||
|
$this->rule = $rule;
|
||||||
|
|
||||||
|
$literals = $rule->getLiterals();
|
||||||
|
|
||||||
|
$this->watch1 = (count($literals) > 0) ? $literals[0]->getId() : 0;
|
||||||
|
$this->watch2 = (count($literals) > 1) ? $literals[1]->getId() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put watch2 on rule's literal with highest level
|
||||||
|
*/
|
||||||
|
public function watch2OnHighest($decisionMap)
|
||||||
|
{
|
||||||
|
$literals = $this->rule->getLiterals();
|
||||||
|
|
||||||
|
// if there are only 2 elements, both are being watched anyway
|
||||||
|
if ($literals < 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$watchLevel = 0;
|
||||||
|
|
||||||
|
foreach ($literals as $literal) {
|
||||||
|
$level = abs($decisionMap[$literal->getPackageId()]);
|
||||||
|
|
||||||
|
if ($level > $watchLevel) {
|
||||||
|
$this->rule->watch2 = $literal->getId();
|
||||||
|
$watchLevel = $level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRule()
|
||||||
|
{
|
||||||
|
return $this->rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNext($literalId)
|
||||||
|
{
|
||||||
|
if ($this->watch1 == $literalId) {
|
||||||
|
return $this->next1;
|
||||||
|
} else {
|
||||||
|
return $this->next2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOtherWatch($literalId)
|
||||||
|
{
|
||||||
|
if ($this->watch1 == $literalId) {
|
||||||
|
return $this->watch2;
|
||||||
|
} else {
|
||||||
|
return $this->watch1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ class Solver
|
||||||
|
|
||||||
protected $addedMap = array();
|
protected $addedMap = array();
|
||||||
protected $updateMap = array();
|
protected $updateMap = array();
|
||||||
protected $watches = array();
|
protected $watchGraph;
|
||||||
protected $decisionMap;
|
protected $decisionMap;
|
||||||
protected $installedMap;
|
protected $installedMap;
|
||||||
|
|
||||||
|
@ -51,59 +51,6 @@ class Solver
|
||||||
$this->ruleSetGenerator = new RuleSetGenerator($policy, $pool);
|
$this->ruleSetGenerator = new RuleSetGenerator($policy, $pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Alters watch chains for a rule.
|
|
||||||
*
|
|
||||||
* Next1/2 always points to the next rule that is watching the same package.
|
|
||||||
* The watches array contains rules to start from for each package
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private function addWatchesToRule(Rule $rule)
|
|
||||||
{
|
|
||||||
// skip simple assertions of the form (A) or (-A)
|
|
||||||
if ($rule->isAssertion()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($this->watches[$rule->watch1])) {
|
|
||||||
$this->watches[$rule->watch1] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rule->next1 = $this->watches[$rule->watch1];
|
|
||||||
$this->watches[$rule->watch1] = $rule;
|
|
||||||
|
|
||||||
if (!isset($this->watches[$rule->watch2])) {
|
|
||||||
$this->watches[$rule->watch2] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rule->next2 = $this->watches[$rule->watch2];
|
|
||||||
$this->watches[$rule->watch2] = $rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put watch2 on rule's literal with highest level
|
|
||||||
*/
|
|
||||||
private function watch2OnHighest(Rule $rule)
|
|
||||||
{
|
|
||||||
$literals = $rule->getLiterals();
|
|
||||||
|
|
||||||
// if there are only 2 elements, both are being watched anyway
|
|
||||||
if ($literals < 3) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$watchLevel = 0;
|
|
||||||
|
|
||||||
foreach ($literals as $literal) {
|
|
||||||
$level = abs($this->decisionMap[$literal->getPackageId()]);
|
|
||||||
|
|
||||||
if ($level > $watchLevel) {
|
|
||||||
$rule->watch2 = $literal->getId();
|
|
||||||
$watchLevel = $level;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function findDecisionRule(PackageInterface $package)
|
private function findDecisionRule(PackageInterface $package)
|
||||||
{
|
{
|
||||||
foreach ($this->decisionQueue as $i => $literal) {
|
foreach ($this->decisionQueue as $i => $literal) {
|
||||||
|
@ -265,9 +212,10 @@ class Solver
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap);
|
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap);
|
||||||
|
$this->watchGraph = new RuleWatchGraph;
|
||||||
|
|
||||||
foreach ($this->rules as $rule) {
|
foreach ($this->rules as $rule) {
|
||||||
$this->addWatchesToRule($rule);
|
$this->watchGraph->insert(new RuleWatchNode($rule));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* make decisions based on job/update assertions */
|
/* make decisions based on job/update assertions */
|
||||||
|
@ -313,6 +261,13 @@ class Solver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function decide($literal, $level, $why)
|
||||||
|
{
|
||||||
|
$this->addDecisionId($literal, $level);
|
||||||
|
$this->decisionQueue[] = $this->literalFromId($literal);
|
||||||
|
$this->decisionQueueWhy[] = $why;
|
||||||
|
}
|
||||||
|
|
||||||
protected function decisionsContain(Literal $l)
|
protected function decisionsContain(Literal $l)
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
|
@ -321,7 +276,7 @@ class Solver
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function decisionsContainId($literalId)
|
public function decisionsContainId($literalId)
|
||||||
{
|
{
|
||||||
$packageId = abs($literalId);
|
$packageId = abs($literalId);
|
||||||
return (
|
return (
|
||||||
|
@ -344,7 +299,7 @@ class Solver
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function decisionsConflictId($literalId)
|
public function decisionsConflictId($literalId)
|
||||||
{
|
{
|
||||||
$packageId = abs($literalId);
|
$packageId = abs($literalId);
|
||||||
return (
|
return (
|
||||||
|
@ -390,68 +345,16 @@ class Solver
|
||||||
|
|
||||||
$this->propagateIndex++;
|
$this->propagateIndex++;
|
||||||
|
|
||||||
// /* foreach rule where 'pkg' is now FALSE */
|
$conflict = $this->watchGraph->walkLiteral(
|
||||||
//for (rp = watches + pkg; *rp; rp = next_rp)
|
$literal->getId(),
|
||||||
if (!isset($this->watches[$literal->getId()])) {
|
$level,
|
||||||
continue;
|
array($this, 'decisionsContainId'),
|
||||||
}
|
array($this, 'decisionsConflictId'),
|
||||||
|
array($this, 'decide')
|
||||||
|
);
|
||||||
|
|
||||||
$prevRule = null;
|
if ($conflict) {
|
||||||
for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) {
|
return $conflict;
|
||||||
$nextRule = $rule->getNext($literal);
|
|
||||||
|
|
||||||
if ($rule->isDisabled()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$otherWatch = $rule->getOtherWatch($literal);
|
|
||||||
|
|
||||||
if ($this->decisionsContainId($otherWatch)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ruleLiterals = $rule->getLiterals();
|
|
||||||
|
|
||||||
if (sizeof($ruleLiterals) > 2) {
|
|
||||||
foreach ($ruleLiterals as $ruleLiteral) {
|
|
||||||
if ($otherWatch !== $ruleLiteral->getId() &&
|
|
||||||
!$this->decisionsConflict($ruleLiteral)) {
|
|
||||||
|
|
||||||
if ($literal->getId() === $rule->watch1) {
|
|
||||||
$rule->watch1 = $ruleLiteral->getId();
|
|
||||||
$rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
|
|
||||||
} else {
|
|
||||||
$rule->watch2 = $ruleLiteral->getId();
|
|
||||||
$rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($prevRule) {
|
|
||||||
if ($prevRule->next1 == $rule) {
|
|
||||||
$prevRule->next1 = $nextRule;
|
|
||||||
} else {
|
|
||||||
$prevRule->next2 = $nextRule;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->watches[$literal->getId()] = $nextRule;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->watches[$ruleLiteral->getId()] = $rule;
|
|
||||||
|
|
||||||
$rule = $prevRule;
|
|
||||||
continue 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// yay, we found a unit clause! try setting it to true
|
|
||||||
if ($this->decisionsConflictId($otherWatch)) {
|
|
||||||
return $rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->addDecisionId($otherWatch, $level);
|
|
||||||
|
|
||||||
$this->decisionQueue[] = $this->literalFromId($otherWatch);
|
|
||||||
$this->decisionQueueWhy[] = $rule;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,8 +453,9 @@ class Solver
|
||||||
|
|
||||||
$this->learnedWhy[$newRule->getId()] = $why;
|
$this->learnedWhy[$newRule->getId()] = $why;
|
||||||
|
|
||||||
$this->watch2OnHighest($newRule);
|
$ruleNode = new RuleWatchNode($newRule);
|
||||||
$this->addWatchesToRule($newRule);
|
$ruleNode->watch2OnHighest($this->decisionMap);
|
||||||
|
$this->watchGraph->insert($ruleNode);
|
||||||
|
|
||||||
$this->addDecision($learnLiteral, $level);
|
$this->addDecision($learnLiteral, $level);
|
||||||
$this->decisionQueue[] = $learnLiteral;
|
$this->decisionQueue[] = $learnLiteral;
|
||||||
|
|
Loading…
Reference in New Issue