diff --git a/PORTING_INFO b/PORTING_INFO new file mode 100644 index 000000000..54d86b25e --- /dev/null +++ b/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: + * Updates: p > 0, d > 0 (A|B1|B2|...) d: + * 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 diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 000000000..7ba5d7997 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/Composer/ + + + + + + ./src/Composer/ + + + diff --git a/src/Composer/DependencyResolver/ArrayRepository.php b/src/Composer/DependencyResolver/ArrayRepository.php new file mode 100644 index 000000000..0bf419a64 --- /dev/null +++ b/src/Composer/DependencyResolver/ArrayRepository.php @@ -0,0 +1,62 @@ + + * + * 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 + */ +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); + } +} diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php new file mode 100644 index 000000000..2da720b2c --- /dev/null +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -0,0 +1,63 @@ + + * + * 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 + */ +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]); + } +} diff --git a/src/Composer/DependencyResolver/Literal.php b/src/Composer/DependencyResolver/Literal.php new file mode 100644 index 000000000..cef358bd0 --- /dev/null +++ b/src/Composer/DependencyResolver/Literal.php @@ -0,0 +1,61 @@ + + * + * 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 + */ +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(); + } +} diff --git a/src/Composer/DependencyResolver/MemoryPackage.php b/src/Composer/DependencyResolver/MemoryPackage.php new file mode 100644 index 000000000..28496a8f3 --- /dev/null +++ b/src/Composer/DependencyResolver/MemoryPackage.php @@ -0,0 +1,190 @@ + + * + * 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 + */ +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; + } +} diff --git a/src/Composer/DependencyResolver/Package.php b/src/Composer/DependencyResolver/Package.php new file mode 100644 index 000000000..7b3efab04 --- /dev/null +++ b/src/Composer/DependencyResolver/Package.php @@ -0,0 +1,189 @@ + + * + * 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 + */ +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(); + } +} diff --git a/src/Composer/DependencyResolver/PackageRelation.php b/src/Composer/DependencyResolver/PackageRelation.php new file mode 100644 index 000000000..e2f9aa8a6 --- /dev/null +++ b/src/Composer/DependencyResolver/PackageRelation.php @@ -0,0 +1,53 @@ + + * + * 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 + */ +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.')'; + } +} diff --git a/src/Composer/DependencyResolver/PolicyInterface.php b/src/Composer/DependencyResolver/PolicyInterface.php new file mode 100644 index 000000000..e0add1413 --- /dev/null +++ b/src/Composer/DependencyResolver/PolicyInterface.php @@ -0,0 +1,25 @@ + + * + * 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 + */ +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); +} diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php new file mode 100644 index 000000000..0ef62aa98 --- /dev/null +++ b/src/Composer/DependencyResolver/Pool.php @@ -0,0 +1,79 @@ + + * + * 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 + */ +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; + } +} diff --git a/src/Composer/DependencyResolver/RelationConstraint/RelationConstraintInterface.php b/src/Composer/DependencyResolver/RelationConstraint/RelationConstraintInterface.php new file mode 100644 index 000000000..49f0797dd --- /dev/null +++ b/src/Composer/DependencyResolver/RelationConstraint/RelationConstraintInterface.php @@ -0,0 +1,21 @@ + + * + * 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 + */ +interface RelationConstraintInterface +{ + function matches($releaseType, $version); + function __toString(); +} diff --git a/src/Composer/DependencyResolver/RelationConstraint/VersionConstraint.php b/src/Composer/DependencyResolver/RelationConstraint/VersionConstraint.php new file mode 100644 index 000000000..040eba766 --- /dev/null +++ b/src/Composer/DependencyResolver/RelationConstraint/VersionConstraint.php @@ -0,0 +1,47 @@ + + * + * 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 + */ +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; + } +} diff --git a/src/Composer/DependencyResolver/RepositoryInterface.php b/src/Composer/DependencyResolver/RepositoryInterface.php new file mode 100644 index 000000000..a131e9135 --- /dev/null +++ b/src/Composer/DependencyResolver/RepositoryInterface.php @@ -0,0 +1,21 @@ + + * + * 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 + */ +interface RepositoryInterface extends \Countable +{ + function getPackages(); + function contains(Package $package); +} diff --git a/src/Composer/DependencyResolver/Request.php b/src/Composer/DependencyResolver/Request.php new file mode 100644 index 000000000..4a8d91faf --- /dev/null +++ b/src/Composer/DependencyResolver/Request.php @@ -0,0 +1,57 @@ + + * + * 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 + */ +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; + } +} diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php new file mode 100644 index 000000000..b5877099d --- /dev/null +++ b/src/Composer/DependencyResolver/Rule.php @@ -0,0 +1,161 @@ + + * + * 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 + */ +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; + } +} diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php new file mode 100644 index 000000000..420d71838 --- /dev/null +++ b/src/Composer/DependencyResolver/Solver.php @@ -0,0 +1,1817 @@ + + * + * 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 + */ +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 +// } +} diff --git a/tests/Composer/Test/DependencyResolver/ArrayRepositoryTest.php b/tests/Composer/Test/DependencyResolver/ArrayRepositoryTest.php new file mode 100644 index 000000000..ce7380970 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/ArrayRepositoryTest.php @@ -0,0 +1,26 @@ + + * + * 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)); + } +} diff --git a/tests/Composer/Test/DependencyResolver/LiteralTest.php b/tests/Composer/Test/DependencyResolver/LiteralTest.php new file mode 100644 index 000000000..15704f213 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/LiteralTest.php @@ -0,0 +1,23 @@ + + * + * 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); + } +} diff --git a/tests/Composer/Test/DependencyResolver/PackageTest.php b/tests/Composer/Test/DependencyResolver/PackageTest.php new file mode 100644 index 000000000..436250eb5 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/PackageTest.php @@ -0,0 +1,29 @@ + + * + * 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); + } +} diff --git a/tests/Composer/Test/DependencyResolver/PoolTest.php b/tests/Composer/Test/DependencyResolver/PoolTest.php new file mode 100644 index 000000000..f8c93eb57 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/PoolTest.php @@ -0,0 +1,32 @@ + + * + * 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')); + } +} diff --git a/tests/Composer/Test/DependencyResolver/RequestTest.php b/tests/Composer/Test/DependencyResolver/RequestTest.php new file mode 100644 index 000000000..29903dd17 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/RequestTest.php @@ -0,0 +1,48 @@ + + * + * 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()); + } +} diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php new file mode 100644 index 000000000..6af17f581 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -0,0 +1,61 @@ + + * + * 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 ... + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 000000000..b88c49eca --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,20 @@ + + * + * 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; + } +}); +