Переглянути джерело

Import initial partial port of the libzypp satsolver.

Nils Adermann 14 роки тому
батько
коміт
933cc6179b

+ 39 - 0
PORTING_INFO

@@ -0,0 +1,39 @@
+ * add rule
+ *  p = direct literal; always < 0 for installed rpm rules
+ *  d, if < 0 direct literal, if > 0 offset into whatprovides, if == 0 rule is assertion (look at p only)
+ *
+ *
+ * A requires b, b provided by B1,B2,B3 => (-A|B1|B2|B3)
+ *
+ * p < 0 : pkg id of A
+ * d > 0 : Offset in whatprovidesdata (list of providers of b)
+ *
+ * A conflicts b, b provided by B1,B2,B3 => (-A|-B1), (-A|-B2), (-A|-B3)
+ * p < 0 : pkg id of A
+ * d < 0 : Id of solvable (e.g. B1)
+ *
+ * d == 0: unary rule, assertion => (A) or (-A)
+ *
+ *   Install:    p > 0, d = 0   (A)             user requested install
+ *   Remove:     p < 0, d = 0   (-A)            user requested remove (also: uninstallable)
+ *   Requires:   p < 0, d > 0   (-A|B1|B2|...)  d: <list of providers for requirement of p>
+ *   Updates:    p > 0, d > 0   (A|B1|B2|...)   d: <list of updates for solvable p>
+ *   Conflicts:  p < 0, d < 0   (-A|-B)         either p (conflict issuer) or d (conflict provider) (binary rule)
+ *                                              also used for obsoletes
+ *   ?:          p > 0, d < 0   (A|-B)
+ *   No-op ?:    p = 0, d = 0   (null)          (used as policy rule placeholder)
+ *
+ *   resulting watches:
+ *   ------------------
+ *   Direct assertion (no watch needed)( if d <0 ) --> d = 0, w1 = p, w2 = 0
+ *   Binary rule: p = first literal, d = 0, w2 = second literal, w1 = p
+ *   every other : w1 = p, w2 = whatprovidesdata[d];
+ *   Disabled rule: w1 = 0
+ *
+ *   always returns a rule for non-rpm rules
+
+
+
+p > 0, d = 0, (A), w1 = p, w2 = 0
+p < 0, d = 0, (-A), w1 = p, w2 = 0
+p !=0, d = 0, (p|q), w1 = p, w2 = q

+ 25 - 0
phpunit.xml.dist

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         syntaxCheck="false"
+         bootstrap="tests/bootstrap.php"
+>
+    <testsuites>
+        <testsuite name="Composer Test Suite">
+            <directory>./tests/Composer/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./src/Composer/</directory>
+        </whitelist>
+    </filter>
+</phpunit>

+ 62 - 0
src/Composer/DependencyResolver/ArrayRepository.php

@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+/**
+ * A repository implementation that simply stores packages in an array
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class ArrayRepository implements RepositoryInterface
+{
+    protected $packages = array();
+
+    /**
+     * Adds a new package to the repository
+     *
+     * @param Package $package
+     */
+    public function addPackage(Package $package)
+    {
+        $this->packages[$package->getId()] = $package;
+    }
+
+    /**
+     * Returns all contained packages
+     *
+     * @return array All packages
+     */
+    public function getPackages()
+    {
+        return $this->packages;
+    }
+
+    /**
+     * Checks if a package is contained in this repository
+     *
+     * @return bool
+     */
+    public function contains(Package $package)
+    {
+        return isset($this->packages[$package->getId()]);
+    }
+
+    /**
+     * Returns the number of packages in this repository
+     *
+     * @return int Number of packages
+     */
+    public function count()
+    {
+        return count($this->packages);
+    }
+}

+ 63 - 0
src/Composer/DependencyResolver/DefaultPolicy.php

@@ -0,0 +1,63 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * 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 DefaultPolicy implements PolicyInterface
+{
+    public function allowUninstall()
+    {
+        return false;
+    }
+
+    public function allowDowngrade()
+    {
+        return false;
+    }
+
+    public function versionCompare(Package $a, Package $b, $operator)
+    {
+        return version_compare($a->getVersion(), $b->getVersion(), $operator);
+    }
+
+    public function findUpdatePackages(Solver $solver, Pool $pool, RepositoryInterface $repo, Package $package, $allowAll = false)
+    {
+        $packages = array();
+
+        foreach ($pool->whatProvides($package->getName()) as $candidate) {
+            // skip old packages unless downgrades are an option
+            if (!$allowAll && !$this->allowDowngrade() && $this->versionCompare($package, $candidate, '>')) {
+                continue;
+            }
+
+            if ($candidate != $package) {
+                $packages[] = $candidate;
+            }
+        }
+
+        return $packages;
+    }
+
+    public function installable(Solver $solver, Pool $pool, RepositoryInterface $repo, Package $package)
+    {
+        // todo: package blacklist?
+        return true;
+    }
+
+    public function selectPreferedPackages(array $literals)
+    {
+        // todo: prefer installed, recommended, highest priority repository, ...
+        return array($literals[0]);
+    }
+}

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

@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * 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 Literal
+{
+    protected $wanted;
+
+    public function __construct(Package $package, $wanted)
+    {
+        $this->package = $package;
+        $this->wanted = $wanted;
+    }
+
+    public function isWanted()
+    {
+        return $this->wanted;
+    }
+
+    public function getPackage()
+    {
+        return $this->package;
+    }
+
+    public function getPackageId()
+    {
+        return $this->package->getId();
+    }
+
+    public function getId()
+    {
+        return (($this->wanted) ? 1 : -1) * $this->package->getId();
+    }
+
+    public function __toString()
+    {
+        return ($this->isWanted() ? '+' : '-').$this->getPackage();
+    }
+
+    public function inverted()
+    {
+        return new Literal($this->getPackage(), !$this->isWanted());
+    }
+
+    public function equals(Literal $b)
+    {
+        return $this->getId() === $b->getId();
+    }
+}

+ 190 - 0
src/Composer/DependencyResolver/MemoryPackage.php

@@ -0,0 +1,190 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+/**
+ * A package with setters for all members to create it dynamically in memory
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class MemoryPackage extends Package
+{
+    protected $releaseType;
+    protected $version;
+
+    protected $requires = array();
+    protected $conflicts = array();
+    protected $provides = array();
+    protected $replaces = array();
+    protected $recommends = array();
+    protected $suggests = array();
+
+    /**
+     * Creates a new in memory package.
+     *
+     * @param string $name        The package's name
+     * @param string $version     The package's version
+     * @param string $releaseType The package's release type (beta/rc/stable)
+     * @param int    $id          A positive unique id, zero to auto generate
+     */
+    public function __construct($name, $version, $releaseType = 'stable', $id = 0)
+    {
+        parent::__construct($name, $id);
+
+        $this->releaseType = $releaseType;
+        $this->version = $version;
+    }
+
+    /**
+     * Set the release type
+     *
+     * @param string $releaseType
+     */
+    public function setReleaseType($releaseType)
+    {
+        $this->releaseType = $releaseType;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getReleaseType()
+    {
+        return $this->releaseType;
+    }
+
+    /**
+     * Set the version
+     *
+     * @param string $version
+     */
+    public function setVersion($version)
+    {
+        $this->version = $version;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getVersion()
+    {
+        return $this->version;
+    }
+
+    /**
+     * Set the required packages
+     *
+     * @param array $requires A set of package relations
+     */
+    public function setRequires(array $requires)
+    {
+        $this->requires = $requires;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getRequires()
+    {
+        return $this->requires;
+    }
+
+    /**
+     * Set the conflicting packages
+     *
+     * @param array $conflicts A set of package relations
+     */
+    public function setConflicts(array $conflicts)
+    {
+        $this->conflicts = $conflicts;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getConflicts()
+    {
+        return $this->conflicts;
+    }
+
+    /**
+     * Set the provided virtual packages
+     *
+     * @param array $conflicts A set of package relations
+     */
+    public function setProvides(array $provides)
+    {
+        $this->provides = $provides;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getProvides()
+    {
+        return $this->provides;
+    }
+
+    /**
+     * Set the packages this one replaces
+     *
+     * @param array $conflicts A set of package relations
+     */
+    public function setReplaces(array $replaces)
+    {
+        $this->replaces = $replaces;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getReplaces()
+    {
+        return $this->replaces;
+    }
+
+    /**
+     * Set the recommended packages
+     *
+     * @param array $conflicts A set of package relations
+     */
+    public function setRecommends(array $recommends)
+    {
+        $this->recommends = $recommends;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getRecommends()
+    {
+        return $this->recommends;
+    }
+
+    /**
+     * Set the suggested packages
+     *
+     * @param array $conflicts A set of package relations
+     */
+    public function setSuggests(array $suggests)
+    {
+        $this->suggests = $suggests;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getSuggests()
+    {
+        return $this->suggests;
+    }
+}

+ 189 - 0
src/Composer/DependencyResolver/Package.php

@@ -0,0 +1,189 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+use Composer\DependencyResolver\RelationConstraint\RelationConstraintInterface;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+abstract class Package
+{
+    protected $name;
+    protected $id;
+
+    static public $incremental_id = 1;
+
+    /**
+     * All descendents' constructors should call this parent constructor
+     *
+     * @param string $name The package's name
+     * @param int    $id   A positive unique id, zero to auto generate
+     */
+    public function __construct($name, $id = 0)
+    {
+        $this->name = $name;
+
+        if (!$id) {
+            $this->id = self::$incremental_id++;
+        } else {
+            $this->id = $id;
+            self::$incremental_id = $id + 1;
+        }
+    }
+
+    /**
+     * Returns the package's identifier
+     *
+     * @return int Package id - a unique positive number
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * Returns the package's name without version info, thus not a unique identifier
+     *
+     * @return string package name
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Returns a set of names that could refer to this package
+     *
+     * No version or release type information should be included in any of the
+     * names. Provided or replaced package names need to be returned as well.
+     *
+     * @return array An array of strings refering to this package
+     */
+    public function getNames()
+    {
+        $names = array(
+            $this->getName(),
+        );
+
+        foreach ($this->getProvides() as $relation) {
+            $names[] = $relation->getToPackageName();
+        }
+
+        foreach ($this->getReplaces() as $relation) {
+            $names[] = $relation->getToPackageName();
+        }
+
+        return $names;
+    }
+
+    /**
+     * Checks if the package matches the given constraint directly or through
+     * provided or replaced packages
+     *
+     * @param string                      $name       Name of the package to be matched
+     * @param RelationConstraintInterface $constraint The constraint to verify
+     * @return bool                                   Whether this package matches the name and constraint
+     */
+    public function matches($name, RelationConstraintInterface $constraint)
+    {
+        if ($this->name === $name) {
+            return $constraint->matches($this->getReleaseType(), $this->getVersion());
+        }
+
+        foreach ($this->getProvides() as $relation) {
+            if ($relation->getToPackageName() === $name) {
+                return $constraint->matches($relation->getToReleaseType(), $relation->getToVersion());
+            }
+        }
+
+        foreach ($this->getReplaces() as $relation) {
+            if ($relation->getToPackageName() === $name) {
+                return $constraint->matches($relation->getToReleaseType(), $relation->getToVersion());
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the release type of this package, e.g. stable or beta
+     *
+     * @return string The release type
+     */
+    abstract public function getReleaseType();
+
+    /**
+     * Returns the version of this package
+     *
+     * @return string version
+     */
+    abstract public function getVersion();
+
+    /**
+     * Returns a set of relations to packages which need to be installed before
+     * this package can be installed
+     *
+     * @return array An array of package relations defining required packages
+     */
+    abstract public function getRequires();
+
+    /**
+     * Returns a set of relations to packages which must not be installed at the
+     * same time as this package
+     *
+     * @return array An array of package relations defining conflicting packages
+     */
+    abstract public function getConflicts();
+
+    /**
+     * Returns a set of relations to virtual packages that are provided through
+     * this package
+     *
+     * @return array An array of package relations defining provided packages
+     */
+    abstract public function getProvides();
+
+    /**
+     * Returns a set of relations to packages which can alternatively be
+     * satisfied by installing this package
+     *
+     * @return array An array of package relations defining replaced packages
+     */
+    abstract public function getReplaces();
+
+    /**
+     * Returns a set of relations to packages which are recommended in
+     * combination with this package.
+     *
+     * @return array An array of package relations defining recommended packages
+     */
+    abstract public function getRecommends();
+
+    /**
+     * Returns a set of relations to packages which are suggested in combination
+     * with this package.
+     *
+     * @return array An array of package relations defining suggested packages
+     */
+    abstract public function getSuggests();
+
+    /**
+     * Converts the package into a readable and unique string
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->getName().'-'.$this->getReleaseType().'-'.$this->getVersion();
+    }
+}

+ 53 - 0
src/Composer/DependencyResolver/PackageRelation.php

@@ -0,0 +1,53 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+use Composer\DependencyResolver\RelationConstraint\RelationConstraintInterface;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class PackageRelation
+{
+    protected $fromPackageName;
+    protected $toPackageName;
+    protected $constraint;
+    protected $description;
+
+    public function __construct($fromPackageName, $toPackageName, RelationConstraintInterface $constraint, $description = 'relates to')
+    {
+        $this->fromPackageName = $fromPackageName;
+        $this->toPackageName = $toPackageName;
+        $this->constraint = $constraint;
+        $this->description = $description;
+    }
+
+    public function getToPackageName()
+    {
+        return $this->toPackageName;
+    }
+
+    public function getFromPackageName()
+    {
+        return $this->fromPackageName;
+    }
+
+    public function getConstraint()
+    {
+        return $this->constraint;
+    }
+
+    public function __toString()
+    {
+        return $this->fromPackageName.' '.$this->description.' '.$this->toPackageName.' ('.$this->constraint.')';
+    }
+}

+ 25 - 0
src/Composer/DependencyResolver/PolicyInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * 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>
+ */
+interface PolicyInterface
+{
+    function allowUninstall();
+    function allowDowngrade();
+    function versionCompare(Package $a, Package $b, $operator);
+    function findUpdatePackages(Solver $solver, Pool $pool, RepositoryInterface $repo, Package $package, $allowAll);
+    function installable(Solver $solver, Pool $pool, RepositoryInterface $repo, Package $package);
+    function selectPreferedPackages(array $literals);
+}

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

@@ -0,0 +1,79 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+use Composer\DependencyResolver\RelationConstraint\RelationConstraintInterface;
+
+/**
+ * A package pool contains repositories that provide packages.
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class Pool
+{
+    protected $repositories = array();
+    protected $packages = array();
+    protected $packageByName = array();
+
+    /**
+     * Adds a repository and its packages to this package pool
+     *
+     * @param RepositoryInterface $repo A package repository
+     */
+    public function addRepository(RepositoryInterface $repo)
+    {
+        $this->repositories[] = $repo;
+
+        foreach ($repo->getPackages() as $package) {
+            $this->packages[$package->getId()] = $package;
+
+            foreach ($package->getNames() as $name) {
+                if (!isset($this->packageByName[$name])) {
+                    $this->packageByNameByName[$name] = array();
+                }
+
+                $this->packageByName[$name][] = $package;
+            }
+        }
+    }
+
+    /**
+     * Searches all packages providing the given package name and match the constraint
+     *
+     * @param string                     $name        The package name to be searched for
+     * @param RelationConstraintInterface $constraint A constraint that all returned
+     *                                                packages must match or null to return all
+     * @return array                                  A set of packages
+     */
+    public function whatProvides($name, RelationConstraintInterface $constraint = null)
+    {
+        if (!isset($this->packageByName[$name])) {
+            return array();
+        }
+
+        $candidates = $this->packageByName[$name];
+
+        if (null === $constraint) {
+            return $candidates;
+        }
+
+        $result = array();
+
+        foreach ($candidates as $candidate) {
+            if ($candidate->matches($name, $constraint)) {
+                $result[] = $candidate;
+            }
+        }
+
+        return $result;
+    }
+}

+ 21 - 0
src/Composer/DependencyResolver/RelationConstraint/RelationConstraintInterface.php

@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver\RelationConstraint;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+interface RelationConstraintInterface
+{
+    function matches($releaseType, $version);
+    function __toString();
+}

+ 47 - 0
src/Composer/DependencyResolver/RelationConstraint/VersionConstraint.php

@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver\RelationConstraint;
+
+/**
+ * Constrains a package relation based on package version
+ *
+ * Version numbers must be compatible with version_compare
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class VersionConstraint implements RelationConstraintInterface
+{
+    private $operator;
+    private $version;
+
+    /**
+     * Sets operator and version to compare a package with
+     *
+     * @param string $operator A comparison operator
+     * @param string $version  A version to compare to
+     */
+    public function __construct($operator, $version)
+    {
+        $this->operator = $operator;
+        $this->version = $version;
+    }
+
+    public function matches($releaseType, $version)
+    {
+        return version_compare($version, $this->version, $this->operator);
+    }
+
+    public function __toString()
+    {
+        return $this->operator.' '.$this->version;
+    }
+}

+ 21 - 0
src/Composer/DependencyResolver/RepositoryInterface.php

@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * 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>
+ */
+interface RepositoryInterface extends \Countable
+{
+    function getPackages();
+    function contains(Package $package);
+}

+ 57 - 0
src/Composer/DependencyResolver/Request.php

@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * 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 Request
+{
+    protected $jobs;
+    protected $pool;
+
+    public function __construct(Pool $pool)
+    {
+        $this->pool = $pool;
+    }
+
+    public function install($packageName)
+    {
+        $this->addJob($packageName, 'install');
+    }
+
+    public function update($packageName)
+    {
+        $this->addJob($packageName, 'update');
+    }
+
+    public function remove($packageName)
+    {
+        $this->addJob($packageName, 'remove');
+    }
+
+    protected function addJob($packageName, $cmd)
+    {
+        $packages = $this->pool->whatProvides($packageName);
+
+        $this->jobs[] = array(
+            'packages' => $packages,
+            'cmd' => $cmd,
+            'packageName' => $packageName,
+        );
+    }
+
+    public function getJobs()
+    {
+        return $this->jobs;
+    }
+}

+ 161 - 0
src/Composer/DependencyResolver/Rule.php

@@ -0,0 +1,161 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * 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 Rule
+{
+    protected $disabled;
+    protected $literals;
+    protected $type;
+
+    public $watch1;
+    public $watch2;
+
+    public $next1;
+    public $next2;
+
+    public function __construct(array $literals, $reason, $reasonData)
+    {
+        // sort all packages ascending by id
+        usort($literals, array($this, 'compareLiteralsById'));
+
+        $this->literals = $literals;
+        $this->reason = $reason;
+        $this->reasonData = $reasonData;
+
+        $this->disabled = false;
+
+        $this->watch1 = (count($this->literals) > 0) ? $literals[0]->getId() : 0;
+        $this->watch2 = (count($this->literals) > 1) ? $literals[1]->getId() : 0;
+
+        $this->type = -1;
+    }
+
+    /**
+     * 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 (count($this->literals) != count($rule->literals)) {
+            return false;
+        }
+
+        for ($i = 0, $n = count($this->literals); $i < $n; $i++) {
+            if (!$this->literals[$i]->equals($rule->literals[$i])) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public function setType($type)
+    {
+        $this->type = $type;
+    }
+
+    public function getType($type)
+    {
+    }
+
+    public function disable()
+    {
+        $this->disabled = true;
+    }
+
+    public function enable()
+    {
+        $this->disabled = false;
+    }
+
+    public function isDisabled()
+    {
+        return $this->disabled;
+    }
+
+    public function isEnabled()
+    {
+        return !$this->disabled;
+    }
+
+    public function getLiterals()
+    {
+        return $this->literals;
+    }
+
+    public function isAssertion()
+    {
+        return 1 === count($this->literals);
+    }
+
+    public function getNext(Literal $literal)
+    {
+        if ($this->watch1->equals($literal)) {
+            return $this->next1;
+        } else {
+            return $this->next2;
+        }
+    }
+
+    public function getOtherWatch(Literal $literal)
+    {
+        if ($this->watch1->equals($literal)) {
+            return $this->watch2;
+        } else {
+            return $this->watch1;
+        }
+    }
+
+    /**
+     * Formats a rule as a string of the format (Literal1|Literal2|...)
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        $result = '(';
+
+        foreach ($this->literals as $i => $literal) {
+            if ($i != 0) {
+                $result .= '|';
+            }
+            $result .= $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;
+    }
+}

+ 1817 - 0
src/Composer/DependencyResolver/Solver.php

@@ -0,0 +1,1817 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * 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 Solver
+{
+    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_NOTHING_PROVIDES_DEP = 6;
+    const RULE_PACKAGE_CONFLICT = 7;
+    const RULE_PACKAGE_NOT_EXIST = 8;
+    const RULE_PACKAGE_REQUIRES = 9;
+
+    const TYPE_PACKAGE = 0;
+    const TYPE_FEATURE = 1;
+    const TYPE_UPDATE = 2;
+    const TYPE_JOB = 3;
+    const TYPE_WEAK = 4;
+    const TYPE_LEARNED = 5;
+
+    protected $policy;
+    protected $pool;
+    protected $installed;
+    protected $rules;
+    protected $updateAll;
+
+    protected $addedMap = array();
+    protected $fixMap = array();
+    protected $updateMap = array();
+    protected $watches = array();
+    protected $removeWatches = array();
+
+    public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
+    {
+        $this->policy = $policy;
+        $this->pool = $pool;
+        $this->installed = $installed;
+        $this->rules = array(
+            // order matters here! further down => higher priority
+            self::TYPE_LEARNED => array(),
+            self::TYPE_WEAK => array(),
+            self::TYPE_FEATURE => array(),
+            self::TYPE_UPDATE => array(),
+            self::TYPE_JOB => array(),
+            self::TYPE_PACKAGE => array(),
+        );
+    }
+
+    /**
+     * 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 Package $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
+     */
+    public function createRequireRule(Package $package, array $providers, $reason, $reasonData = null)
+    {
+        $literals = array(new Literal($package, false));
+
+        foreach ($providers as $provider) {
+            // self fulfilling rule?
+            if ($provider === $package) {
+                return null;
+            }
+            $literals[] = new Literal($provider, true);
+        }
+
+        return new Rule($literals, $reason, $reasonData);
+    }
+
+    /**
+     * Create a new rule for updating a package
+     *
+     * If package A1 can be updated to A2 or A3 the rule is (A1|A2|A3).
+     *
+     * @param Package $package    The package to be updated
+     * @param array   $updates    An array of update candidate packages
+     * @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 or null if tautology
+     */
+    protected function createUpdateRule(Package $package, array $updates, $reason, $reasonData = null)
+    {
+        $literals = array(new Literal($package, true));
+
+        foreach ($updates as $update) {
+            $literals[] = new Literal($update, true);
+        }
+
+        return new Rule($literals, $reason, $reasonData);
+    }
+
+    /**
+     * Creates a new rule for installing a package
+     *
+     * The rule is simply (A) for a package A to be installed.
+     *
+     * @param Package $package    The package to be installed
+     * @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
+     */
+    public function createInstallRule(Package $package, $reason, $reasonData = null)
+    {
+        return new Rule(new Literal($package, true));
+    }
+
+    /**
+     * 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 mixed   $reasonData Any data, e.g. the package name, that goes with
+     *                            the reason
+     * @return Rule               The generated rule
+     */
+    public function createInstallOneOfRule(array $packages, $reason, $reasonData = null)
+    {
+        if (empty($packages)) {
+            return $this->createImpossibleRule($reason, $reasonData);
+        }
+
+        $literals = array();
+        foreach ($packages as $package) {
+            $literals[] = new Literal($package, true);
+        }
+
+        return new Rule($literals, $reason, $reasonData);
+    }
+
+    /**
+     * Creates a rule to remove a package
+     *
+     * The rule for a package A is (-A).
+     *
+     * @param Package $package    The package to be removed
+     * @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
+     */
+    public function createRemoveRule(Package $package, $reason, $reasonData = null)
+    {
+        return new Rule(array(new Literal($package, false)), $reason, $reasonData);
+    }
+
+    /**
+     * 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 Package $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
+     */
+    public function createConflictRule(Package $issuer, Package $provider, $reason, $reasonData = null)
+    {
+        // ignore self conflict
+        if ($issuer === $provider) {
+            return null;
+        }
+
+        return new Rule(array(new Literal($issuer, false), new Literal($provider, false)), $reason, $reasonData);
+    }
+
+    /**
+     * Intentionally creates a rule impossible to solve
+     *
+     * The rule is an empty one so it can never be satisfied.
+     *
+     * @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               An empty rule
+     */
+    public function createImpossibleRule($reason, $reasonData = null)
+    {
+        return new Rule(array(), $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 the rule may also be null to indicate that no rule should be
+     * added.
+     *
+     * @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 ($newRule) {
+            foreach ($this->rules as $rules) {
+                foreach ($rules as $rule) {
+                    if ($rule->equals($newRule)) {
+                        return;
+                    }
+                }
+            }
+
+            $newRule->setType($type);
+            $this->rules[$type][] = $newRule;
+        }
+    }
+
+    public function addRulesForPackage(Package $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;
+
+            $dontFix = 0;
+            if ($this->installed->contains($package) && !isset($this->fixMap[$package->getId()])) {
+                $dontFix = 1;
+            }
+
+            if (!$dontFix && !$this->policy->installable($this, $this->pool, $this->installed, $package)) {
+                $this->addRule(self::TYPE_PACKAGE, $this->createRemoveRule($package, self::RULE_NOT_INSTALLABLE, (string) $package));
+                continue;
+            }
+
+            foreach ($package->getRequires() as $relation) {
+                $possibleRequires = $this->pool->whatProvides($relation->getToPackageName(), $relation->getConstraint());
+
+                // the strategy here is to not insist on dependencies
+                // that are already broken. so if we find one provider
+                // that was already installed, we know that the
+                // dependency was not broken before so we enforce it
+                if ($dontFix) {
+                    $foundInstalled = false;
+                    foreach ($possibleRequires as $require) {
+                        if ($this->installed->contains($require)) {
+                            $foundInstalled = true;
+                            break;
+                        }
+                    }
+
+                    // no installed provider found: previously broken dependency => don't add rule
+                    if (!$foundInstalled) {
+                        continue;
+                    }
+                }
+
+                $this->addRule(self::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, self::RULE_PACKAGE_REQUIRES, (string) $relation));
+
+                foreach ($possibleRequires as $require) {
+                    $workQueue->enqueue($require);
+                }
+            }
+
+            foreach ($package->getConflicts() as $relation) {
+                $possibleConflicts = $this->pool->whatProvides($relation->getToPackageName(), $relation->getConstraint());
+
+                foreach ($possibleConflicts as $conflict) {
+                    if ($dontfix && $this->installed->contains($conflict)) {
+                        continue;
+                    }
+
+                    $this->addRule(self::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, self::RULE_PACKAGE_CONFLICT, (string) $relation));
+                }
+            }
+
+            foreach ($package->getRecommends() as $relation) {
+                foreach ($this->pool->whatProvides($relation->getToPackageName(), $relation->getConstraint()) as $recommend) {
+                    $workQueue->enqueue($recommend);
+                }
+            }
+
+            foreach ($package->getSuggests() as $relation) {
+                foreach ($this->pool->whatProvides($relation->getToPackageName(), $relation->getConstraint()) as $suggest) {
+                    $workQueue->enqueue($suggest);
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds all rules for all update packages of a given package
+     *
+     * @param Package $package  Rules for this package's updates are to be added
+     * @param bool    $allowAll Whether downgrades are allowed
+     */
+    private function addRulesForUpdatePackages(Package $package, $allowAll)
+    {
+        $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installed, $package, $allowAll);
+
+        $this->addRulesForPackage($package);
+
+        foreach ($updates as $update) {
+            $this->addRulesForPackage($update);
+        }
+    }
+
+    /**
+     * Sets up watch chains for all rules.
+     *
+     * 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 makeWatches()
+    {
+        foreach ($this->rules as $type => $rules) {
+            foreach ($rules as $i => $rule) {
+
+                // skip simple assertions of the form (A) or (-A)
+                if ($rule->isAssertion()) {
+                    continue;
+                }
+
+                if (!isset($this->watches[$rule->watch1])) {
+                    $this->watches[$rule->watch1] = 0;
+                }
+
+                $rule->next1 = $this->watches[$rule->watch1];
+                $this->watches[$rule->watch1] = $rule;
+
+                if (!isset($this->watches[$rule->watch2])) {
+                    $this->watches[$rule->watch2] = 0;
+                }
+
+                $rule->next2 = $this->watches[$rule->watch2];
+                $this->watches[$rule->watch2] = $rule;
+
+            }
+        }
+    }
+
+    private function findDecisionRule(Package $package)
+    {
+        foreach ($this->decisionQueue as $i => $literal) {
+            if ($package === $literal->getPackage()) {
+                return $this->decisionQueueWhy[$i];
+            }
+        }
+
+        return null;
+    }
+
+    private function makeAssertionRuleDecisions()
+    {
+        // do we need to decide a SYSTEMSOLVABLE at level 1?
+
+        foreach ($this->rules as $type => $rules) {
+            if (self::TYPE_WEAK === $type) {
+                continue;
+            }
+            foreach ($rules as $rule) {
+                if (!$rule->isAssertion() || $rule->isDisabled()) {
+                    continue;
+                }
+
+                $literals = $rule->getLiterals();
+                $literal = $literals[0];
+
+                if (!$this->decided($literal->getPackage())) {
+
+                }
+
+                if ($this->decisionsSatisfy($literal)) {
+                    continue;
+                }
+
+                // found a conflict
+                if (self::TYPE_LEARNED === $type) {
+                    $rule->disable();
+                }
+
+                $conflict = $this->findDecisionRule($literal->getPackage());
+                // todo: handle conflict with systemsolvable?
+
+                if (self::TYPE_PACKAGE === $conflict->getType()) {
+
+                }
+            }
+        }
+
+        foreach ($this->rules[self::TYPE_WEAK] as $rule) {
+            if (!$rule->isAssertion() || $rule->isDisabled()) {
+                continue;
+            }
+
+            if ($this->decisionsSatisfy($literals[0])) {
+                continue;
+            }
+
+            // conflict, but this is a weak rule => disable
+            $rule->disable();
+        }
+    }
+
+    public function solve(Request $request)
+    {
+        $this->jobs = $request->getJobs();
+        $installedPackages = $this->installed->getPackages();
+
+        foreach ($this->jobs as $job) {
+            switch ($job['cmd']) {
+                case 'update-all':
+                    foreach ($installedPackages as $package) {
+                        $this->updateMap[$package->getId()] = true;
+                    }
+                break;
+
+                case 'fix-all':
+                    foreach ($installedPackages as $package) {
+                        $this->fixMap[$package->getId()] = true;
+                    }
+                break;
+            }
+
+            foreach ($job['packages'] as $package) {
+                switch ($job['cmd']) {
+                    case 'fix':
+                        if ($this->installed->contains($package)) {
+                            $this->fixMap[$package->getId()] = true;
+                        }
+                        break;
+                    case 'update':
+                        if ($this->installed->contains($package)) {
+                            $this->updateMap[$package->getId()] = true;
+                        }
+                        break;
+                }
+            }
+        }
+
+        foreach ($installedPackages as $package) {
+            $this->addRulesForPackage($package);
+        }
+
+        foreach ($installedPackages as $package) {
+            $this->addRulesForUpdatePackages($package, true);
+        }
+
+
+        foreach ($this->jobs as $job) {
+            foreach ($job['packages'] as $package) {
+                switch ($job['cmd']) {
+                    case 'install':
+                        $this->installCandidateMap[$package->getId()] = true;
+                        $this->addRulesForPackage($package);
+                    break;
+                }
+            }
+        }
+
+        // solver_addrpmrulesforweak(solv, &addedmap);
+        /*
+        * first pass done, we now have all the rpm rules we need.
+        * unify existing rules before going over all job rules and
+        * policy rules.
+        * at this point the system is always solvable,
+        * as an empty system (remove all packages) is a valid solution
+        */
+        // solver_unifyrules(solv);                          /* remove duplicate rpm rules */
+
+// no idea what this is
+//   /* create dup maps if needed. We need the maps early to create our
+//    * update rules */
+//   if (hasdupjob)
+//     solver_createdupmaps(solv);
+
+        foreach ($installedPackages as $package) {
+            // create a feature rule which allows downgrades
+            $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installed, $package, true);
+            $featureRule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
+
+            // create an update rule which does not allow downgrades
+            $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installed, $package, false);
+            $rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
+
+            if ($rule->equals($featureRule)) {
+                if ($this->policy->allowUninstall()) {
+                    $this->addRule(self::TYPE_WEAK, $featureRule);
+                } else {
+                    $this->addRule(self::TYPE_UPDATE, $rule);
+                }
+            } else if ($this->policy->allowUninstall()) {
+                $this->addRule(self::TYPE_WEAK, $featureRule);
+                $this->addRule(self::TYPE_WEAK, $rule);
+            }
+        }
+
+        foreach ($this->jobs as $job) {
+            switch ($job['cmd']) {
+                case 'install':
+                    $rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']);
+                    $this->addRule(self::TYPE_JOB, $rule);
+                    //$this->ruleToJob[$rule] = $job;
+                    break;
+                case 'remove':
+                    // remove all packages with this name including uninstalled
+                    // ones to make sure none of them are picked as replacements
+
+                    // todo: cleandeps
+                    foreach ($job['packages'] as $package) {
+                        $rule = $this->createRemoveRule($package, self::RULE_JOB_REMOVE);
+                        $this->addRule(self::TYPE_JOB, $rule);
+                        //$this->ruleToJob[$rule] = $job;
+                    }
+                    break;
+                case 'lock':
+                    foreach ($job['packages'] as $package) {
+                        if ($this->installed->contains($package)) {
+                            $rule = $this->createInstallRule($package, self::RULE_JOB_LOCK);
+                        } else {
+                            $rule = $this->createRemoveRule($package, self::RULE_JOB_LOCK);
+                        }
+                        $this->addRule(self::TYPE_JOB, $rule);
+                        //$this->ruleToJob[$rule] = $job;
+                    }
+                break;
+            }
+        }
+
+        // solver_addchoicerules(solv);
+
+        $this->makeWatches();
+
+        /* disable update rules that conflict with our job */
+        //solver_disablepolicyrules(solv);
+
+        /* make decisions based on job/update assertions */
+        $this->makeAssertionRuleDecisions();
+
+        $installRecommended = 0;
+        $this->runSat(true, $installRecommended);
+
+        //findrecommendedsuggested(solv);
+        //solver_prepare_solutions(solv);
+        //transaction_calculate(&solv->trans, &solv->decisionq, &solv->noobsoletes);
+
+    }
+
+    public function printRules()
+    {
+        print "\n";
+        foreach ($this->rules as $type => $rules) {
+            print $type . ": ";
+            foreach ($rules as $rule) {
+                print $rule;
+            }
+            print "\n";
+        }
+    }
+
+    protected $decisionQueue = array();
+    protected $propagateIndex;
+    protected $decisionMap = array();
+    protected $branches = array();
+
+    protected function addDecision(Literal $l, $level)
+    {
+        if ($l->isWanted()) {
+            $this->decisionMap[$l->getPackageId()] = $level;
+        } else {
+            $this->decisionMap[$l->getPackageId()] = -$level;
+        }
+    }
+
+    protected function decisionsContain(Literal $l)
+    {
+        return (isset($this->decisionMap[$l->getPackageId()]) && (
+            $this->decisionMap[$l->getPackageId()] > 0 && $l->isWanted() ||
+            $this->decisionMap[$l->getPackageId()] < 0 && !$l->isWanted()
+        ));
+    }
+
+    protected function decisionsSatisfy(Literal $l)
+    {
+        return ($l->isWanted() && isset($this->decisionMap[$l->getPackageId()]) && $this->decisionMap[$l->getPackageId()] > 0) ||
+            (!$l->isWanted() && (!isset($this->decisionMap[$l->getPackageId()]) || $this->decisionMap[$l->getPackageId()] < 0));
+    }
+
+    protected function decisionsConflict(Literal $l)
+    {
+        return (isset($this->decisionMap[$l->getPackageId()]) && (
+            $this->decisionMap[$l->getPackageId()] > 0 && !$l->isWanted() ||
+            $this->decisionMap[$l->getPackageId()] < 0 && $l->isWanted()
+        ));
+    }
+
+    protected function decided(Package $p)
+    {
+        return isset($this->decisionMap[$p->getId()]);
+    }
+
+    protected function undecided(Package $p)
+    {
+        return !isset($this->decisionMap[$p->getId()]);
+    }
+
+    protected function decidedInstall(Package $p) {
+        return isset($this->decisionMap[$p->getId()]) && $this->decisionMap[$p->getId()] > 0;
+    }
+
+    protected function decidedRemove(Package $p) {
+        return isset($this->decisionMap[$p->getId()]) && $this->decisionMap[$p->getId()] < 0;
+    }
+
+    /**
+     * Makes a decision and propagates it to all rules.
+     *
+     * Evaluates each term affected by the decision (linked through watches)
+     * If we find unit rules we make new decisions based on them
+     *
+     * @return Rule|null A rule on conflict, otherwise null.
+     */
+    protected function propagate($level)
+    {
+        while ($this->propagateIndex < count($this->decisionQueue)) {
+            // 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 = $this->decisionQueue[$this->propagateIndex]->inverted();
+
+            $this->propagateIndex++;
+
+            // /* foreach rule where 'pkg' is now FALSE */
+            //for (rp = watches + pkg; *rp; rp = next_rp)
+            for ($rule = $this->watches[$literal->getId()]; $rule !== null; $rule = $nextRule) {
+                $nextRule = $rule->getNext($literal);
+
+                if ($rule->isDisabled()) {
+                    continue;
+                }
+
+                $otherWatch = $rule->getOtherWatch($literal);
+
+                if ($this->decisionsContain($otherWatch)) {
+                    continue;
+                }
+
+                $ruleLiterals = $rule->getLiterals();
+
+                if (sizeof($ruleLiterals) > 2) {
+                    foreach ($ruleLiterals as $ruleLiteral) {
+                        if (!$otherWatch->equals($ruleLiteral) &&
+                            !$this->decisionsConflict($ruleLiteral)) {
+
+
+                            if ($literal->equals($rule->getWatch1())) {
+                                $rule->setWatch1($ruleLiteral);
+                                $rule->setNext1($rule);
+                            } else {
+                                $rule->setWatch2($ruleLiteral);
+                                $rule->setNext2($rule);
+                            }
+
+                            $this->watches[$ruleLiteral->getId()] = $rule;
+                            continue 2;
+                        }
+                    }
+                }
+
+                // yay, we found a unit clause! try setting it to true
+                if ($this->decisionsConflict($otherWatch)) {
+                    return $rule;
+                }
+
+                $this->addDecision($otherWatch, $level);
+
+                $this->decisionQueue[] = $otherWatch;
+                $this->decisionQueueWhy[] = $rule;
+            }
+        }
+
+        return null;
+    }
+
+    private function setPropagateLearn($level, Literal $literal, $disableRules, Rule $rule)
+    {
+        return 0;
+    }
+
+    private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
+    {
+        // choose best package to install from decisionQueue
+        $literals = $this->policy->selectPreferedPackages($decisionQueue);
+
+        // if there are multiple candidates, then branch
+        if (count($literals) > 1) {
+            foreach ($literals as $i => $literal) {
+                if (0 !== $i) {
+                    $this->branches[] = array($literal, $level);
+                }
+            }
+        }
+
+        return $this->setPropagateLearn($level, $literals[0], $disableRules, $rule);
+    }
+
+    private function runSat($disableRules = true, $installRecommended = false)
+    {
+        $this->propagateIndex = 0;
+
+        //   /*
+        //    * here's the main loop:
+        //    * 1) propagate new decisions (only needed once)
+        //    * 2) fulfill jobs
+        //    * 3) try to keep installed packages
+        //    * 4) fulfill all unresolved rules
+        //    * 5) install recommended packages
+        //    * 6) minimalize solution if we had choices
+        //    * if we encounter a problem, we rewind to a safe level and restart
+        //    * with step 1
+        //    */
+
+        $decisionQueue = array();
+        $decisionSupplementQueue = array();
+        $disableRules = array();
+
+        $level = 1;
+        $systemLevel = $level + 1;
+        $minimizationsteps = 0;
+        $installedPos = 0;
+
+        $this->installedPackages = array_values($this->installed->getPackages());
+
+        while (true) {
+
+            $conflictRule = $this->propagate($level);
+            if ($conflictRule !== null) {
+//              if (analyze_unsolvable(solv, r, disablerules))
+                if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
+                    continue;
+                } else {
+                    return;
+                }
+            }
+
+            // handle job rules
+            if ($level < $systemLevel) {
+                $ruleIndex = 0;
+                foreach ($this->rules[self::TYPE_JOB] as $ruleIndex => $rule) {
+                    if ($rule->isEnabled()) {
+                        $decisionQueue = array();
+                        $noneSatisfied = true;
+
+                        foreach ($rule->getLiterals() as $literal) {
+                            if ($this->decisionsSatisfy($literal)) {
+                                $noneSatisfied = false;
+                                break;
+                            }
+                            $decisionQueue[] = $literal;
+                        }
+
+                        if ($noneSatisfied && count($decisionQueue)) {
+                            // prune all update packages until installed version
+                            // except for requested updates
+                            if (count($this->installed) != count($this->updateMap)) {
+                                $prunedQueue = array();
+                                foreach ($decisionQueue as $literal) {
+                                    if ($this->installed->contains($literal->getPackage())) {
+                                        $prunedQueue[] = $literal;
+                                        if (isset($this->updateMap[$literal->getPackageId()])) {
+                                            $prunedQueue = $decisionQueue;
+                                            break;
+                                        }
+                                    }
+                                }
+                                $decisionQueue = $prunedQueue;
+                            }
+                        }
+
+                        if ($noneSatisfied && count($decisionQueue)) {
+
+                            $oLevel = $level;
+                            $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
+
+                            if (0 === $level) {
+                                return;
+                            }
+                            if ($level <= $oLevel) {
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                $systemLevel = $level + 1;
+
+                // jobs left
+                if ($ruleIndex + 1 < count($this->rules[Solver::TYPE_JOB])) {
+                    continue;
+                }
+            }
+
+            // handle installed packages
+            if ($level < $systemLevel) {
+                // use two passes if any packages are being updated
+                // -> better user experience
+                for ($pass = (count($this->updateMap)) ? 0 : 1; $pass < 2; $pass++) {
+                    $passLevel = $level;
+                    for ($i = $installedPos, $n = 0; $n < count($this->installedPackages); $i++, $n++) {
+                        $repeat = false;
+
+                        if ($i == count($this->installedPackages)) {
+                            $i = 0;
+                        }
+                        $literal = new Literal($this->installedPackages[$i], true);
+
+                        if ($this->decisionsContain($literal)) {
+                            continue;
+                        }
+
+                        // only process updates in first pass
+                        if (0 === $pass && !isset($this->updateMap[$literal->getPackageId()])) {
+                            continue;
+                        }
+
+                        $rule = null;
+                        if (isset($this->rules[Solver::TYPE_UPDATE][$literal->getPackageId()])) {
+                            $rule = $this->rules[Solver::TYPE_UPDATE][$literal->getPackageId()];
+                        }
+
+                        if ((!$rule || $rule->isDisabled()) && isset($this->rules[Solver::TYPE_FEATURE][$literal->getPackageId()])) {
+                            $rule = $this->rules[Solver::TYPE_FEATURE][$literal->getPackageId()];
+                        }
+
+                        if (!$rule || $rule->isDisabled()) {
+                            continue;
+                        }
+
+                        $decisionQueue = array();
+                        if (!isset($this->noUpdate[$literal->getPackageId()]) && (
+                            $this->decidedRemove($literal->getPackage()) ||
+                            isset($this->updateMap[$literal->getPackageId()]) ||
+                            !$literal->equals($rule->getFirstLiteral())
+                        )) {
+                            foreach ($rule->getLiterals() as $ruleLiteral) {
+                                if ($this->decidedInstall($ruleLiteral->getPackage())) {
+                                    // already fulfilled
+                                    break;
+                                }
+                                if ($this->undecided($ruleLiteral->getPackage())) {
+                                    $decisionQueue[] = $ruleLiteral;
+                                }
+                            }
+                        }
+
+                        if (sizeof($decisionQueue)) {
+                            $oLevel = $level;
+                            $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
+
+                            if (0 === $level) {
+                                return;
+                            }
+
+                            if ($level <= $oLevel) {
+                                $repeat = true;
+                            }
+                        }
+
+                        // still undecided? keep package.
+                        if (!$repeat && $this->undecided($literal->getPackage())) {
+                            $oLevel = $level;
+                            if (isset($this->cleanDepsMap[$literal->getPackageId()])) {
+                                // clean deps removes package
+                                $level = $this->setPropagateLearn($level, $literal->invert(), $disableRules, null);
+                            } else {
+                                // ckeeping package
+                                $level = $this->setPropagateLearn($level, $literal, $disableRules, $rule);
+                            }
+
+
+                            if (0 === $level) {
+                                return;
+                            }
+
+                            if ($level <= $oLevel) {
+                                $repeat = true;
+                            }
+                        }
+
+                        if ($repeat) {
+                            if (1 === $level || $level < $passLevel) {
+                                // trouble
+                                break;
+                            }
+                            if ($level < $oLevel) {
+                                // redo all
+                                $n = 0;
+                            }
+
+                            // repeat
+                            $i--;
+                            $n--;
+                            continue;
+                        }
+                    }
+
+                    if ($n < count($this->installedPackages)) {
+                        $installedPos = $i; // retry this problem next time
+                        break;
+                    }
+
+                    $installedPos = 0;
+                }
+
+                $systemlevel = $level + 1;
+
+                if ($pass < 2) {
+                    // had trouble => retry
+                    continue;
+                }
+            }
+
+            if ($level < $systemLevel) {
+                $systemLevel = $level;
+            }
+
+            foreach ($this->rules as $ruleType => $rules) {
+                foreach ($rules as $rule) {
+                    if ($rule->isEnabled()) {
+                        $decisionQueue = array();
+                    }
+                }
+            }
+$this->printRules();
+//
+//       /*
+//        * decide
+//        */
+//       POOL_DEBUG(SAT_DEBUG_POLICY, "deciding unresolved rules\n");
+//       for (i = 1, n = 1; n < solv->nrules; i++, n++)
+//     {
+//       if (i == solv->nrules)
+//         i = 1;
+//       r = solv->rules + i;
+//       if (r->d < 0)     /* ignore disabled rules */
+//         continue;
+//       queue_empty(&dq);
+//       if (r->d == 0)
+//         {
+//           /* binary or unary rule */
+//           /* need two positive undecided literals */
+//           if (r->p < 0 || r->w2 <= 0)
+//         continue;
+//           if (solv->decisionmap[r->p] || solv->decisionmap[r->w2])
+//         continue;
+//           queue_push(&dq, r->p);
+//           queue_push(&dq, r->w2);
+//         }
+//       else
+//         {
+//           /* make sure that
+//                * all negative literals are installed
+//                * no positive literal is installed
+//            * i.e. the rule is not fulfilled and we
+//                * just need to decide on the positive literals
+//                */
+//           if (r->p < 0)
+//         {
+//           if (solv->decisionmap[-r->p] <= 0)
+//             continue;
+//         }
+//           else
+//         {
+//           if (solv->decisionmap[r->p] > 0)
+//             continue;
+//           if (solv->decisionmap[r->p] == 0)
+//             queue_push(&dq, r->p);
+//         }
+//           dp = pool->whatprovidesdata + r->d;
+//           while ((p = *dp++) != 0)
+//         {
+//           if (p < 0)
+//             {
+//               if (solv->decisionmap[-p] <= 0)
+//             break;
+//             }
+//           else
+//             {
+//               if (solv->decisionmap[p] > 0)
+//             break;
+//               if (solv->decisionmap[p] == 0)
+//             queue_push(&dq, p);
+//             }
+//         }
+//           if (p)
+//         continue;
+//         }
+//       IF_POOLDEBUG (SAT_DEBUG_PROPAGATE)
+//         {
+//           POOL_DEBUG(SAT_DEBUG_PROPAGATE, "unfulfilled ");
+//           solver_printruleclass(solv, SAT_DEBUG_PROPAGATE, r);
+//         }
+//       /* dq.count < 2 cannot happen as this means that
+//        * the rule is unit */
+//       assert(dq.count > 1);
+//
+//       olevel = level;
+//       level = selectandinstall(solv, level, &dq, disablerules, r - solv->rules);
+//       if (level == 0)
+//         {
+//           queue_free(&dq);
+//           queue_free(&dqs);
+//           return;
+//         }
+//       if (level < systemlevel || level == 1)
+//         break;      /* trouble */
+//       /* something changed, so look at all rules again */
+//       n = 0;
+//     }
+//
+//       if (n != solv->nrules)    /* ran into trouble, restart */
+//     continue;
+//
+//       /* at this point we have a consistent system. now do the extras... */
+//
+        }
+    }
+
+// void
+// solver_run_sat(Solver *solv, int disablerules, int doweak)
+// {
+//   Queue dq;     /* local decisionqueue */
+//   Queue dqs;        /* local decisionqueue for supplements */
+//   int systemlevel;
+//   int level, olevel;
+//   Rule *r;
+//   int i, j, n;
+//   Solvable *s;
+//   Pool *pool = solv->pool;
+//   Id p, *dp;
+//   int minimizationsteps;
+//   int installedpos = solv->installed ? solv->installed->start : 0;
+//
+//   IF_POOLDEBUG (SAT_DEBUG_RULE_CREATION)
+//     {
+//       POOL_DEBUG (SAT_DEBUG_RULE_CREATION, "number of rules: %d\n", solv->nrules);
+//       for (i = 1; i < solv->nrules; i++)
+//     solver_printruleclass(solv, SAT_DEBUG_RULE_CREATION, solv->rules + i);
+//     }
+//
+//   POOL_DEBUG(SAT_DEBUG_SOLVER, "initial decisions: %d\n", solv->decisionq.count);
+//
+//   IF_POOLDEBUG (SAT_DEBUG_SCHUBI)
+//     solver_printdecisions(solv);
+//
+//   /* start SAT algorithm */
+//   level = 1;
+//   systemlevel = level + 1;
+//   POOL_DEBUG(SAT_DEBUG_SOLVER, "solving...\n");
+//
+//   queue_init(&dq);
+//   queue_init(&dqs);
+//
+//   /*
+//    * here's the main loop:
+//    * 1) propagate new decisions (only needed once)
+//    * 2) fulfill jobs
+//    * 3) try to keep installed packages
+//    * 4) fulfill all unresolved rules
+//    * 5) install recommended packages
+//    * 6) minimalize solution if we had choices
+//    * if we encounter a problem, we rewind to a safe level and restart
+//    * with step 1
+//    */
+//
+//   minimizationsteps = 0;
+//   for (;;)
+//     {
+//       /*
+//        * initial propagation of the assertions
+//        */
+//       if (level == 1)
+//     {
+//       POOL_DEBUG(SAT_DEBUG_PROPAGATE, "propagating (propagate_index: %d;  size decisionq: %d)...\n", solv->propagate_index, solv->decisionq.count);
+//       if ((r = propagate(solv, level)) != 0)
+//         {
+//           if (analyze_unsolvable(solv, r, disablerules))
+//         continue;
+//           queue_free(&dq);
+//           queue_free(&dqs);
+//           return;
+//         }
+//     }
+//
+//       /*
+//        * resolve jobs first
+//        */
+//      if (level < systemlevel)
+//     {
+//       POOL_DEBUG(SAT_DEBUG_SOLVER, "resolving job rules\n");
+//       for (i = solv->jobrules, r = solv->rules + i; i < solv->jobrules_end; i++, r++)
+//         {
+//           Id l;
+//           if (r->d < 0)     /* ignore disabled rules */
+//         continue;
+//           queue_empty(&dq);
+//           FOR_RULELITERALS(l, dp, r)
+//         {
+//           if (l < 0)
+//             {
+//               if (solv->decisionmap[-l] <= 0)
+//             break;
+//             }
+//           else
+//             {
+//               if (solv->decisionmap[l] > 0)
+//             break;
+//               if (solv->decisionmap[l] == 0)
+//             queue_push(&dq, l);
+//             }
+//         }
+//           if (l || !dq.count)
+//         continue;
+//           /* prune to installed if not updating */
+//           if (dq.count > 1 && solv->installed && !solv->updatemap_all)
+//         {
+//           int j, k;
+//           for (j = k = 0; j < dq.count; j++)
+//             {
+//               Solvable *s = pool->solvables + dq.elements[j];
+//               if (s->repo == solv->installed)
+//             {
+//               dq.elements[k++] = dq.elements[j];
+//               if (solv->updatemap.size && MAPTST(&solv->updatemap, dq.elements[j] - solv->installed->start))
+//                 {
+//                   k = 0;    /* package wants to be updated, do not prune */
+//                   break;
+//                 }
+//             }
+//             }
+//           if (k)
+//             dq.count = k;
+//         }
+//           olevel = level;
+//           level = selectandinstall(solv, level, &dq, disablerules, i);
+//           if (level == 0)
+//         {
+//           queue_free(&dq);
+//           queue_free(&dqs);
+//           return;
+//         }
+//           if (level <= olevel)
+//         break;
+//         }
+//       systemlevel = level + 1;
+//       if (i < solv->jobrules_end)
+//         continue;
+//     }
+//
+//
+//       /*
+//        * installed packages
+//        */
+//       if (level < systemlevel && solv->installed && solv->installed->nsolvables && !solv->installed->disabled)
+//     {
+//       Repo *installed = solv->installed;
+//       int pass;
+//
+//       POOL_DEBUG(SAT_DEBUG_SOLVER, "resolving installed packages\n");
+//       /* we use two passes if we need to update packages
+//            * to create a better user experience */
+//       for (pass = solv->updatemap.size ? 0 : 1; pass < 2; pass++)
+//         {
+//           int passlevel = level;
+//           /* start with installedpos, the position that gave us problems last time */
+//           for (i = installedpos, n = installed->start; n < installed->end; i++, n++)
+//         {
+//           Rule *rr;
+//           Id d;
+//
+//           if (i == installed->end)
+//             i = installed->start;
+//           s = pool->solvables + i;
+//           if (s->repo != installed)
+//             continue;
+//
+//           if (solv->decisionmap[i] > 0)
+//             continue;
+//           if (!pass && solv->updatemap.size && !MAPTST(&solv->updatemap, i - installed->start))
+//             continue;       /* updates first */
+//           r = solv->rules + solv->updaterules + (i - installed->start);
+//           rr = r;
+//           if (!rr->p || rr->d < 0)  /* disabled -> look at feature rule */
+//             rr -= solv->installed->end - solv->installed->start;
+//           if (!rr->p)       /* identical to update rule? */
+//             rr = r;
+//           if (!rr->p)
+//             continue;       /* orpaned package */
+//
+//           /* XXX: noupdate check is probably no longer needed, as all jobs should
+//            * already be satisfied */
+//           /* Actually we currently still need it because of erase jobs */
+//           /* if noupdate is set we do not look at update candidates */
+//           queue_empty(&dq);
+//           if (!MAPTST(&solv->noupdate, i - installed->start) && (solv->decisionmap[i] < 0 || solv->updatemap_all || (solv->updatemap.size && MAPTST(&solv->updatemap, i - installed->start)) || rr->p != i))
+//             {
+//               if (solv->noobsoletes.size && solv->multiversionupdaters
+//                  && (d = solv->multiversionupdaters[i - installed->start]) != 0)
+//             {
+//               /* special multiversion handling, make sure best version is chosen */
+//               queue_push(&dq, i);
+//               while ((p = pool->whatprovidesdata[d++]) != 0)
+//                 if (solv->decisionmap[p] >= 0)
+//                   queue_push(&dq, p);
+//               policy_filter_unwanted(solv, &dq, POLICY_MODE_CHOOSE);
+//               p = dq.elements[0];
+//               if (p != i && solv->decisionmap[p] == 0)
+//                 {
+//                   rr = solv->rules + solv->featurerules + (i - solv->installed->start);
+//                   if (!rr->p)       /* update rule == feature rule? */
+//                 rr = rr - solv->featurerules + solv->updaterules;
+//                   dq.count = 1;
+//                 }
+//               else
+//                 dq.count = 0;
+//             }
+//               else
+//             {
+//               /* update to best package */
+//               FOR_RULELITERALS(p, dp, rr)
+//                 {
+//                   if (solv->decisionmap[p] > 0)
+//                 {
+//                   dq.count = 0;     /* already fulfilled */
+//                   break;
+//                 }
+//                   if (!solv->decisionmap[p])
+//                 queue_push(&dq, p);
+//                 }
+//             }
+//             }
+//           /* install best version */
+//           if (dq.count)
+//             {
+//               olevel = level;
+//               level = selectandinstall(solv, level, &dq, disablerules, rr - solv->rules);
+//               if (level == 0)
+//             {
+//               queue_free(&dq);
+//               queue_free(&dqs);
+//               return;
+//             }
+//               if (level <= olevel)
+//             {
+//               if (level == 1 || level < passlevel)
+//                 break;  /* trouble */
+//               if (level < olevel)
+//                 n = installed->start;   /* redo all */
+//               i--;
+//               n--;
+//               continue;
+//             }
+//             }
+//           /* if still undecided keep package */
+//           if (solv->decisionmap[i] == 0)
+//             {
+//               olevel = level;
+//               if (solv->cleandepsmap.size && MAPTST(&solv->cleandepsmap, i - installed->start))
+//             {
+//               POOL_DEBUG(SAT_DEBUG_POLICY, "cleandeps erasing %s\n", solvid2str(pool, i));
+//               level = setpropagatelearn(solv, level, -i, disablerules, 0);
+//             }
+//               else
+//             {
+//               POOL_DEBUG(SAT_DEBUG_POLICY, "keeping %s\n", solvid2str(pool, i));
+//               level = setpropagatelearn(solv, level, i, disablerules, r - solv->rules);
+//             }
+//               if (level == 0)
+//             {
+//               queue_free(&dq);
+//               queue_free(&dqs);
+//               return;
+//             }
+//               if (level <= olevel)
+//             {
+//               if (level == 1 || level < passlevel)
+//                 break;  /* trouble */
+//               if (level < olevel)
+//                 n = installed->start;   /* redo all */
+//               i--;
+//               n--;
+//               continue; /* retry with learnt rule */
+//             }
+//             }
+//         }
+//           if (n < installed->end)
+//         {
+//           installedpos = i; /* retry problem solvable next time */
+//           break;        /* ran into trouble */
+//         }
+//           installedpos = installed->start;  /* reset installedpos */
+//         }
+//       systemlevel = level + 1;
+//       if (pass < 2)
+//         continue;       /* had trouble, retry */
+//     }
+//
+//       if (level < systemlevel)
+//         systemlevel = level;
+//
+//       /*
+//        * decide
+//        */
+//       POOL_DEBUG(SAT_DEBUG_POLICY, "deciding unresolved rules\n");
+//       for (i = 1, n = 1; n < solv->nrules; i++, n++)
+//     {
+//       if (i == solv->nrules)
+//         i = 1;
+//       r = solv->rules + i;
+//       if (r->d < 0)     /* ignore disabled rules */
+//         continue;
+//       queue_empty(&dq);
+//       if (r->d == 0)
+//         {
+//           /* binary or unary rule */
+//           /* need two positive undecided literals */
+//           if (r->p < 0 || r->w2 <= 0)
+//         continue;
+//           if (solv->decisionmap[r->p] || solv->decisionmap[r->w2])
+//         continue;
+//           queue_push(&dq, r->p);
+//           queue_push(&dq, r->w2);
+//         }
+//       else
+//         {
+//           /* make sure that
+//                * all negative literals are installed
+//                * no positive literal is installed
+//            * i.e. the rule is not fulfilled and we
+//                * just need to decide on the positive literals
+//                */
+//           if (r->p < 0)
+//         {
+//           if (solv->decisionmap[-r->p] <= 0)
+//             continue;
+//         }
+//           else
+//         {
+//           if (solv->decisionmap[r->p] > 0)
+//             continue;
+//           if (solv->decisionmap[r->p] == 0)
+//             queue_push(&dq, r->p);
+//         }
+//           dp = pool->whatprovidesdata + r->d;
+//           while ((p = *dp++) != 0)
+//         {
+//           if (p < 0)
+//             {
+//               if (solv->decisionmap[-p] <= 0)
+//             break;
+//             }
+//           else
+//             {
+//               if (solv->decisionmap[p] > 0)
+//             break;
+//               if (solv->decisionmap[p] == 0)
+//             queue_push(&dq, p);
+//             }
+//         }
+//           if (p)
+//         continue;
+//         }
+//       IF_POOLDEBUG (SAT_DEBUG_PROPAGATE)
+//         {
+//           POOL_DEBUG(SAT_DEBUG_PROPAGATE, "unfulfilled ");
+//           solver_printruleclass(solv, SAT_DEBUG_PROPAGATE, r);
+//         }
+//       /* dq.count < 2 cannot happen as this means that
+//        * the rule is unit */
+//       assert(dq.count > 1);
+//
+//       olevel = level;
+//       level = selectandinstall(solv, level, &dq, disablerules, r - solv->rules);
+//       if (level == 0)
+//         {
+//           queue_free(&dq);
+//           queue_free(&dqs);
+//           return;
+//         }
+//       if (level < systemlevel || level == 1)
+//         break;      /* trouble */
+//       /* something changed, so look at all rules again */
+//       n = 0;
+//     }
+//
+//       if (n != solv->nrules)    /* ran into trouble, restart */
+//     continue;
+//
+//       /* at this point we have a consistent system. now do the extras... */
+//
+//       if (doweak)
+//     {
+//       int qcount;
+//
+//       POOL_DEBUG(SAT_DEBUG_POLICY, "installing recommended packages\n");
+//       queue_empty(&dq); /* recommended packages */
+//       queue_empty(&dqs);    /* supplemented packages */
+//       for (i = 1; i < pool->nsolvables; i++)
+//         {
+//           if (solv->decisionmap[i] < 0)
+//         continue;
+//           if (solv->decisionmap[i] > 0)
+//         {
+//           /* installed, check for recommends */
+//           Id *recp, rec, pp, p;
+//           s = pool->solvables + i;
+//           if (solv->ignorealreadyrecommended && s->repo == solv->installed)
+//             continue;
+//           /* XXX need to special case AND ? */
+//           if (s->recommends)
+//             {
+//               recp = s->repo->idarraydata + s->recommends;
+//               while ((rec = *recp++) != 0)
+//             {
+//               qcount = dq.count;
+//               FOR_PROVIDES(p, pp, rec)
+//                 {
+//                   if (solv->decisionmap[p] > 0)
+//                 {
+//                   dq.count = qcount;
+//                   break;
+//                 }
+//                   else if (solv->decisionmap[p] == 0)
+//                 {
+//                   queue_pushunique(&dq, p);
+//                 }
+//                 }
+//             }
+//             }
+//         }
+//           else
+//         {
+//           s = pool->solvables + i;
+//           if (!s->supplements)
+//             continue;
+//           if (!pool_installable(pool, s))
+//             continue;
+//           if (!solver_is_supplementing(solv, s))
+//             continue;
+//           queue_push(&dqs, i);
+//         }
+//         }
+//
+//       /* filter out all packages obsoleted by installed packages */
+//       /* this is no longer needed if we have reverse obsoletes */
+//           if ((dqs.count || dq.count) && solv->installed)
+//         {
+//           Map obsmap;
+//           Id obs, *obsp, po, ppo;
+//
+//           map_init(&obsmap, pool->nsolvables);
+//           for (p = solv->installed->start; p < solv->installed->end; p++)
+//         {
+//           s = pool->solvables + p;
+//           if (s->repo != solv->installed || !s->obsoletes)
+//             continue;
+//           if (solv->decisionmap[p] <= 0)
+//             continue;
+//           if (solv->noobsoletes.size && MAPTST(&solv->noobsoletes, p))
+//             continue;
+//           obsp = s->repo->idarraydata + s->obsoletes;
+//           /* foreach obsoletes */
+//           while ((obs = *obsp++) != 0)
+//             FOR_PROVIDES(po, ppo, obs)
+//               MAPSET(&obsmap, po);
+//         }
+//           for (i = j = 0; i < dqs.count; i++)
+//         if (!MAPTST(&obsmap, dqs.elements[i]))
+//           dqs.elements[j++] = dqs.elements[i];
+//           dqs.count = j;
+//           for (i = j = 0; i < dq.count; i++)
+//         if (!MAPTST(&obsmap, dq.elements[i]))
+//           dq.elements[j++] = dq.elements[i];
+//           dq.count = j;
+//           map_free(&obsmap);
+//         }
+//
+//           /* filter out all already supplemented packages if requested */
+//           if (solv->ignorealreadyrecommended && dqs.count)
+//         {
+//           /* turn off all new packages */
+//           for (i = 0; i < solv->decisionq.count; i++)
+//         {
+//           p = solv->decisionq.elements[i];
+//           if (p < 0)
+//             continue;
+//           s = pool->solvables + p;
+//           if (s->repo && s->repo != solv->installed)
+//             solv->decisionmap[p] = -solv->decisionmap[p];
+//         }
+//           /* filter out old supplements */
+//           for (i = j = 0; i < dqs.count; i++)
+//         {
+//           p = dqs.elements[i];
+//           s = pool->solvables + p;
+//           if (!s->supplements)
+//             continue;
+//           if (!solver_is_supplementing(solv, s))
+//             dqs.elements[j++] = p;
+//         }
+//           dqs.count = j;
+//           /* undo turning off */
+//           for (i = 0; i < solv->decisionq.count; i++)
+//         {
+//           p = solv->decisionq.elements[i];
+//           if (p < 0)
+//             continue;
+//           s = pool->solvables + p;
+//           if (s->repo && s->repo != solv->installed)
+//             solv->decisionmap[p] = -solv->decisionmap[p];
+//         }
+//         }
+//
+//       /* multiversion doesn't mix well with supplements.
+//        * filter supplemented packages where we already decided
+//        * to install a different version (see bnc#501088) */
+//           if (dqs.count && solv->noobsoletes.size)
+//         {
+//           for (i = j = 0; i < dqs.count; i++)
+//         {
+//           p = dqs.elements[i];
+//           if (MAPTST(&solv->noobsoletes, p))
+//             {
+//               Id p2, pp2;
+//               s = pool->solvables + p;
+//               FOR_PROVIDES(p2, pp2, s->name)
+//             if (solv->decisionmap[p2] > 0 && pool->solvables[p2].name == s->name)
+//               break;
+//               if (p2)
+//             continue;   /* ignore this package */
+//             }
+//           dqs.elements[j++] = p;
+//         }
+//           dqs.count = j;
+//         }
+//
+//           /* make dq contain both recommended and supplemented pkgs */
+//       if (dqs.count)
+//         {
+//           for (i = 0; i < dqs.count; i++)
+//         queue_pushunique(&dq, dqs.elements[i]);
+//         }
+//
+//       if (dq.count)
+//         {
+//           Map dqmap;
+//           int decisioncount = solv->decisionq.count;
+//
+//           if (dq.count == 1)
+//         {
+//           /* simple case, just one package. no need to choose  */
+//           p = dq.elements[0];
+//           if (dqs.count)
+//             POOL_DEBUG(SAT_DEBUG_POLICY, "installing supplemented %s\n", solvid2str(pool, p));
+//           else
+//             POOL_DEBUG(SAT_DEBUG_POLICY, "installing recommended %s\n", solvid2str(pool, p));
+//           queue_push(&solv->recommendations, p);
+//           level = setpropagatelearn(solv, level, p, 0, 0);
+//           continue; /* back to main loop */
+//         }
+//
+//           /* filter packages, this gives us the best versions */
+//           policy_filter_unwanted(solv, &dq, POLICY_MODE_RECOMMEND);
+//
+//           /* create map of result */
+//           map_init(&dqmap, pool->nsolvables);
+//           for (i = 0; i < dq.count; i++)
+//         MAPSET(&dqmap, dq.elements[i]);
+//
+//           /* install all supplemented packages */
+//           for (i = 0; i < dqs.count; i++)
+//         {
+//           p = dqs.elements[i];
+//           if (solv->decisionmap[p] || !MAPTST(&dqmap, p))
+//             continue;
+//           POOL_DEBUG(SAT_DEBUG_POLICY, "installing supplemented %s\n", solvid2str(pool, p));
+//           queue_push(&solv->recommendations, p);
+//           olevel = level;
+//           level = setpropagatelearn(solv, level, p, 0, 0);
+//           if (level <= olevel)
+//             break;
+//         }
+//           if (i < dqs.count || solv->decisionq.count < decisioncount)
+//         {
+//           map_free(&dqmap);
+//           continue;
+//         }
+//
+//           /* install all recommended packages */
+//           /* more work as we want to created branches if multiple
+//                * choices are valid */
+//           for (i = 0; i < decisioncount; i++)
+//         {
+//           Id rec, *recp, pp;
+//           p = solv->decisionq.elements[i];
+//           if (p < 0)
+//             continue;
+//           s = pool->solvables + p;
+//           if (!s->repo || (solv->ignorealreadyrecommended && s->repo == solv->installed))
+//             continue;
+//           if (!s->recommends)
+//             continue;
+//           recp = s->repo->idarraydata + s->recommends;
+//           while ((rec = *recp++) != 0)
+//             {
+//               queue_empty(&dq);
+//               FOR_PROVIDES(p, pp, rec)
+//             {
+//               if (solv->decisionmap[p] > 0)
+//                 {
+//                   dq.count = 0;
+//                   break;
+//                 }
+//               else if (solv->decisionmap[p] == 0 && MAPTST(&dqmap, p))
+//                 queue_pushunique(&dq, p);
+//             }
+//               if (!dq.count)
+//             continue;
+//               if (dq.count > 1)
+//             {
+//               /* multiple candidates, open a branch */
+//               for (i = 1; i < dq.count; i++)
+//                 queue_push(&solv->branches, dq.elements[i]);
+//               queue_push(&solv->branches, -level);
+//             }
+//               p = dq.elements[0];
+//               POOL_DEBUG(SAT_DEBUG_POLICY, "installing recommended %s\n", solvid2str(pool, p));
+//               queue_push(&solv->recommendations, p);
+//               olevel = level;
+//               level = setpropagatelearn(solv, level, p, 0, 0);
+//               if (level <= olevel || solv->decisionq.count < decisioncount)
+//             break;  /* we had to revert some decisions */
+//             }
+//           if (rec)
+//             break;  /* had a problem above, quit loop */
+//         }
+//           map_free(&dqmap);
+//
+//           continue;     /* back to main loop so that all deps are checked */
+//         }
+//     }
+//
+//      if (solv->dupmap_all && solv->installed)
+//     {
+//       int installedone = 0;
+//
+//       /* let's see if we can install some unsupported package */
+//       POOL_DEBUG(SAT_DEBUG_SOLVER, "deciding orphaned packages\n");
+//       for (i = 0; i < solv->orphaned.count; i++)
+//         {
+//           p = solv->orphaned.elements[i];
+//           if (solv->decisionmap[p])
+//         continue;   /* already decided */
+//           olevel = level;
+//           if (solv->droporphanedmap_all)
+//         continue;
+//           if (solv->droporphanedmap.size && MAPTST(&solv->droporphanedmap, p - solv->installed->start))
+//         continue;
+//           POOL_DEBUG(SAT_DEBUG_SOLVER, "keeping orphaned %s\n", solvid2str(pool, p));
+//           level = setpropagatelearn(solv, level, p, 0, 0);
+//           installedone = 1;
+//           if (level < olevel)
+//         break;
+//         }
+//       if (installedone || i < solv->orphaned.count)
+//         continue;       /* back to main loop */
+//       for (i = 0; i < solv->orphaned.count; i++)
+//         {
+//           p = solv->orphaned.elements[i];
+//           if (solv->decisionmap[p])
+//         continue;   /* already decided */
+//           POOL_DEBUG(SAT_DEBUG_SOLVER, "removing orphaned %s\n", solvid2str(pool, p));
+//           olevel = level;
+//           level = setpropagatelearn(solv, level, -p, 0, 0);
+//           if (level < olevel)
+//         break;
+//         }
+//       if (i < solv->orphaned.count)
+//         continue;       /* back to main loop */
+//     }
+//
+//      if (solv->solution_callback)
+//     {
+//       solv->solution_callback(solv, solv->solution_callback_data);
+//       if (solv->branches.count)
+//         {
+//           int i = solv->branches.count - 1;
+//           int l = -solv->branches.elements[i];
+//           Id why;
+//
+//           for (; i > 0; i--)
+//         if (solv->branches.elements[i - 1] < 0)
+//           break;
+//           p = solv->branches.elements[i];
+//           POOL_DEBUG(SAT_DEBUG_SOLVER, "branching with %s\n", solvid2str(pool, p));
+//           queue_empty(&dq);
+//           for (j = i + 1; j < solv->branches.count; j++)
+//         queue_push(&dq, solv->branches.elements[j]);
+//           solv->branches.count = i;
+//           level = l;
+//           revert(solv, level);
+//           if (dq.count > 1)
+//             for (j = 0; j < dq.count; j++)
+//           queue_push(&solv->branches, dq.elements[j]);
+//           olevel = level;
+//           why = -solv->decisionq_why.elements[solv->decisionq_why.count];
+//           assert(why >= 0);
+//           level = setpropagatelearn(solv, level, p, disablerules, why);
+//           if (level == 0)
+//         {
+//           queue_free(&dq);
+//           queue_free(&dqs);
+//           return;
+//         }
+//           continue;
+//         }
+//       /* all branches done, we're finally finished */
+//       break;
+//     }
+//
+//       /* minimization step */
+//      if (solv->branches.count)
+//     {
+//       int l = 0, lasti = -1, lastl = -1;
+//       Id why;
+//
+//       p = 0;
+//       for (i = solv->branches.count - 1; i >= 0; i--)
+//         {
+//           p = solv->branches.elements[i];
+//           if (p < 0)
+//         l = -p;
+//           else if (p > 0 && solv->decisionmap[p] > l + 1)
+//         {
+//           lasti = i;
+//           lastl = l;
+//         }
+//         }
+//       if (lasti >= 0)
+//         {
+//           /* kill old solvable so that we do not loop */
+//           p = solv->branches.elements[lasti];
+//           solv->branches.elements[lasti] = 0;
+//           POOL_DEBUG(SAT_DEBUG_SOLVER, "minimizing %d -> %d with %s\n", solv->decisionmap[p], lastl, solvid2str(pool, p));
+//           minimizationsteps++;
+//
+//           level = lastl;
+//           revert(solv, level);
+//           why = -solv->decisionq_why.elements[solv->decisionq_why.count];
+//           assert(why >= 0);
+//           olevel = level;
+//           level = setpropagatelearn(solv, level, p, disablerules, why);
+//           if (level == 0)
+//         {
+//           queue_free(&dq);
+//           queue_free(&dqs);
+//           return;
+//         }
+//           continue;     /* back to main loop */
+//         }
+//     }
+//       /* no minimization found, we're finally finished! */
+//       break;
+//     }
+//
+//   POOL_DEBUG(SAT_DEBUG_STATS, "solver statistics: %d learned rules, %d unsolvable, %d minimization steps\n", solv->stats_learned, solv->stats_unsolvable, minimizationsteps);
+//
+//   POOL_DEBUG(SAT_DEBUG_STATS, "done solving.\n\n");
+//   queue_free(&dq);
+//   queue_free(&dqs);
+// #if 0
+//   solver_printdecisionq(solv, SAT_DEBUG_RESULT);
+// #endif
+// }
+}

+ 26 - 0
tests/Composer/Test/DependencyResolver/ArrayRepositoryTest.php

@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * 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\ArrayRepository;
+use Composer\DependencyResolver\MemoryPackage;
+
+class ArrayRepositoryTest extends \PHPUnit_Framework_TestCase
+{
+    public function testAddLiteral()
+    {
+        $repo = new ArrayRepository;
+        $repo->addPackage(new MemoryPackage('foo', '1'));
+
+        $this->assertEquals(1, count($repo));
+    }
+}

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

@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * 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\DependencyResolver\MemoryPackage;
+
+class SolvableTest extends \PHPUnit_Framework_TestCase
+{
+    public function testSolvable()
+    {
+        $literal = new Literal(new MemoryPackage('foo', '1'), true);
+    }
+}

+ 29 - 0
tests/Composer/Test/DependencyResolver/PackageTest.php

@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * 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\MemoryPackage;
+
+class PackageTest extends \PHPUnit_Framework_TestCase
+{
+    public function testPackage()
+    {
+        $package = new MemoryPackage('foo', '1', 'beta', 21);
+
+        $this->assertEquals('foo', $package->getName());
+        $this->assertEquals('1', $package->getVersion());
+        $this->assertEquals('beta', $package->getReleaseType());
+        $this->assertEquals(21, $package->getId());
+
+        $this->assertEquals('foo-beta-1', (string) $package);
+    }
+}

+ 32 - 0
tests/Composer/Test/DependencyResolver/PoolTest.php

@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * 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\Pool;
+use Composer\DependencyResolver\ArrayRepository;
+use Composer\DependencyResolver\MemoryPackage;
+
+class PoolTest extends \PHPUnit_Framework_TestCase
+{
+    public function testPool()
+    {
+        $pool = new Pool;
+        $repo = new ArrayRepository;
+        $package = new MemoryPackage('foo', '1');
+
+        $repo->addPackage($package);
+        $pool->addRepository($repo);
+
+        $this->assertEquals(array($package), $pool->whatProvides('foo'));
+        $this->assertEquals(array($package), $pool->whatProvides('foo'));
+    }
+}

+ 48 - 0
tests/Composer/Test/DependencyResolver/RequestTest.php

@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * 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\Request;
+use Composer\DependencyResolver\Pool;
+use Composer\DependencyResolver\ArrayRepository;
+use Composer\DependencyResolver\Literal;
+use Composer\DependencyResolver\MemoryPackage;
+
+class RequestTest extends \PHPUnit_Framework_TestCase
+{
+    public function testRequestInstallAndRemove()
+    {
+        $pool = new Pool;
+        $repo = new ArrayRepository;
+        $foo = new MemoryPackage('foo', '1');
+        $bar = new MemoryPackage('bar', '1');
+        $foobar = new MemoryPackage('foobar', '1');
+
+        $repo->addPackage($foo);
+        $repo->addPackage($bar);
+        $repo->addPackage($foobar);
+        $pool->addRepository($repo);
+
+        $request = new Request($pool);
+        $request->install('foo');
+        $request->install('bar');
+        $request->remove('foobar');
+
+        $this->assertEquals(
+            array(
+                array('packages' => array($foo), 'cmd' => 'install', 'packageName' => 'foo'),
+                array('packages' => array($bar), 'cmd' => 'install', 'packageName' => 'bar'),
+                array('packages' => array($foobar), 'cmd' => 'remove', 'packageName' => 'foobar'),
+            ),
+            $request->getJobs());
+    }
+}

+ 61 - 0
tests/Composer/Test/DependencyResolver/SolverTest.php

@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * 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\ArrayRepository;
+use Composer\DependencyResolver\DefaultPolicy;
+use Composer\DependencyResolver\Pool;
+use Composer\DependencyResolver\Request;
+use Composer\DependencyResolver\MemoryPackage;
+use Composer\DependencyResolver\PackageRelation;
+use Composer\DependencyResolver\Solver;
+use Composer\DependencyResolver\RelationConstraint\VersionConstraint;
+
+class SolverTest extends \PHPUnit_Framework_TestCase
+{
+    public function testSolver()
+    {
+        $this->markTestIncomplete('incomplete');
+        return;
+
+        $pool = new Pool;
+
+        $repoInstalled = new ArrayRepository;
+        $repoInstalled->addPackage(new MemoryPackage('old', '1.0'));
+        $repoInstalled->addPackage(new MemoryPackage('C', '1.0'));
+
+        $repo = new ArrayRepository;
+        $repo->addPackage($packageA = new MemoryPackage('A', '2.0'));
+        $repo->addPackage($packageB = new MemoryPackage('B', '1.0'));
+        $repo->addPackage($newPackageB = new MemoryPackage('B', '1.1'));
+        $repo->addPackage($packageC = new MemoryPackage('C', '1.0'));
+        $repo->addPackage($oldPackage = new MemoryPackage('old', '1.0'));
+        $packageA->setRequires(array(new PackageRelation('A', 'B', new VersionConstraint('<', '1.1'), 'requires')));
+
+        $pool->addRepository($repoInstalled);
+        $pool->addRepository($repo);
+
+        $request = new Request($pool);
+        $request->install('A');
+        $request->update('C');
+        $request->remove('old');
+
+        $policy = new DefaultPolicy;
+        $solver = new Solver($policy, $pool, $repoInstalled);
+        $result = $solver->solve($request);
+
+        $this->assertTrue($result, 'Request could be solved');
+
+        //$transaction = $solver->getTransaction();
+        // assert ...
+    }
+}

+ 20 - 0
tests/bootstrap.php

@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+spl_autoload_register(function($class)
+{
+    $file = __DIR__.'/../src/'.strtr($class, '\\', '/').'.php';
+    if (file_exists($file)) {
+        require $file;
+        return true;
+    }
+});
+