From 0b8fb68e9301a719b4ea664a14d1a6c7bba2fb5f Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 19 Feb 2012 15:19:34 +0100 Subject: [PATCH 1/4] Add a debug print method for the entire watch tree to the solver --- src/Composer/DependencyResolver/Solver.php | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 4359f3102..16295fd5d 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -2033,4 +2033,42 @@ class Solver } echo "\n"; } + + private function printWatches() + { + echo "\nWatches:\n"; + foreach ($this->watches as $literalId => $watch) { + echo ' '.$this->literalFromId($literalId)."\n"; + $queue = array(array(' ', $watch)); + + while (!empty($queue)) { + list($indent, $watch) = array_pop($queue); + + echo $indent.$watch; + + if ($watch) { + echo ' [id='.$watch->getId().',watch1='.$this->literalFromId($watch->watch1).',watch2='.$this->literalFromId($watch->watch2)."]"; + } + + echo "\n"; + + if ($watch && ($watch->next1 == $watch || $watch->next2 == $watch)) { + if ($watch->next1 == $watch) { + echo $indent." 1 *RECURSION*"; + } + if ($watch->next2 == $watch) { + echo $indent." 2 *RECURSION*"; + } + } + else if ($watch && ($watch->next1 || $watch->next2)) { + $indent = str_replace(array('1', '2'), ' ', $indent); + + array_push($queue, array($indent.' 2 ', $watch->next2)); + array_push($queue, array($indent.' 1 ', $watch->next1)); + } + } + + echo "\n"; + } + } } From 8484199677ecdc5aa54f073aa6938fa723e8d08c Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 19 Feb 2012 15:20:13 +0100 Subject: [PATCH 2/4] Display undecided literals as undecided with a ?, when printing the decision map --- src/Composer/DependencyResolver/Solver.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 16295fd5d..833548246 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -2018,8 +2018,10 @@ class Solver } if ($level > 0) { echo ' +' . $this->pool->packageById($packageId)."\n"; - } else { + } elseif ($level < 0) { echo ' -' . $this->pool->packageById($packageId)."\n"; + } else { + echo ' ?' . $this->pool->packageById($packageId)."\n"; } } echo "\n"; From 1ee5d994050f126dbd0ea05a7243cf1491e3d47f Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 19 Feb 2012 15:35:13 +0100 Subject: [PATCH 3/4] When changing watched literals of a rule, update the parent's next pointer The previous rule was not previously updated to point to the next rule when removing a middle rule from the watch tree for a literal. This resulted in jumping from one literal's watch tree to another's, which could then jump back to the original and cause infinite loop in a case like #265. Fixes #265 --- src/Composer/DependencyResolver/Solver.php | 19 ++++++++--- .../Test/DependencyResolver/SolverTest.php | 34 +++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 833548246..6d1293e1b 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -1222,7 +1222,8 @@ class Solver continue; } - for ($rule = $this->watches[$literal->getId()]; $rule !== null; $rule = $nextRule) { + $prevRule = null; + for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) { $nextRule = $rule->getNext($literal); if ($rule->isDisabled()) { @@ -1242,13 +1243,23 @@ class Solver 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 ; + $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 ; + $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null; + } + + if ($prevRule) { + if ($prevRule->watch1 === $literal->getId()) { + $prevRule->next1 = $nextRule; + } else { + $prevRule->next2 = $nextRule; + } + } + else { + $this->watches[$literal->getId()] = $nextRule; } $this->watches[$ruleLiteral->getId()] = $rule; diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 18cb9f4a1..e74f11b06 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -485,6 +485,40 @@ class SolverTest extends TestCase )); } + public function testIssue265() + { + $this->repo->addPackage($packageA1 = $this->getPackage('A', '2.0.999999-dev')); + $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.1-dev')); + $this->repo->addPackage($packageA3 = $this->getPackage('A', '2.2-dev')); + $this->repo->addPackage($packageB1 = $this->getPackage('B', '2.0.10')); + $this->repo->addPackage($packageB2 = $this->getPackage('B', '2.0.9')); + $this->repo->addPackage($packageC = $this->getPackage('C', '2.0-dev')); + $this->repo->addPackage($packageD = $this->getPackage('D', '2.0.9')); + + $packageC->setRequires(array( + new Link('C', 'A', new VersionConstraint('>=', '2.0'), 'requires'), + new Link('C', 'D', new VersionConstraint('>=', '2.0'), 'requires'), + )); + + $packageD->setRequires(array( + new Link('D', 'A', new VersionConstraint('>=', '2.1'), 'requires'), + new Link('D', 'B', new VersionConstraint('>=', '2.0-dev'), 'requires'), + )); + + $packageB1->setRequires(array(new Link('B', 'A', new VersionConstraint('==', '2.1.0.0-dev'), 'requires'))); + $packageB2->setRequires(array(new Link('B', 'A', new VersionConstraint('==', '2.1.0.0-dev'), 'requires'))); + + $packageB2->setReplaces(array(new Link('B', 'D', new VersionConstraint('==', '2.0.9.0'), 'replaces'))); + + $this->reposComplete(); + + $this->request->install('C', new VersionConstraint('==', '2.0.0.0-dev')); + + $this->setExpectedException('Composer\DependencyResolver\SolverProblemsException'); + + $this->solver->solve($this->request); + } + public function testConflictResultEmpty() { $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); From 2a92b904d2a5628fa52f27e1f71c6f7ad5aeed0d Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 19 Feb 2012 15:42:23 +0100 Subject: [PATCH 4/4] Correct placing of braces --- src/Composer/DependencyResolver/Solver.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 6d1293e1b..4ec591c29 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -1257,8 +1257,7 @@ class Solver } else { $prevRule->next2 = $nextRule; } - } - else { + } else { $this->watches[$literal->getId()] = $nextRule; } @@ -2072,8 +2071,7 @@ class Solver if ($watch->next2 == $watch) { echo $indent." 2 *RECURSION*"; } - } - else if ($watch && ($watch->next1 || $watch->next2)) { + } elseif ($watch && ($watch->next1 || $watch->next2)) { $indent = str_replace(array('1', '2'), ' ', $indent); array_push($queue, array($indent.' 2 ', $watch->next2));