Bläddra i källkod

Merge remote-tracking branch 'naderman/solver-refactor'

Jordi Boggiano 13 år sedan
förälder
incheckning
4ea9b33a6c

+ 83 - 0
src/Composer/DependencyResolver/DebugSolver.php

@@ -0,0 +1,83 @@
+<?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 DebugSolver extends Solver
+{
+    protected function printDecisionMap()
+    {
+        echo "\nDecisionMap: \n";
+        foreach ($this->decisionMap as $packageId => $level) {
+            if ($packageId === 0) {
+                continue;
+            }
+            if ($level > 0) {
+                echo '    +' . $this->pool->packageById($packageId)."\n";
+            } elseif ($level < 0) {
+                echo '    -' . $this->pool->packageById($packageId)."\n";
+            } else {
+                echo '    ?' . $this->pool->packageById($packageId)."\n";
+            }
+        }
+        echo "\n";
+    }
+
+    protected function printDecisionQueue()
+    {
+        echo "DecisionQueue: \n";
+        foreach ($this->decisionQueue as $i => $literal) {
+            echo '    ' . $this->pool->literalToString($literal) . ' ' . $this->decisionQueueWhy[$i]." level ".$this->decisionMap[abs($literal)]."\n";
+        }
+        echo "\n";
+    }
+
+    protected 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*";
+                    }
+                } elseif ($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";
+        }
+    }
+}

+ 20 - 24
src/Composer/DependencyResolver/DefaultPolicy.php

@@ -30,7 +30,7 @@ class DefaultPolicy implements PolicyInterface
         return $constraint->matchSpecific($version);
     }
 
-    public function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package)
+    public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package)
     {
         $packages = array();
 
@@ -43,12 +43,6 @@ class DefaultPolicy implements PolicyInterface
         return $packages;
     }
 
-    public function installable(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package)
-    {
-        // todo: package blacklist?
-        return true;
-    }
-
     public function getPriority(Pool $pool, PackageInterface $package)
     {
         return $pool->getPriority($package->getRepository());
@@ -56,44 +50,44 @@ class DefaultPolicy implements PolicyInterface
 
     public function selectPreferedPackages(Pool $pool, array $installedMap, array $literals)
     {
-        $packages = $this->groupLiteralsByNamePreferInstalled($installedMap, $literals);
+        $packages = $this->groupLiteralsByNamePreferInstalled($pool,$installedMap, $literals);
 
         foreach ($packages as &$literals) {
             $policy = $this;
             usort($literals, function ($a, $b) use ($policy, $pool, $installedMap) {
-                return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $a->getPackage(), $b->getPackage(), true);
+                return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), true);
             });
         }
 
         foreach ($packages as &$literals) {
-            $literals = $this->pruneToBestVersion($literals);
+            $literals = $this->pruneToBestVersion($pool, $literals);
 
             $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);
 
-            $literals = $this->pruneRemoteAliases($literals);
+            $literals = $this->pruneRemoteAliases($pool, $literals);
         }
 
         $selected = call_user_func_array('array_merge', $packages);
 
         // now sort the result across all packages to respect replaces across packages
         usort($selected, function ($a, $b) use ($policy, $pool, $installedMap) {
-            return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $a->getPackage(), $b->getPackage());
+            return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b));
         });
 
         return $selected;
     }
 
-    protected function groupLiteralsByNamePreferInstalled(array $installedMap, $literals)
+    protected function groupLiteralsByNamePreferInstalled(Pool $pool, array $installedMap, $literals)
     {
         $packages = array();
         foreach ($literals as $literal) {
-            $packageName = $literal->getPackage()->getName();
+            $packageName = $pool->literalToPackage($literal)->getName();
 
             if (!isset($packages[$packageName])) {
                 $packages[$packageName] = array();
             }
 
-            if (isset($installedMap[$literal->getPackageId()])) {
+            if (isset($installedMap[abs($literal)])) {
                 array_unshift($packages[$packageName], $literal);
             } else {
                 $packages[$packageName][] = $literal;
@@ -171,19 +165,21 @@ class DefaultPolicy implements PolicyInterface
         return false;
     }
 
-    protected function pruneToBestVersion($literals)
+    protected function pruneToBestVersion(Pool $pool, $literals)
     {
         $bestLiterals = array($literals[0]);
-        $bestPackage = $literals[0]->getPackage();
+        $bestPackage = $pool->literalToPackage($literals[0]);
         foreach ($literals as $i => $literal) {
             if (0 === $i) {
                 continue;
             }
 
-            if ($this->versionCompare($literal->getPackage(), $bestPackage, '>')) {
-                $bestPackage = $literal->getPackage();
+            $package = $pool->literalToPackage($literal);
+
+            if ($this->versionCompare($package, $bestPackage, '>')) {
+                $bestPackage = $package;
                 $bestLiterals = array($literal);
-            } else if ($this->versionCompare($literal->getPackage(), $bestPackage, '==')) {
+            } else if ($this->versionCompare($package, $bestPackage, '==')) {
                 $bestLiterals[] = $literal;
             }
         }
@@ -221,7 +217,7 @@ class DefaultPolicy implements PolicyInterface
         $priority = null;
 
         foreach ($literals as $literal) {
-            $package = $literal->getPackage();
+            $package = $pool->literalToPackage($literal);
 
             if (isset($installedMap[$package->getId()])) {
                 $selected[] = $literal;
@@ -247,12 +243,12 @@ class DefaultPolicy implements PolicyInterface
      *
      * If no package is a local alias, nothing happens
      */
-    protected function pruneRemoteAliases(array $literals)
+    protected function pruneRemoteAliases(Pool $pool, array $literals)
     {
         $hasLocalAlias = false;
 
         foreach ($literals as $literal) {
-            $package = $literal->getPackage();
+            $package = $pool->literalToPackage($literal);
 
             if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                 $hasLocalAlias = true;
@@ -266,7 +262,7 @@ class DefaultPolicy implements PolicyInterface
 
         $selected = array();
         foreach ($literals as $literal) {
-            $package = $literal->getPackage();
+            $package = $pool->literalToPackage($literal);
 
             if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                 $selected[] = $literal;

+ 0 - 67
src/Composer/DependencyResolver/Literal.php

@@ -1,67 +0,0 @@
-<?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;
-
-/**
- * @author Nils Adermann <naderman@naderman.de>
- */
-class Literal
-{
-    protected $package;
-    protected $wanted;
-    protected $id;
-
-    public function __construct(PackageInterface $package, $wanted)
-    {
-        $this->package = $package;
-        $this->wanted = $wanted;
-        $this->id = ($this->wanted ? '' : '-') . $this->package->getId();
-    }
-
-    public function isWanted()
-    {
-        return $this->wanted;
-    }
-
-    public function getPackage()
-    {
-        return $this->package;
-    }
-
-    public function getPackageId()
-    {
-        return $this->package->getId();
-    }
-
-    public function getId()
-    {
-        return $this->id;
-    }
-
-    public function __toString()
-    {
-        return ($this->wanted ? '+' : '-') . $this->getPackage();
-    }
-
-    public function inverted()
-    {
-        return new Literal($this->getPackage(), !$this->isWanted());
-    }
-
-    public function equals(Literal $b)
-    {
-        return $this->id === $b->id;
-    }
-}

+ 1 - 2
src/Composer/DependencyResolver/PolicyInterface.php

@@ -21,7 +21,6 @@ use Composer\Package\PackageInterface;
 interface PolicyInterface
 {
     function versionCompare(PackageInterface $a, PackageInterface $b, $operator);
-    function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package);
-    function installable(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package);
+    function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package);
     function selectPreferedPackages(Pool $pool, array $installedMap, array $literals);
 }

+ 11 - 0
src/Composer/DependencyResolver/Pool.php

@@ -151,4 +151,15 @@ class Pool
 
         return $result;
     }
+
+    public function literalToPackage($literal)
+    {
+        $packageId = abs($literal);
+        return $this->packageById($packageId);
+    }
+
+    public function literalToString($literal)
+    {
+        return ($literal > 0 ? '+' : '-') . $this->literalToPackage($literal);
+    }
 }

+ 1 - 15
src/Composer/DependencyResolver/Problem.php

@@ -25,20 +25,6 @@ class Problem
      */
     protected $reasons;
 
-    /**
-     * Add a job as a reason
-     *
-     * @param   array   $job    A job descriptor which is a reason for this problem
-     * @param   Rule    $rule   An optional rule associated with the job
-     */
-    public function addJobRule($job, Rule $rule = null)
-    {
-        $this->addReason(serialize($job), array(
-            'rule' => $rule,
-            'job' => $job,
-        ));
-    }
-
     /**
      * Add a rule as a reason
      *
@@ -48,7 +34,7 @@ class Problem
     {
         $this->addReason($rule->getId(), array(
             'rule' => $rule,
-            'job' => null,
+            'job' => $rule->getJob(),
         ));
     }
 

+ 22 - 73
src/Composer/DependencyResolver/Rule.php

@@ -20,8 +20,6 @@ class Rule
     const RULE_INTERNAL_ALLOW_UPDATE = 1;
     const RULE_JOB_INSTALL = 2;
     const RULE_JOB_REMOVE = 3;
-    const RULE_JOB_LOCK = 4;
-    const RULE_NOT_INSTALLABLE = 5;
     const RULE_PACKAGE_CONFLICT = 6;
     const RULE_PACKAGE_REQUIRES = 7;
     const RULE_PACKAGE_OBSOLETES = 8;
@@ -31,40 +29,35 @@ class Rule
     const RULE_LEARNED = 12;
     const RULE_PACKAGE_ALIAS = 13;
 
+    protected $pool;
+
     protected $disabled;
     protected $literals;
     protected $type;
     protected $id;
-    protected $weak;
-
-    public $watch1;
-    public $watch2;
 
-    public $next1;
-    public $next2;
+    protected $job;
 
-    public $ruleHash;
+    protected $ruleHash;
 
-    public function __construct(array $literals, $reason, $reasonData)
+    public function __construct(Pool $pool, array $literals, $reason, $reasonData, $job = null)
     {
+        $this->pool = $pool;
+
         // sort all packages ascending by id
-        usort($literals, array($this, 'compareLiteralsById'));
+        sort($literals);
 
         $this->literals = $literals;
         $this->reason = $reason;
         $this->reasonData = $reasonData;
 
         $this->disabled = false;
-        $this->weak = false;
 
-        $this->watch1 = (count($this->literals) > 0) ? $literals[0]->getId() : 0;
-        $this->watch2 = (count($this->literals) > 1) ? $literals[1]->getId() : 0;
+        $this->job = $job;
 
         $this->type = -1;
 
-        $this->ruleHash = substr(md5(implode(',', array_map(function ($l) {
-            return $l->getId();
-        }, $this->literals))), 0, 5);
+        $this->ruleHash = substr(md5(implode(',', $this->literals)), 0, 5);
     }
 
     public function getHash()
@@ -82,6 +75,11 @@ class Rule
         return $this->id;
     }
 
+    public function getJob()
+    {
+        return $this->job;
+    }
+
     /**
      * Checks if this rule is equal to another one
      *
@@ -101,7 +99,7 @@ class Rule
         }
 
         for ($i = 0, $n = count($this->literals); $i < $n; $i++) {
-            if ($this->literals[$i]->getId() !== $rule->literals[$i]->getId()) {
+            if ($this->literals[$i] !== $rule->literals[$i]) {
                 return false;
             }
         }
@@ -139,16 +137,6 @@ class Rule
         return !$this->disabled;
     }
 
-    public function isWeak()
-    {
-        return $this->weak;
-    }
-
-    public function setWeak($weak)
-    {
-        $this->weak = $weak;
-    }
-
     public function getLiterals()
     {
         return $this->literals;
@@ -159,24 +147,6 @@ class Rule
         return 1 === count($this->literals);
     }
 
-    public function getNext(Literal $literal)
-    {
-        if ($this->watch1 == $literal->getId()) {
-            return $this->next1;
-        } else {
-            return $this->next2;
-        }
-    }
-
-    public function getOtherWatch(Literal $literal)
-    {
-        if ($this->watch1 == $literal->getId()) {
-            return $this->watch2;
-        } else {
-            return $this->watch1;
-        }
-    }
-
     public function toHumanReadableString()
     {
         $ruleText = '';
@@ -184,7 +154,7 @@ class Rule
             if ($i != 0) {
                 $ruleText .= '|';
             }
-            $ruleText .= $literal;
+            $ruleText .= $this->pool->literalToString($literal);
         }
 
         switch ($this->reason) {
@@ -197,25 +167,19 @@ class Rule
             case self::RULE_JOB_REMOVE:
                 return "Remove command rule ($ruleText)";
 
-            case self::RULE_JOB_LOCK:
-                return "Lock command rule ($ruleText)";
-
-            case self::RULE_NOT_INSTALLABLE:
-                return $ruleText;
-
             case self::RULE_PACKAGE_CONFLICT:
-                $package1 = $this->literals[0]->getPackage();
-                $package2 = $this->literals[1]->getPackage();
+                $package1 = $this->pool->literalToPackage($this->literals[0]);
+                $package2 = $this->pool->literalToPackage($this->literals[1]);
                 return 'Package "'.$package1.'" conflicts with "'.$package2.'"';
 
             case self::RULE_PACKAGE_REQUIRES:
                 $literals = $this->literals;
                 $sourceLiteral = array_shift($literals);
-                $sourcePackage = $sourceLiteral->getPackage();
+                $sourcePackage = $this->pool->literalToPackage($sourceLiteral);
 
                 $requires = array();
                 foreach ($literals as $literal) {
-                    $requires[] = $literal->getPackage();
+                    $requires[] = $this->pool->literalToPackage($literal);
                 }
 
                 $text = 'Package "'.$sourcePackage.'" contains the rule '.$this->reasonData.'. ';
@@ -254,26 +218,11 @@ class Rule
             if ($i != 0) {
                 $result .= '|';
             }
-            $result .= $literal;
+            $result .= $this->pool->literalToString($literal);
         }
 
         $result .= ')';
 
         return $result;
     }
-
-    /**
-     * Comparison function for sorting literals by their id
-     *
-     * @param  Literal $a
-     * @param  Literal $b
-     * @return int        0 if the literals are equal, 1 if b is larger than a, -1 else
-     */
-    private function compareLiteralsById(Literal $a, Literal $b)
-    {
-        if ($a->getId() === $b->getId()) {
-            return 0;
-        }
-        return $a->getId() < $b->getId() ? -1 : 1;
-    }
 }

+ 289 - 0
src/Composer/DependencyResolver/RuleSetGenerator.php

@@ -0,0 +1,289 @@
+<?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\Repository\RepositoryInterface;
+use Composer\Package\PackageInterface;
+use Composer\Package\AliasPackage;
+use Composer\DependencyResolver\Operation;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class RuleSetGenerator
+{
+    protected $policy;
+    protected $pool;
+    protected $rules;
+    protected $jobs;
+    protected $installedMap;
+
+    public function __construct(PolicyInterface $policy, Pool $pool)
+    {
+        $this->policy = $policy;
+        $this->pool = $pool;
+    }
+
+    /**
+     * Creates a new rule for the requirements of a package
+     *
+     * This rule is of the form (-A|B|C), where B and C are the providers of
+     * one requirement of the package A.
+     *
+     * @param PackageInterface $package    The package with a requirement
+     * @param array            $providers  The providers of the requirement
+     * @param int              $reason     A RULE_* constant describing the
+     *                                     reason for generating this rule
+     * @param mixed            $reasonData Any data, e.g. the requirement name,
+     *                                     that goes with the reason
+     * @return Rule                        The generated rule or null if tautological
+     */
+    protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
+    {
+        $literals = array(-$package->getId());
+
+        foreach ($providers as $provider) {
+            // self fulfilling rule?
+            if ($provider === $package) {
+                return null;
+            }
+            $literals[] = $provider->getId();
+        }
+
+        return new Rule($this->pool, $literals, $reason, $reasonData);
+    }
+
+    /**
+     * Creates a rule to install at least one of a set of packages
+     *
+     * The rule is (A|B|C) with A, B and C different packages. If the given
+     * set of packages is empty an impossible rule is generated.
+     *
+     * @param array   $packages   The set of packages to choose from
+     * @param int     $reason     A RULE_* constant describing the reason for
+     *                            generating this rule
+     * @param array   $job        The job this rule was created from
+     * @return Rule               The generated rule
+     */
+    protected function createInstallOneOfRule(array $packages, $reason, $job)
+    {
+        $literals = array();
+        foreach ($packages as $package) {
+            $literals[] = $package->getId();
+        }
+
+        return new Rule($this->pool, $literals, $reason, $job['packageName'], $job);
+    }
+
+    /**
+     * Creates a rule to remove a package
+     *
+     * The rule for a package A is (-A).
+     *
+     * @param PackageInterface $package    The package to be removed
+     * @param int              $reason     A RULE_* constant describing the
+     *                                     reason for generating this rule
+     * @param array            $job        The job this rule was created from
+     * @return Rule                        The generated rule
+     */
+    protected function createRemoveRule(PackageInterface $package, $reason, $job)
+    {
+        return new Rule($this->pool, array(-$package->getId()), $reason, $job['packageName'], $job);
+    }
+
+    /**
+     * Creates a rule for two conflicting packages
+     *
+     * The rule for conflicting packages A and B is (-A|-B). A is called the issuer
+     * and B the provider.
+     *
+     * @param PackageInterface $issuer     The package declaring the conflict
+     * @param Package          $provider   The package causing the conflict
+     * @param int              $reason     A RULE_* constant describing the
+     *                                     reason for generating this rule
+     * @param mixed            $reasonData Any data, e.g. the package name, that
+     *                                     goes with the reason
+     * @return Rule                        The generated rule
+     */
+    protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
+    {
+        // ignore self conflict
+        if ($issuer === $provider) {
+            return null;
+        }
+
+        return new Rule($this->pool, array(-$issuer->getId(), -$provider->getId()), $reason, $reasonData);
+    }
+
+    /**
+     * Adds a rule unless it duplicates an existing one of any type
+     *
+     * To be able to directly pass in the result of one of the rule creation
+     * methods.
+     *
+     * @param int  $type    A TYPE_* constant defining the rule type
+     * @param Rule $newRule The rule about to be added
+     */
+    private function addRule($type, Rule $newRule = null) {
+        if ($this->rules->containsEqual($newRule)) {
+            return;
+        }
+
+        $this->rules->add($newRule, $type);
+    }
+
+    protected function addRulesForPackage(PackageInterface $package)
+    {
+        $workQueue = new \SplQueue;
+        $workQueue->enqueue($package);
+
+        while (!$workQueue->isEmpty()) {
+            $package = $workQueue->dequeue();
+            if (isset($this->addedMap[$package->getId()])) {
+                continue;
+            }
+
+            $this->addedMap[$package->getId()] = true;
+
+            foreach ($package->getRequires() as $link) {
+                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
+
+                $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, (string) $link));
+
+                foreach ($possibleRequires as $require) {
+                    $workQueue->enqueue($require);
+                }
+            }
+
+            foreach ($package->getConflicts() as $link) {
+                $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
+
+                foreach ($possibleConflicts as $conflict) {
+                    $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, (string) $link));
+                }
+            }
+
+            // check obsoletes and implicit obsoletes of a package
+            $isInstalled = (isset($this->installedMap[$package->getId()]));
+
+            foreach ($package->getReplaces() as $link) {
+                $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
+
+                foreach ($obsoleteProviders as $provider) {
+                    if ($provider === $package) {
+                        continue;
+                    }
+
+                    if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
+                        $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
+                        $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link));
+                    }
+                }
+            }
+
+            // check implicit obsoletes
+            // for installed packages we only need to check installed/installed problems,
+            // as the others are picked up when looking at the uninstalled package.
+            if (!$isInstalled) {
+                $obsoleteProviders = $this->pool->whatProvides($package->getName(), null);
+
+                foreach ($obsoleteProviders as $provider) {
+                    if ($provider === $package) {
+                        continue;
+                    }
+
+                    if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
+                        $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, (string) $package));
+                    } elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
+                        $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
+                        $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package));
+                    }
+                }
+            }
+        }
+    }
+
+    protected function obsoleteImpossibleForAlias($package, $provider)
+    {
+        $packageIsAlias = $package instanceof AliasPackage;
+        $providerIsAlias = $provider instanceof AliasPackage;
+
+        $impossible = (
+            ($packageIsAlias && $package->getAliasOf() === $provider) ||
+            ($providerIsAlias && $provider->getAliasOf() === $package) ||
+            ($packageIsAlias && $providerIsAlias && $provider->getAliasOf() === $package->getAliasOf())
+        );
+
+        return $impossible;
+    }
+
+    /**
+     * Adds all rules for all update packages of a given package
+     *
+     * @param PackageInterface $package  Rules for this package's updates are to
+     *                                   be added
+     * @param bool             $allowAll Whether downgrades are allowed
+     */
+    private function addRulesForUpdatePackages(PackageInterface $package)
+    {
+        $updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
+
+        foreach ($updates as $update) {
+            $this->addRulesForPackage($update);
+        }
+    }
+
+    protected function addRulesForJobs()
+    {
+        foreach ($this->jobs as $job) {
+            switch ($job['cmd']) {
+                case 'install':
+                    if ($job['packages']) {
+                        foreach ($job['packages'] as $package) {
+                            if (!isset($this->installedMap[$package->getId()])) {
+                                $this->addRulesForPackage($package);
+                            }
+                        }
+
+                        $rule = $this->createInstallOneOfRule($job['packages'], Rule::RULE_JOB_INSTALL, $job);
+                        $this->addRule(RuleSet::TYPE_JOB, $rule);
+                    }
+                    break;
+                case 'remove':
+                    // remove all packages with this name including uninstalled
+                    // ones to make sure none of them are picked as replacements
+                    foreach ($job['packages'] as $package) {
+                        $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE, $job);
+                        $this->addRule(RuleSet::TYPE_JOB, $rule);
+                    }
+                    break;
+            }
+        }
+    }
+
+    public function getRulesFor($jobs, $installedMap)
+    {
+        $this->jobs = $jobs;
+        $this->rules = new RuleSet;
+        $this->installedMap = $installedMap;
+
+        foreach ($this->installedMap as $package) {
+            $this->addRulesForPackage($package);
+            $this->addRulesForUpdatePackages($package);
+        }
+
+        $this->addRulesForJobs();
+
+        return $this->rules;
+    }
+}

+ 52 - 0
src/Composer/DependencyResolver/RuleWatchChain.php

@@ -0,0 +1,52 @@
+<?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;
+
+/**
+ * An extension of SplDoublyLinkedList with seek and removal of current element
+ *
+ * SplDoublyLinkedList only allows deleting a particular offset and has no
+ * method to set the internal iterator to a particular offset.
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class RuleWatchChain extends \SplDoublyLinkedList
+{
+    protected $offset = 0;
+
+    /**
+     * Moves the internal iterator to the specified offset
+     *
+     * @param int $offset The offset to seek to.
+     */
+    public function seek($offset)
+    {
+        $this->rewind();
+        for ($i = 0; $i < $offset; $i++, $this->next());
+    }
+
+    /**
+     * Removes the current element from the list
+     *
+     * As SplDoublyLinkedList only allows deleting a particular offset and
+     * incorrectly sets the internal iterator if you delete the current value
+     * this method sets the internal iterator back to the following element
+     * using the seek method.
+     */
+    public function remove()
+    {
+        $offset = $this->key();
+        $this->offsetUnset($offset);
+        $this->seek($offset);
+    }
+}

+ 149 - 0
src/Composer/DependencyResolver/RuleWatchGraph.php

@@ -0,0 +1,149 @@
+<?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;
+
+/**
+ * The RuleWatchGraph efficiently propagates decisions to other rules
+ *
+ * All rules generated for solving a SAT problem should be inserted into the
+ * graph. When a decision on a literal is made, the graph can be used to
+ * propagate the decision to all other rules involving the rule, leading to
+ * other trivial decisions resulting from unit clauses.
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class RuleWatchGraph
+{
+    protected $watchChains = array();
+
+    /**
+     * Inserts a rule node into the appropriate chains within the graph
+     *
+     * The node is prepended to the watch chains for each of the two literals it
+     * watches.
+     *
+     * Assertions are skipped because they only depend on a single package and
+     * have no alternative literal that could be true, so there is no need to
+     * watch chnages in any literals.
+     *
+     * @param RuleWatchNode $node The rule node to be inserted into the graph
+     */
+    public function insert(RuleWatchNode $node)
+    {
+        if ($node->getRule()->isAssertion()) {
+            return;
+        }
+
+        foreach (array($node->watch1, $node->watch2) as $literal) {
+            if (!isset($this->watchChains[$literal])) {
+                $this->watchChains[$literal] = new RuleWatchChain;
+            }
+
+            $this->watchChains[$literal]->unshift($node);
+        }
+    }
+
+    /**
+     * Propagates a decision on a literal to all rules watching the literal
+     *
+     * If a decision, e.g. +A has been made, then all rules containing -A, e.g.
+     * (-A|+B|+C) now need to satisfy at least one of the other literals, so
+     * that the rule as a whole becomes true, since with +A applied the rule
+     * is now (false|+B|+C) so essentialy (+B|+C).
+     *
+     * This means that all rules watching the literal -A need to be updated to
+     * watch 2 other literals which can still be satisfied instead. So literals
+     * that conflict with previously made decisions are not an option.
+     *
+     * Alternatively it can occur that a unit clause results: e.g. if in the
+     * above example the rule was (-A|+B), then A turning true means that
+     * B must now be decided true as well.
+     *
+     * @param int $decidedLiteral The literal which was decided (A in our example)
+     * @param int $level The level at which the decision took place and at which
+     *     all resulting decisions should be made.
+     * @param Callable $decisionsSatisfyCallback A callback which checks if a
+     *     literal has already been positively decided and the rule is thus
+     *     already true and can be skipped.
+     * @param Callable $conflictCallback A callback which checks if a literal
+     *     would conflict with previously made decisions on the same package
+     * @param Callable $decideCallback A callback which is responsible for
+     *     registering decided literals resulting from unit clauses
+     * @return Rule|null If a conflict is found the conflicting rule is returned
+     */
+    public function propagateLiteral($decidedLiteral, $level, $decisionsSatisfyCallback, $conflictCallback, $decideCallback)
+    {
+        // we invert the decided literal here, example:
+        // A was decided => (-A|B) now requires B to be true, so we look for
+        // rules which are fulfilled by -A, rather than A.
+        $literal = -$decidedLiteral;
+
+        if (!isset($this->watchChains[$literal])) {
+            return null;
+        }
+
+        $chain = $this->watchChains[$literal];
+
+        $chain->rewind();
+        while ($chain->valid()) {
+            $node = $chain->current();
+            $otherWatch = $node->getOtherWatch($literal);
+
+            if (!$node->getRule()->isDisabled() && !call_user_func($decisionsSatisfyCallback, $otherWatch)) {
+                $ruleLiterals = $node->getRule()->getLiterals();
+
+                $alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $conflictCallback) {
+                    return $literal !== $ruleLiteral &&
+                        $otherWatch !== $ruleLiteral &&
+                        !call_user_func($conflictCallback, $ruleLiteral);
+                });
+
+                if ($alternativeLiterals) {
+                    reset($alternativeLiterals);
+                    $this->moveWatch($literal, current($alternativeLiterals), $node);
+                    continue;
+                }
+
+                if (call_user_func($conflictCallback, $otherWatch)) {
+                    return $node->getRule();
+                }
+
+                call_user_func($decideCallback, $otherWatch, $level, $node->getRule());
+            }
+
+            $chain->next();
+        }
+
+        return null;
+    }
+
+    /**
+     * Moves a rule node from one watch chain to another
+     *
+     * The rule node's watched literals are updated accordingly.
+     *
+     * @param $fromLiteral A literal the node used to watch
+     * @param $toLiteral A literal the node should watch now
+     * @param $node The rule node to be moved
+     */
+    protected function moveWatch($fromLiteral, $toLiteral, $node)
+    {
+        if (!isset($this->watchChains[$toLiteral])) {
+            $this->watchChains[$toLiteral] = new RuleWatchChain;
+        }
+
+        $node->moveWatch($fromLiteral, $toLiteral);
+        $this->watchChains[$fromLiteral]->remove();
+        $this->watchChains[$toLiteral]->unshift($node);
+    }
+}

+ 112 - 0
src/Composer/DependencyResolver/RuleWatchNode.php

@@ -0,0 +1,112 @@
+<?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;
+
+/**
+ * Wrapper around a Rule which keeps track of the two literals it watches
+ *
+ * Used by RuleWatchGraph to store rules in two RuleWatchChains.
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class RuleWatchNode
+{
+    public $watch1;
+    public $watch2;
+
+    protected $rule;
+
+    /**
+     * Creates a new node watching the first and second literals of the rule.
+     *
+     * @param Rule $rule The rule to wrap
+     */
+    public function __construct($rule)
+    {
+        $this->rule = $rule;
+
+        $literals = $rule->getLiterals();
+
+        $this->watch1 = count($literals) > 0 ? $literals[0] : 0;
+        $this->watch2 = count($literals) > 1 ? $literals[1] : 0;
+    }
+
+    /**
+     * Places the second watch on the rule's literal, decided at the highest level
+     *
+     * Useful for learned rules where the literal for the highest rule is most
+     * likely to quickly lead to further decisions.
+     *
+     * @param SplFixedArray $decisionMap A package to decision lookup table
+     */
+    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[abs($literal)]);
+
+            if ($level > $watchLevel) {
+                $this->rule->watch2 = $literal;
+                $watchLevel = $level;
+            }
+        }
+    }
+
+    /**
+     * Returns the rule this node wraps
+     *
+     * @return Rule
+     */
+    public function getRule()
+    {
+        return $this->rule;
+    }
+
+    /**
+     * Given one watched literal, this method returns the other watched literal
+     *
+     * @param int The watched literal that should not be returned
+     * @return int A literal
+     */
+    public function getOtherWatch($literal)
+    {
+        if ($this->watch1 == $literal) {
+            return $this->watch2;
+        } else {
+            return $this->watch1;
+        }
+    }
+
+    /**
+     * Moves a watch from one literal to another
+     *
+     * @param int $from The previously watched literal
+     * @param int $to The literal to be watched now
+     */
+    public function moveWatch($from, $to)
+    {
+        if ($this->watch1 == $from) {
+            $this->watch1 = $to;
+        } else {
+            $this->watch2 = $to;
+        }
+    }
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 84 - 712
src/Composer/DependencyResolver/Solver.php


+ 167 - 0
src/Composer/DependencyResolver/Transaction.php

@@ -0,0 +1,167 @@
+<?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\AliasPackage;
+use Composer\DependencyResolver\Operation;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class Transaction
+{
+    protected $policy;
+    protected $pool;
+    protected $installedMap;
+    protected $decisionMap;
+    protected $decisionQueue;
+    protected $decisionQueueWhy;
+
+    public function __construct($policy, $pool, $installedMap, $decisionMap, array $decisionQueue, $decisionQueueWhy)
+    {
+        $this->policy = $policy;
+        $this->pool = $pool;
+        $this->installedMap = $installedMap;
+        $this->decisionMap = $decisionMap;
+        $this->decisionQueue = $decisionQueue;
+        $this->decisionQueueWhy = $decisionQueueWhy;
+    }
+
+    public function getOperations()
+    {
+        $transaction = array();
+        $installMeansUpdateMap = array();
+
+        foreach ($this->decisionQueue as $i => $literal) {
+            $package = $this->pool->literalToPackage($literal);
+
+            // !wanted & installed
+            if ($literal <= 0 && isset($this->installedMap[$package->getId()])) {
+                $updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
+
+                $literals = array($package->getId());
+
+                foreach ($updates as $update) {
+                    $literals[] = $update->getId();
+                }
+
+                foreach ($literals as $updateLiteral) {
+                    if ($updateLiteral !== $literal) {
+                        $installMeansUpdateMap[abs($updateLiteral)] = $package;
+                    }
+                }
+            }
+        }
+
+        foreach ($this->decisionQueue as $i => $literal) {
+            $package = $this->pool->literalToPackage($literal);
+
+            // wanted & installed || !wanted & !installed
+            if (($literal > 0) == (isset($this->installedMap[$package->getId()]))) {
+                continue;
+            }
+
+            if ($literal > 0) {
+                if ($package instanceof AliasPackage) {
+                    $transaction[] = new Operation\MarkAliasInstalledOperation(
+                        $package, $this->decisionQueueWhy[$i]
+                    );
+                    continue;
+                }
+
+                if (isset($installMeansUpdateMap[abs($literal)])) {
+
+                    $source = $installMeansUpdateMap[abs($literal)];
+
+                    $transaction[] = new Operation\UpdateOperation(
+                        $source, $package, $this->decisionQueueWhy[$i]
+                    );
+
+                    // avoid updates to one package from multiple origins
+                    unset($installMeansUpdateMap[abs($literal)]);
+                    $ignoreRemove[$source->getId()] = true;
+                } else {
+                    $transaction[] = new Operation\InstallOperation(
+                        $package, $this->decisionQueueWhy[$i]
+                    );
+                }
+            } else if (!isset($ignoreRemove[$package->getId()])) {
+                if ($package instanceof AliasPackage) {
+                    $transaction[] = new Operation\MarkAliasInstalledOperation(
+                        $package, $this->decisionQueueWhy[$i]
+                    );
+                } else {
+                    $transaction[] = new Operation\UninstallOperation(
+                        $package, $this->decisionQueueWhy[$i]
+                    );
+                }
+            }
+        }
+
+        $allDecidedMap = $this->decisionMap;
+        foreach ($this->decisionMap as $packageId => $decision) {
+            if ($decision != 0) {
+                $package = $this->pool->packageById($packageId);
+                if ($package instanceof AliasPackage) {
+                    $allDecidedMap[$package->getAliasOf()->getId()] = $decision;
+                }
+            }
+        }
+
+        foreach ($allDecidedMap as $packageId => $decision) {
+            if ($packageId === 0) {
+                continue;
+            }
+
+            if (0 == $decision && isset($this->installedMap[$packageId])) {
+                $package = $this->pool->packageById($packageId);
+
+                if ($package instanceof AliasPackage) {
+                    $transaction[] = new Operation\MarkAliasInstalledOperation(
+                        $package, null
+                    );
+                } else {
+                    $transaction[] = new Operation\UninstallOperation(
+                        $package, null
+                    );
+                }
+
+                $this->decisionMap[$packageId] = -1;
+            }
+        }
+
+        foreach ($allDecidedMap as $packageId => $decision) {
+            if ($packageId === 0) {
+                continue;
+            }
+
+            if (0 == $decision && isset($this->installedMap[$packageId])) {
+                $package = $this->pool->packageById($packageId);
+
+                if ($package instanceof AliasPackage) {
+                    $transaction[] = new Operation\MarkAliasInstalledOperation(
+                        $package, null
+                    );
+                } else {
+                    $transaction[] = new Operation\UninstallOperation(
+                        $package, null
+                    );
+                }
+
+                $this->decisionMap[$packageId] = -1;
+            }
+        }
+
+        return array_reverse($transaction);
+    }
+}

+ 13 - 14
tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php

@@ -16,7 +16,6 @@ use Composer\Repository\ArrayRepository;
 use Composer\Repository\RepositoryInterface;
 use Composer\DependencyResolver\DefaultPolicy;
 use Composer\DependencyResolver\Pool;
-use Composer\DependencyResolver\Literal;
 use Composer\Package\Link;
 use Composer\Package\AliasPackage;
 use Composer\Package\LinkConstraint\VersionConstraint;
@@ -44,8 +43,8 @@ class DefaultPolicyTest extends TestCase
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $this->pool->addRepository($this->repo);
 
-        $literals = array(new Literal($packageA, true));
-        $expected = array(new Literal($packageA, true));
+        $literals = array($packageA->getId());
+        $expected = array($packageA->getId());
 
         $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
 
@@ -58,8 +57,8 @@ class DefaultPolicyTest extends TestCase
         $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0'));
         $this->pool->addRepository($this->repo);
 
-        $literals = array(new Literal($packageA1, true), new Literal($packageA2, true));
-        $expected = array(new Literal($packageA2, true));
+        $literals = array($packageA1->getId(), $packageA2->getId());
+        $expected = array($packageA2->getId());
 
         $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
 
@@ -73,8 +72,8 @@ class DefaultPolicyTest extends TestCase
         $this->pool->addRepository($this->repoInstalled);
         $this->pool->addRepository($this->repo);
 
-        $literals = array(new Literal($packageA, true), new Literal($packageAInstalled, true));
-        $expected = array(new Literal($packageA, true));
+        $literals = array($packageA->getId(), $packageAInstalled->getId());
+        $expected = array($packageA->getId());
 
         $selected = $this->policy->selectPreferedPackages($this->pool, $this->mapFromRepo($this->repoInstalled), $literals);
 
@@ -92,8 +91,8 @@ class DefaultPolicyTest extends TestCase
         $this->pool->addRepository($this->repoImportant);
         $this->pool->addRepository($this->repo);
 
-        $literals = array(new Literal($packageA, true), new Literal($packageAImportant, true));
-        $expected = array(new Literal($packageAImportant, true));
+        $literals = array($packageA->getId(), $packageAImportant->getId());
+        $expected = array($packageAImportant->getId());
 
         $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
 
@@ -119,10 +118,10 @@ class DefaultPolicyTest extends TestCase
         $packages = $this->pool->whatProvides('a', new VersionConstraint('=', '2.1.9999999.9999999-dev'));
         $literals = array();
         foreach ($packages as $package) {
-            $literals[] = new Literal($package, true);
+            $literals[] = $package->getId();
         }
 
-        $expected = array(new Literal($packageAAliasImportant, true));
+        $expected = array($packageAAliasImportant->getId());
 
         $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
 
@@ -139,7 +138,7 @@ class DefaultPolicyTest extends TestCase
 
         $this->pool->addRepository($this->repo);
 
-        $literals = array(new Literal($packageA, true), new Literal($packageB, true));
+        $literals = array($packageA->getId(), $packageB->getId());
         $expected = $literals;
 
         $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
@@ -157,8 +156,8 @@ class DefaultPolicyTest extends TestCase
 
         $this->pool->addRepository($this->repo);
 
-        $literals = array(new Literal($packageA, true), new Literal($packageB, true));
-        $expected = array(new Literal($packageA, true), new Literal($packageB, true));
+        $literals = array($packageA->getId(), $packageB->getId());
+        $expected = $literals;
 
         $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
 

+ 0 - 63
tests/Composer/Test/DependencyResolver/LiteralTest.php

@@ -1,63 +0,0 @@
-<?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\Test\DependencyResolver;
-
-use Composer\DependencyResolver\Literal;
-use Composer\Test\TestCase;
-
-class LiteralTest extends TestCase
-{
-    protected $package;
-
-    public function setUp()
-    {
-        $this->package = $this->getPackage('foo', '1');
-        $this->package->setId(12);
-    }
-
-    public function testLiteralWanted()
-    {
-        $literal = new Literal($this->package, true);
-
-        $this->assertEquals(12, $literal->getId());
-        $this->assertEquals('+'.(string) $this->package, (string) $literal);
-    }
-
-    public function testLiteralUnwanted()
-    {
-        $literal = new Literal($this->package, false);
-
-        $this->assertEquals(-12, $literal->getId());
-        $this->assertEquals('-'.(string) $this->package, (string) $literal);
-    }
-
-    public function testLiteralInverted()
-    {
-        $literal = new Literal($this->package, false);
-
-        $inverted = $literal->inverted();
-
-        $this->assertInstanceOf('\Composer\DependencyResolver\Literal', $inverted);
-        $this->assertTrue($inverted->isWanted());
-        $this->assertSame($this->package, $inverted->getPackage());
-        $this->assertFalse($literal->equals($inverted));
-
-        $doubleInverted = $inverted->inverted();
-
-        $this->assertInstanceOf('\Composer\DependencyResolver\Literal', $doubleInverted);
-        $this->assertFalse($doubleInverted->isWanted());
-        $this->assertSame($this->package, $doubleInverted->getPackage());
-
-        $this->assertTrue($literal->equals($doubleInverted));
-    }
-}

+ 6 - 3
tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php

@@ -15,6 +15,7 @@ namespace Composer\Test\DependencyResolver;
 use Composer\DependencyResolver\Rule;
 use Composer\DependencyResolver\RuleSet;
 use Composer\DependencyResolver\RuleSetIterator;
+use Composer\DependencyResolver\Pool;
 
 class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
 {
@@ -22,13 +23,15 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
+        $this->pool = new Pool;
+
         $this->rules = array(
             RuleSet::TYPE_JOB => array(
-                new Rule(array(), 'job1', null),
-                new Rule(array(), 'job2', null),
+                new Rule($this->pool, array(), 'job1', null),
+                new Rule($this->pool, array(), 'job2', null),
             ),
             RuleSet::TYPE_LEARNED => array(
-                new Rule(array(), 'update1', null),
+                new Rule($this->pool, array(), 'update1', null),
             ),
             RuleSet::TYPE_PACKAGE => array(),
         );

+ 28 - 16
tests/Composer/Test/DependencyResolver/RuleSetTest.php

@@ -14,21 +14,29 @@ namespace Composer\Test\DependencyResolver;
 
 use Composer\DependencyResolver\Rule;
 use Composer\DependencyResolver\RuleSet;
-use Composer\DependencyResolver\Literal;
+use Composer\DependencyResolver\Pool;
+use Composer\Repository\ArrayRepository;
 use Composer\Test\TestCase;
 
 class RuleSetTest extends TestCase
 {
+    protected $pool;
+
+    public function setUp()
+    {
+        $this->pool = new Pool;
+    }
+
     public function testAdd()
     {
         $rules = array(
             RuleSet::TYPE_PACKAGE => array(),
             RuleSet::TYPE_JOB => array(
-                new Rule(array(), 'job1', null),
-                new Rule(array(), 'job2', null),
+                new Rule($this->pool, array(), 'job1', null),
+                new Rule($this->pool, array(), 'job2', null),
             ),
             RuleSet::TYPE_LEARNED => array(
-                new Rule(array(), 'update1', null),
+                new Rule($this->pool, array(), 'update1', null),
             ),
         );
 
@@ -48,15 +56,15 @@ class RuleSetTest extends TestCase
     {
         $ruleSet = new RuleSet;
 
-        $ruleSet->add(new Rule(array(), 'job1', null), 7);
+        $ruleSet->add(new Rule($this->pool, array(), 'job1', null), 7);
     }
 
     public function testCount()
     {
         $ruleSet = new RuleSet;
 
-        $ruleSet->add(new Rule(array(), 'job1', null), RuleSet::TYPE_JOB);
-        $ruleSet->add(new Rule(array(), 'job2', null), RuleSet::TYPE_JOB);
+        $ruleSet->add(new Rule($this->pool, array(), 'job1', null), RuleSet::TYPE_JOB);
+        $ruleSet->add(new Rule($this->pool, array(), 'job2', null), RuleSet::TYPE_JOB);
 
         $this->assertEquals(2, $ruleSet->count());
     }
@@ -65,7 +73,7 @@ class RuleSetTest extends TestCase
     {
         $ruleSet = new RuleSet;
 
-        $rule = new Rule(array(), 'job1', null);
+        $rule = new Rule($this->pool, array(), 'job1', null);
         $ruleSet->add($rule, RuleSet::TYPE_JOB);
 
         $this->assertSame($rule, $ruleSet->ruleById(0));
@@ -75,8 +83,8 @@ class RuleSetTest extends TestCase
     {
         $ruleSet = new RuleSet;
 
-        $rule1 = new Rule(array(), 'job1', null);
-        $rule2 = new Rule(array(), 'job1', null);
+        $rule1 = new Rule($this->pool, array(), 'job1', null);
+        $rule2 = new Rule($this->pool, array(), 'job1', null);
         $ruleSet->add($rule1, RuleSet::TYPE_JOB);
         $ruleSet->add($rule2, RuleSet::TYPE_LEARNED);
 
@@ -90,8 +98,8 @@ class RuleSetTest extends TestCase
     public function testGetIteratorFor()
     {
         $ruleSet = new RuleSet;
-        $rule1 = new Rule(array(), 'job1', null);
-        $rule2 = new Rule(array(), 'job1', null);
+        $rule1 = new Rule($this->pool, array(), 'job1', null);
+        $rule2 = new Rule($this->pool, array(), 'job1', null);
 
         $ruleSet->add($rule1, RuleSet::TYPE_JOB);
         $ruleSet->add($rule2, RuleSet::TYPE_LEARNED);
@@ -104,8 +112,8 @@ class RuleSetTest extends TestCase
     public function testGetIteratorWithout()
     {
         $ruleSet = new RuleSet;
-        $rule1 = new Rule(array(), 'job1', null);
-        $rule2 = new Rule(array(), 'job1', null);
+        $rule1 = new Rule($this->pool, array(), 'job1', null);
+        $rule2 = new Rule($this->pool, array(), 'job1', null);
 
         $ruleSet->add($rule1, RuleSet::TYPE_JOB);
         $ruleSet->add($rule2, RuleSet::TYPE_LEARNED);
@@ -149,9 +157,13 @@ class RuleSetTest extends TestCase
 
     public function testToString()
     {
+        $repo = new ArrayRepository;
+        $repo->addPackage($p = $this->getPackage('foo', '2.1'));
+        $this->pool->addRepository($repo);
+
         $ruleSet = new RuleSet;
-        $literal = new Literal($this->getPackage('foo', '2.1'), true);
-        $rule = new Rule(array($literal), 'job1', null);
+        $literal = $p->getId();
+        $rule = new Rule($this->pool, array($literal), 'job1', null);
 
         $ruleSet->add($rule, RuleSet::TYPE_JOB);
 

+ 29 - 83
tests/Composer/Test/DependencyResolver/RuleTest.php

@@ -13,22 +13,29 @@
 namespace Composer\Test\DependencyResolver;
 
 use Composer\DependencyResolver\Rule;
-use Composer\DependencyResolver\Literal;
+use Composer\DependencyResolver\Pool;
+use Composer\Repository\ArrayRepository;
 use Composer\Test\TestCase;
 
 class RuleTest extends TestCase
 {
+    protected $pool;
+
+    public function setUp()
+    {
+        $this->pool = new Pool;
+    }
+
     public function testGetHash()
     {
-        $rule = new Rule(array(), 'job1', null);
-        $rule->ruleHash = '123';
+        $rule = new Rule($this->pool, array(123), 'job1', null);
 
-        $this->assertEquals('123', $rule->getHash());
+        $this->assertEquals(substr(md5('123'), 0, 5), $rule->getHash());
     }
 
     public function testSetAndGetId()
     {
-        $rule = new Rule(array(), 'job1', null);
+        $rule = new Rule($this->pool, array(), 'job1', null);
         $rule->setId(666);
 
         $this->assertEquals(666, $rule->getId());
@@ -36,73 +43,31 @@ class RuleTest extends TestCase
 
     public function testEqualsForRulesWithDifferentHashes()
     {
-        $rule = new Rule(array(), 'job1', null);
-        $rule->ruleHash = '123';
-
-        $rule2 = new Rule(array(), 'job1', null);
-        $rule2->ruleHash = '321';
-
-        $this->assertFalse($rule->equals($rule2));
-    }
-
-    public function testEqualsForRulesWithDifferentLiterals()
-    {
-        $literal = $this->getLiteralMock();
-        $literal->expects($this->any())
-            ->method('getId')
-            ->will($this->returnValue(1));
-        $rule = new Rule(array($literal), 'job1', null);
-        $rule->ruleHash = '123';
-
-        $literal = $this->getLiteralMock();
-        $literal->expects($this->any())
-            ->method('getId')
-            ->will($this->returnValue(12));
-        $rule2 = new Rule(array($literal), 'job1', null);
-        $rule2->ruleHash = '123';
+        $rule = new Rule($this->pool, array(1, 2), 'job1', null);
+        $rule2 = new Rule($this->pool, array(1, 3), 'job1', null);
 
         $this->assertFalse($rule->equals($rule2));
     }
 
     public function testEqualsForRulesWithDifferLiteralsQuantity()
     {
-        $literal = $this->getLiteralMock();
-        $literal->expects($this->any())
-            ->method('getId')
-            ->will($this->returnValue(1));
-        $literal2 = $this->getLiteralMock();
-        $literal2->expects($this->any())
-            ->method('getId')
-            ->will($this->returnValue(12));
-
-        $rule = new Rule(array($literal, $literal2), 'job1', null);
-        $rule->ruleHash = '123';
-        $rule2 = new Rule(array($literal), 'job1', null);
-        $rule2->ruleHash = '123';
+        $rule = new Rule($this->pool, array(1, 12), 'job1', null);
+        $rule2 = new Rule($this->pool, array(1), 'job1', null);
 
         $this->assertFalse($rule->equals($rule2));
     }
 
-    public function testEqualsForRulesWithThisSameLiterals()
+    public function testEqualsForRulesWithSameLiterals()
     {
-        $literal = $this->getLiteralMock();
-        $literal->expects($this->any())
-            ->method('getId')
-            ->will($this->returnValue(1));
-        $literal2 = $this->getLiteralMock();
-        $literal2->expects($this->any())
-            ->method('getId')
-            ->will($this->returnValue(12));
-
-        $rule = new Rule(array($literal, $literal2), 'job1', null);
-        $rule2 = new Rule(array($literal, $literal2), 'job1', null);
+        $rule = new Rule($this->pool, array(1, 12), 'job1', null);
+        $rule2 = new Rule($this->pool, array(1, 12), 'job1', null);
 
         $this->assertTrue($rule->equals($rule2));
     }
 
     public function testSetAndGetType()
     {
-        $rule = new Rule(array(), 'job1', null);
+        $rule = new Rule($this->pool, array(), 'job1', null);
         $rule->setType('someType');
 
         $this->assertEquals('someType', $rule->getType());
@@ -110,7 +75,7 @@ class RuleTest extends TestCase
 
     public function testEnable()
     {
-        $rule = new Rule(array(), 'job1', null);
+        $rule = new Rule($this->pool, array(), 'job1', null);
         $rule->disable();
         $rule->enable();
 
@@ -120,7 +85,7 @@ class RuleTest extends TestCase
 
     public function testDisable()
     {
-        $rule = new Rule(array(), 'job1', null);
+        $rule = new Rule($this->pool, array(), 'job1', null);
         $rule->enable();
         $rule->disable();
 
@@ -128,24 +93,10 @@ class RuleTest extends TestCase
         $this->assertFalse($rule->isEnabled());
     }
 
-    public function testSetWeak()
-    {
-        $rule = new Rule(array(), 'job1', null);
-        $rule->setWeak(true);
-
-        $rule2 = new Rule(array(), 'job1', null);
-        $rule2->setWeak(false);
-
-        $this->assertTrue($rule->isWeak());
-        $this->assertFalse($rule2->isWeak());
-    }
-
     public function testIsAssertions()
     {
-        $literal = $this->getLiteralMock();
-        $literal2 = $this->getLiteralMock();
-        $rule = new Rule(array($literal, $literal2), 'job1', null);
-        $rule2 = new Rule(array($literal), 'job1', null);
+        $rule = new Rule($this->pool, array(1, 12), 'job1', null);
+        $rule2 = new Rule($this->pool, array(1), 'job1', null);
 
         $this->assertFalse($rule->isAssertion());
         $this->assertTrue($rule2->isAssertion());
@@ -153,18 +104,13 @@ class RuleTest extends TestCase
 
     public function testToString()
     {
-        $literal = new Literal($this->getPackage('foo', '2.1'), true);
-        $literal2 = new Literal($this->getPackage('baz', '1.1'), false);
+        $repo = new ArrayRepository;
+        $repo->addPackage($p1 = $this->getPackage('foo', '2.1'));
+        $repo->addPackage($p2 = $this->getPackage('baz', '1.1'));
+        $this->pool->addRepository($repo);
 
-        $rule = new Rule(array($literal, $literal2), 'job1', null);
+        $rule = new Rule($this->pool, array($p1->getId(), -$p2->getId()), 'job1', null);
 
         $this->assertEquals('(-baz-1.1.0.0|+foo-2.1.0.0)', $rule->__toString());
     }
-
-    private function getLiteralMock()
-    {
-        return $this->getMockBuilder('Composer\DependencyResolver\Literal')
-            ->disableOriginalConstructor()
-            ->getMock();
-    }
 }

Vissa filer visades inte eftersom för många filer har ändrats