Browse Source

Merge commit '45a2070feebdaf9ead7eccf5cd679cb92f0d24c9'

Jordi Boggiano 13 years ago
parent
commit
5959493679

+ 1 - 1
src/Composer/DependencyResolver/Operation/InstallOperation.php

@@ -61,6 +61,6 @@ class InstallOperation extends SolverOperation
      */
     public function __toString()
     {
-        return 'Installing '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().')';
+        return 'Installing '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
     }
 }

+ 66 - 0
src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php

@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver\Operation;
+
+use Composer\Package\AliasPackage;
+
+/**
+ * Solver install operation.
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class MarkAliasInstalledOperation extends SolverOperation
+{
+    protected $package;
+
+    /**
+     * Initializes operation.
+     *
+     * @param   PackageInterface    $package    package instance
+     * @param   string              $reason     operation reason
+     */
+    public function __construct(AliasPackage $package, $reason = null)
+    {
+        parent::__construct($reason);
+
+        $this->package = $package;
+    }
+
+    /**
+     * Returns package instance.
+     *
+     * @return  PackageInterface
+     */
+    public function getPackage()
+    {
+        return $this->package;
+    }
+
+    /**
+     * Returns job type.
+     *
+     * @return  string
+     */
+    public function getJobType()
+    {
+        return 'markAliasInstalled';
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function __toString()
+    {
+        return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getPrettyVersion().')';
+    }
+}

+ 66 - 0
src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php

@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver\Operation;
+
+use Composer\Package\AliasPackage;
+
+/**
+ * Solver install operation.
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class MarkAliasUninstalledOperation extends SolverOperation
+{
+    protected $package;
+
+    /**
+     * Initializes operation.
+     *
+     * @param   PackageInterface    $package    package instance
+     * @param   string              $reason     operation reason
+     */
+    public function __construct(AliasPackage $package, $reason = null)
+    {
+        parent::__construct($reason);
+
+        $this->package = $package;
+    }
+
+    /**
+     * Returns package instance.
+     *
+     * @return  PackageInterface
+     */
+    public function getPackage()
+    {
+        return $this->package;
+    }
+
+    /**
+     * Returns job type.
+     *
+     * @return  string
+     */
+    public function getJobType()
+    {
+        return 'markAliasUninstalled';
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function __toString()
+    {
+        return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getPrettyVersion().')';
+    }
+}

+ 9 - 0
src/Composer/DependencyResolver/Operation/SolverOperation.php

@@ -42,4 +42,13 @@ abstract class SolverOperation implements OperationInterface
     {
         return $this->reason;
     }
+
+    protected function formatVersion(PackageInterface $package)
+    {
+        if (!$package->isDev() || !in_array($package->getSourceType(), array('hg', 'git'))) {
+            return $package->getPrettyVersion();
+        }
+
+        return $package->getPrettyVersion().' '.substr($package->getSourceReference(), 0, 6);
+    }
 }

+ 1 - 1
src/Composer/DependencyResolver/Operation/UninstallOperation.php

@@ -61,6 +61,6 @@ class UninstallOperation extends SolverOperation
      */
     public function __toString()
     {
-        return 'Uninstalling '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().')';
+        return 'Uninstalling '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
     }
 }

+ 2 - 2
src/Composer/DependencyResolver/Operation/UpdateOperation.php

@@ -74,7 +74,7 @@ class UpdateOperation extends SolverOperation
      */
     public function __toString()
     {
-        return 'Updating '.$this->initialPackage->getPrettyName().' ('.$this->initialPackage->getPrettyVersion().') to '.
-            $this->targetPackage->getPrettyName(). ' ('.$this->targetPackage->getPrettyVersion().')';
+        return 'Updating '.$this->initialPackage->getPrettyName().' ('.$this->formatVersion($this->initialPackage).') to '.
+            $this->targetPackage->getPrettyName(). ' ('.$this->formatVersion($this->targetPackage).')';
     }
 }

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

@@ -29,6 +29,7 @@ class Rule
     const RULE_PACKAGE_SAME_NAME = 10;
     const RULE_PACKAGE_IMPLICIT_OBSOLETES = 11;
     const RULE_LEARNED = 12;
+    const RULE_PACKAGE_ALIAS = 13;
 
     protected $disabled;
     protected $literals;
@@ -235,6 +236,8 @@ class Rule
                 return $ruleText;
             case self::RULE_LEARNED:
                 return 'learned: '.$ruleText;
+            case self::RULE_PACKAGE_ALIAS:
+                return $ruleText;
         }
     }
 

+ 1 - 5
src/Composer/DependencyResolver/RuleSet.php

@@ -20,16 +20,12 @@ class RuleSet implements \IteratorAggregate, \Countable
     // highest priority => lowest number
     const TYPE_PACKAGE = 0;
     const TYPE_JOB = 1;
-    const TYPE_FEATURE = 3;
-    const TYPE_CHOICE = 4;
-    const TYPE_LEARNED = 5;
+    const TYPE_LEARNED = 4;
 
     protected static $types = array(
         -1 => 'UNKNOWN',
         self::TYPE_PACKAGE => 'PACKAGE',
-        self::TYPE_FEATURE => 'FEATURE',
         self::TYPE_JOB => 'JOB',
-        self::TYPE_CHOICE => 'CHOICE',
         self::TYPE_LEARNED => 'LEARNED',
     );
 

+ 152 - 577
src/Composer/DependencyResolver/Solver.php

@@ -14,6 +14,7 @@ namespace Composer\DependencyResolver;
 
 use Composer\Repository\RepositoryInterface;
 use Composer\Package\PackageInterface;
+use Composer\Package\AliasPackage;
 use Composer\DependencyResolver\Operation;
 
 /**
@@ -30,13 +31,21 @@ class Solver
     protected $ruleToJob = array();
     protected $addedMap = array();
     protected $updateMap = array();
-    protected $noObsoletes = array();
     protected $watches = array();
     protected $removeWatches = array();
     protected $decisionMap;
     protected $installedMap;
 
-    protected $packageToFeatureRule = array();
+    protected $packageToUpdateRule = array();
+
+    protected $decisionQueue = array();
+    protected $decisionQueueWhy = array();
+    protected $decisionQueueFree = array();
+    protected $propagateIndex;
+    protected $branches = array();
+    protected $problems = array();
+    protected $learnedPool = array();
+    protected $recommendsIndex;
 
     public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
     {
@@ -237,47 +246,37 @@ class Solver
             }
 
             // check obsoletes and implicit obsoletes of a package
-            // if ignoreinstalledsobsoletes is not set, we're also checking
-            // obsoletes of installed packages (like newer rpm versions)
-            //
-            /** TODO if ($this->noInstalledObsoletes) */
-            if (true) {
-                $noObsoletes = isset($this->noObsoletes[$package->getId()]);
-                $isInstalled = (isset($this->installedMap[$package->getId()]));
-
-                foreach ($package->getReplaces() as $link) {
-                    $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
-
-                    foreach ($obsoleteProviders as $provider) {
-                        if ($provider === $package) {
-                            continue;
-                        }
+            $isInstalled = (isset($this->installedMap[$package->getId()]));
+
+            foreach ($package->getReplaces() as $link) {
+                $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
+
+                foreach ($obsoleteProviders as $provider) {
+                    if ($provider === $package) {
+                        continue;
+                    }
 
+                    if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
                         $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
                         $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link));
                     }
                 }
+            }
 
-                // check implicit obsoletes
-                // for installed packages we only need to check installed/installed problems,
-                // as the others are picked up when looking at the uninstalled package.
-                if (!$isInstalled) {
-                    $obsoleteProviders = $this->pool->whatProvides($package->getName(), null);
-
-                    foreach ($obsoleteProviders as $provider) {
-                        if ($provider === $package) {
-                            continue;
-                        }
+            // check implicit obsoletes
+            // for installed packages we only need to check installed/installed problems,
+            // as the others are picked up when looking at the uninstalled package.
+            if (!$isInstalled) {
+                $obsoleteProviders = $this->pool->whatProvides($package->getName(), null);
 
-                        if ($isInstalled && !isset($this->installedMap[$provider->getId()])) {
-                            continue;
-                        }
-
-                        // obsolete same packages even when noObsoletes
-                        if ($noObsoletes && (!$package->equals($provider))) {
-                            continue;
-                        }
+                foreach ($obsoleteProviders as $provider) {
+                    if ($provider === $package) {
+                        continue;
+                    }
 
+                    if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
+                        $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, (string) $package));
+                    } else if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
                         $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
                         $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package));
                     }
@@ -286,6 +285,20 @@ class Solver
         }
     }
 
+    protected function obsoleteImpossibleForAlias($package, $provider)
+    {
+        $packageIsAlias = $package instanceof AliasPackage;
+        $providerIsAlias = $provider instanceof AliasPackage;
+
+        $impossible = (
+            ($packageIsAlias && $package->getAliasOf() === $provider) ||
+            ($providerIsAlias && $provider->getAliasOf() === $package) ||
+            ($packageIsAlias && $providerIsAlias && $provider->getAliasOf() === $package->getAliasOf())
+        );
+
+        return $impossible;
+    }
+
     /**
      * Adds all rules for all update packages of a given package
      *
@@ -431,7 +444,7 @@ class Solver
 
             // push all of our rules (can only be feature or job rules)
             // asserting this literal on the problem stack
-            foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_FEATURE)) as $assertRule) {
+            foreach ($this->rules->getIteratorFor(RuleSet::TYPE_JOB) as $assertRule) {
                 if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) {
                     continue;
                 }
@@ -492,370 +505,22 @@ class Solver
             }
 
             $this->disableProblem($why);
-            /** TODO solver_reenablepolicyrules(solv, -(v + 1)); */
-        }
-    }
-
-    protected function addChoiceRules()
-    {
-
-// void
-// solver_addchoicerules(Solver *solv)
-// {
-//   Pool *pool = solv->pool;
-//   Map m, mneg;
-//   Rule *r;
-//   Queue q, qi;
-//   int i, j, rid, havechoice;
-//   Id p, d, *pp;
-//   Id p2, pp2;
-//   Solvable *s, *s2;
-//
-//   solv->choicerules = solv->nrules;
-//   if (!pool->installed)
-//     {
-//       solv->choicerules_end = solv->nrules;
-//       return;
-//     }
-//   solv->choicerules_ref = sat_calloc(solv->rpmrules_end, sizeof(Id));
-//   queue_init(&q);
-//   queue_init(&qi);
-//   map_init(&m, pool->nsolvables);
-//   map_init(&mneg, pool->nsolvables);
-//   /* set up negative assertion map from infarch and dup rules */
-//   for (rid = solv->infarchrules, r = solv->rules + rid; rid < solv->infarchrules_end; rid++, r++)
-//     if (r->p < 0 && !r->w2 && (r->d == 0 || r->d == -1))
-//       MAPSET(&mneg, -r->p);
-//   for (rid = solv->duprules, r = solv->rules + rid; rid < solv->duprules_end; rid++, r++)
-//     if (r->p < 0 && !r->w2 && (r->d == 0 || r->d == -1))
-//       MAPSET(&mneg, -r->p);
-//   for (rid = 1; rid < solv->rpmrules_end ; rid++)
-//     {
-//       r = solv->rules + rid;
-//       if (r->p >= 0 || ((r->d == 0 || r->d == -1) && r->w2 < 0))
-//     continue;   /* only look at requires rules */
-//       // solver_printrule(solv, SAT_DEBUG_RESULT, r);
-//       queue_empty(&q);
-//       queue_empty(&qi);
-//       havechoice = 0;
-//       FOR_RULELITERALS(p, pp, r)
-//     {
-//       if (p < 0)
-//         continue;
-//       s = pool->solvables + p;
-//       if (!s->repo)
-//         continue;
-//       if (s->repo == pool->installed)
-//         {
-//           queue_push(&q, p);
-//           continue;
-//         }
-//       /* check if this package is "blocked" by a installed package */
-//       s2 = 0;
-//       FOR_PROVIDES(p2, pp2, s->name)
-//         {
-//           s2 = pool->solvables + p2;
-//           if (s2->repo != pool->installed)
-//         continue;
-//           if (!pool->implicitobsoleteusesprovides && s->name != s2->name)
-//             continue;
-//           if (pool->obsoleteusescolors && !pool_colormatch(pool, s, s2))
-//             continue;
-//           break;
-//         }
-//       if (p2)
-//         {
-//           /* found installed package p2 that we can update to p */
-//           if (MAPTST(&mneg, p))
-//         continue;
-//           if (policy_is_illegal(solv, s2, s, 0))
-//         continue;
-//           queue_push(&qi, p2);
-//           queue_push(&q, p);
-//           continue;
-//         }
-//       if (s->obsoletes)
-//         {
-//           Id obs, *obsp = s->repo->idarraydata + s->obsoletes;
-//           s2 = 0;
-//           while ((obs = *obsp++) != 0)
-//         {
-//           FOR_PROVIDES(p2, pp2, obs)
-//             {
-//               s2 = pool->solvables + p2;
-//               if (s2->repo != pool->installed)
-//             continue;
-//               if (!pool->obsoleteusesprovides && !pool_match_nevr(pool, pool->solvables + p2, obs))
-//             continue;
-//               if (pool->obsoleteusescolors && !pool_colormatch(pool, s, s2))
-//             continue;
-//               break;
-//             }
-//           if (p2)
-//             break;
-//         }
-//           if (obs)
-//         {
-//           /* found installed package p2 that we can update to p */
-//           if (MAPTST(&mneg, p))
-//             continue;
-//           if (policy_is_illegal(solv, s2, s, 0))
-//             continue;
-//           queue_push(&qi, p2);
-//           queue_push(&q, p);
-//           continue;
-//         }
-//         }
-//       /* package p is independent of the installed ones */
-//       havechoice = 1;
-//     }
-//       if (!havechoice || !q.count)
-//     continue;   /* no choice */
-//
-//       /* now check the update rules of the installed package.
-//        * if all packages of the update rules are contained in
-//        * the dependency rules, there's no need to set up the choice rule */
-//       map_empty(&m);
-//       FOR_RULELITERALS(p, pp, r)
-//         if (p > 0)
-//       MAPSET(&m, p);
-//       for (i = 0; i < qi.count; i++)
-//     {
-//       if (!qi.elements[i])
-//         continue;
-//       Rule *ur = solv->rules + solv->updaterules + (qi.elements[i] - pool->installed->start);
-//       if (!ur->p)
-//         ur = solv->rules + solv->featurerules + (qi.elements[i] - pool->installed->start);
-//       if (!ur->p)
-//         continue;
-//       FOR_RULELITERALS(p, pp, ur)
-//         if (!MAPTST(&m, p))
-//           break;
-//       if (p)
-//         break;
-//       for (j = i + 1; j < qi.count; j++)
-//         if (qi.elements[i] == qi.elements[j])
-//           qi.elements[j] = 0;
-//     }
-//       if (i == qi.count)
-//     {
-// #if 0
-//       printf("skipping choice ");
-//       solver_printrule(solv, SAT_DEBUG_RESULT, solv->rules + rid);
-// #endif
-//       continue;
-//     }
-//       d = q.count ? pool_queuetowhatprovides(pool, &q) : 0;
-//       solver_addrule(solv, r->p, d);
-//       queue_push(&solv->weakruleq, solv->nrules - 1);
-//       solv->choicerules_ref[solv->nrules - 1 - solv->choicerules] = rid;
-// #if 0
-//       printf("OLD ");
-//       solver_printrule(solv, SAT_DEBUG_RESULT, solv->rules + rid);
-//       printf("WEAK CHOICE ");
-//       solver_printrule(solv, SAT_DEBUG_RESULT, solv->rules + solv->nrules - 1);
-// #endif
-//     }
-//   queue_free(&q);
-//   queue_free(&qi);
-//   map_free(&m);
-//   map_free(&mneg);
-//   solv->choicerules_end = solv->nrules;
-// }
-    }
-
-/***********************************************************************
- ***
- ***  Policy rule disabling/reenabling
- ***
- ***  Disable all policy rules that conflict with our jobs. If a job
- ***  gets disabled later on, reenable the involved policy rules again.
- ***
- *** /
-
-#define DISABLE_UPDATE  1
-#define DISABLE_INFARCH 2
-#define DISABLE_DUP 3
-*/
-    protected function jobToDisableQueue(array $job, array $disableQueue)
-    {
-        switch ($job['cmd']) {
-            case 'install':
-                foreach ($job['packages'] as $package) {
-                    if (isset($this->installedMap[$package->getId()])) {
-                        $disableQueue[] = array('type' => 'update', 'package' => $package);
-                    }
-
-      /* all job packages obsolete * /
-      qstart = q->count;
-      pass = 0;
-      memset(&omap, 0, sizeof(omap));
-      FOR_JOB_SELECT(p, pp, select, what)
-    {
-      Id p2, pp2;
-
-      if (pass == 1)
-        map_grow(&omap, installed->end - installed->start);
-      s = pool->solvables + p;
-      if (s->obsoletes)
-        {
-          Id obs, *obsp;
-          obsp = s->repo->idarraydata + s->obsoletes;
-          while ((obs = *obsp++) != 0)
-        FOR_PROVIDES(p2, pp2, obs)
-          {
-            Solvable *ps = pool->solvables + p2;
-            if (ps->repo != installed)
-              continue;
-            if (!pool->obsoleteusesprovides && !pool_match_nevr(pool, ps, obs))
-              continue;
-            if (pool->obsoleteusescolors && !pool_colormatch(pool, s, ps))
-              continue;
-            if (pass)
-              MAPSET(&omap, p2 - installed->start);
-            else
-              queue_push2(q, DISABLE_UPDATE, p2);
-          }
-        }
-      FOR_PROVIDES(p2, pp2, s->name)
-        {
-          Solvable *ps = pool->solvables + p2;
-          if (ps->repo != installed)
-        continue;
-          if (!pool->implicitobsoleteusesprovides && ps->name != s->name)
-        continue;
-          if (pool->obsoleteusescolors && !pool_colormatch(pool, s, ps))
-        continue;
-          if (pass)
-            MAPSET(&omap, p2 - installed->start);
-              else
-            queue_push2(q, DISABLE_UPDATE, p2);
-        }
-      if (pass)
-        {
-          for (i = j = qstart; i < q->count; i += 2)
-        {
-          if (MAPTST(&omap, q->elements[i + 1] - installed->start))
-            {
-              MAPCLR(&omap, q->elements[i + 1] - installed->start);
-              q->elements[j + 1] = q->elements[i + 1];
-              j += 2;
-            }
-        }
-          queue_truncate(q, j);
-        }
-      if (q->count == qstart)
-        break;
-      pass++;
-    }
-      if (omap.size)
-        map_free(&omap);
-
-      if (qstart == q->count)
-    return;     /* nothing to prune * /
-      if ((set & (SOLVER_SETEVR | SOLVER_SETARCH | SOLVER_SETVENDOR)) == (SOLVER_SETEVR | SOLVER_SETARCH | SOLVER_SETVENDOR))
-    return;     /* all is set */
-
-      /* now that we know which installed packages are obsoleted check each of them * /
-      for (i = j = qstart; i < q->count; i += 2)
-    {
-      Solvable *is = pool->solvables + q->elements[i + 1];
-      FOR_JOB_SELECT(p, pp, select, what)
-        {
-          int illegal = 0;
-          s = pool->solvables + p;
-          if ((set & SOLVER_SETEVR) != 0)
-        illegal |= POLICY_ILLEGAL_DOWNGRADE;    /* ignore * /
-          if ((set & SOLVER_SETARCH) != 0)
-        illegal |= POLICY_ILLEGAL_ARCHCHANGE;   /* ignore * /
-          if ((set & SOLVER_SETVENDOR) != 0)
-        illegal |= POLICY_ILLEGAL_VENDORCHANGE; /* ignore * /
-          illegal = policy_is_illegal(solv, is, s, illegal);
-          if (illegal && illegal == POLICY_ILLEGAL_DOWNGRADE && (set & SOLVER_SETEV) != 0)
-        {
-          /* it's ok if the EV is different * /
-          if (evrcmp(pool, is->evr, s->evr, EVRCMP_COMPARE_EVONLY) != 0)
-            illegal = 0;
-        }
-          if (illegal)
-        break;
-        }
-      if (!p)
-        {
-          /* no package conflicts with the update rule * /
-          /* thus keep the DISABLE_UPDATE * /
-          q->elements[j + 1] = q->elements[i + 1];
-          j += 2;
-        }
-    }
-      queue_truncate(q, j);
-      return;*/
-                }
-            break;
-
-            case 'remove':
-                foreach ($job['packages'] as $package) {
-                    if (isset($this->installedMap[$package->getId()])) {
-                        $disableQueue[] = array('type' => 'update', 'package' => $package);
-                    }
-                }
-            break;
-        }
-
-        return $disableQueue;
-    }
-
-    protected function disableUpdateRule($package)
-    {
-        if (isset($this->packageToFeatureRule[$package->getId()])) {
-            $this->packageToFeatureRule[$package->getId()]->disable();
         }
     }
 
-    /**
-    * Disables all policy rules that conflict with jobs
-    */
-    protected function disablePolicyRules()
+    protected function setupInstalledMap()
     {
-        $lastJob = null;
-        $allQueue = array();
-
-        $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
-        foreach ($iterator as $rule) {
-            if ($rule->isDisabled()) {
-                continue;
-            }
-
-            $job = $this->ruleToJob[$rule->getId()];
-
-            if ($job === $lastJob) {
-                continue;
-            }
-
-            $lastJob = $job;
-
-            $allQueue = $this->jobToDisableQueue($job, $allQueue);
-        }
-
-        foreach ($allQueue as $disable) {
-            switch ($disable['type']) {
-                case 'update':
-                    $this->disableUpdateRule($disable['package']);
-                break;
-                default:
-                    throw new \RuntimeException("Unsupported disable type: " . $disable['type']);
-            }
+        $this->installedMap = array();
+        foreach ($this->installed->getPackages() as $package) {
+            $this->installedMap[$package->getId()] = $package;
         }
     }
 
     public function solve(Request $request)
     {
         $this->jobs = $request->getJobs();
-        $installedPackages = $this->installed->getPackages();
-        $this->installedMap = array();
-        foreach ($installedPackages as $package) {
-            $this->installedMap[$package->getId()] = $package;
-        }
+
+        $this->setupInstalledMap();
 
         if (version_compare(PHP_VERSION, '5.3.4', '>=')) {
             $this->decisionMap = new \SplFixedArray($this->pool->getMaxId() + 1);
@@ -876,18 +541,18 @@ class Solver
 
             switch ($job['cmd']) {
                 case 'update-all':
-                    foreach ($installedPackages as $package) {
+                    foreach ($this->installedMap as $package) {
                         $this->updateMap[$package->getId()] = true;
                     }
                 break;
             }
         }
 
-        foreach ($installedPackages as $package) {
+        foreach ($this->installedMap as $package) {
             $this->addRulesForPackage($package);
         }
 
-        foreach ($installedPackages as $package) {
+        foreach ($this->installedMap as $package) {
             $this->addRulesForUpdatePackages($package);
         }
 
@@ -903,15 +568,11 @@ class Solver
             }
         }
 
-        // solver_addrpmrulesforweak(solv, &addedmap);
-
-        foreach ($installedPackages as $package) {
+        foreach ($this->installedMap as $package) {
             $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
             $rule = $this->createUpdateRule($package, $updates, Rule::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
 
-            $rule->setWeak(true);
-            $this->addRule(RuleSet::TYPE_FEATURE, $rule);
-            $this->packageToFeatureRule[$package->getId()] = $rule;
+            $this->packageToUpdateRule[$package->getId()] = $rule;
         }
 
         foreach ($this->jobs as $job) {
@@ -952,23 +613,15 @@ class Solver
             }
         }
 
-        $this->addChoiceRules();
-
         foreach ($this->rules as $rule) {
             $this->addWatchesToRule($rule);
         }
 
-        /* disable update rules that conflict with our job */
-        $this->disablePolicyRules();
-
         /* make decisions based on job/update assertions */
         $this->makeAssertionRuleDecisions();
 
         $installRecommended = 0;
         $this->runSat(true, $installRecommended);
-        //$this->printDecisionMap();
-        //findrecommendedsuggested(solv);
-        //solver_prepare_solutions(solv);
 
         if ($this->problems) {
             throw new SolverProblemsException($this->problems);
@@ -989,8 +642,8 @@ class Solver
             if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) {
                 $literals = array();
 
-                if (isset($this->packageToFeatureRule[$package->getId()])) {
-                    $literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals());
+                if (isset($this->packageToUpdateRule[$package->getId()])) {
+                    $literals = array_merge($literals, $this->packageToUpdateRule[$package->getId()]->getLiterals());
                 }
 
                 foreach ($literals as $updateLiteral) {
@@ -1010,7 +663,15 @@ class Solver
             }
 
             if ($literal->isWanted()) {
+                if ($package instanceof AliasPackage) {
+                    $transaction[] = new Operation\MarkAliasInstalledOperation(
+                        $package, $this->decisionQueueWhy[$i]
+                    );
+                    continue;
+                }
+
                 if (isset($installMeansUpdateMap[$literal->getPackageId()])) {
+
                     $source = $installMeansUpdateMap[$literal->getPackageId()];
 
                     $transaction[] = new Operation\UpdateOperation(
@@ -1026,24 +687,75 @@ class Solver
                     );
                 }
             } else if (!isset($ignoreRemove[$package->getId()])) {
-                $transaction[] = new Operation\UninstallOperation(
-                    $package, $this->decisionQueueWhy[$i]
-                );
+                if ($package instanceof AliasPackage) {
+                    $transaction[] = new Operation\MarkAliasInstalledOperation(
+                        $package, $this->decisionQueueWhy[$i]
+                    );
+                } else {
+                    $transaction[] = new Operation\UninstallOperation(
+                        $package, $this->decisionQueueWhy[$i]
+                    );
+                }
+            }
+        }
+
+        $allDecidedMap = $this->decisionMap;
+        foreach ($this->decisionMap as $packageId => $decision) {
+            if ($decision != 0) {
+                $package = $this->pool->packageById($packageId);
+                if ($package instanceof AliasPackage) {
+                    $allDecidedMap[$package->getAliasOf()->getId()] = $decision;
+                }
+            }
+        }
+
+        foreach ($allDecidedMap as $packageId => $decision) {
+            if ($packageId === 0) {
+                continue;
+            }
+
+            if (0 == $decision && isset($this->installedMap[$packageId])) {
+                $package = $this->pool->packageById($packageId);
+
+                if ($package instanceof AliasPackage) {
+                    $transaction[] = new Operation\MarkAliasInstalledOperation(
+                        $package, null
+                    );
+                } else {
+                    $transaction[] = new Operation\UninstallOperation(
+                        $package, null
+                    );
+                }
+
+                $this->decisionMap[$packageId] = -1;
+            }
+        }
+
+        foreach ($this->decisionMap as $packageId => $decision) {
+            if ($packageId === 0) {
+                continue;
+            }
+
+            if (0 == $decision && isset($this->installedMap[$packageId])) {
+                $package = $this->pool->packageById($packageId);
+
+                if ($package instanceof AliasPackage) {
+                    $transaction[] = new Operation\MarkAliasInstalledOperation(
+                        $package, null
+                    );
+                } else {
+                    $transaction[] = new Operation\UninstallOperation(
+                        $package, null
+                    );
+                }
+
+                $this->decisionMap[$packageId] = -1;
             }
         }
 
         return array_reverse($transaction);
     }
 
-    protected $decisionQueue = array();
-    protected $decisionQueueWhy = array();
-    protected $decisionQueueFree = array();
-    protected $propagateIndex;
-    protected $branches = array();
-    protected $problems = array();
-    protected $learnedPool = array();
-    protected $recommendsIndex;
-
     protected function literalFromId($id)
     {
         $package = $this->pool->packageById(abs($id));
@@ -1094,7 +806,7 @@ class Solver
     protected function decisionsSatisfy(Literal $l)
     {
         return ($l->isWanted() && $this->decisionMap[$l->getPackageId()] > 0) ||
-            (!$l->isWanted() && $this->decisionMap[$l->getPackageId()] <= 0);
+            (!$l->isWanted() && $this->decisionMap[$l->getPackageId()] < 0);
     }
 
     protected function decisionsConflict(Literal $l)
@@ -1237,11 +949,6 @@ class Solver
                 break;
             }
 
-            /** TODO: implement recommendations
-             *if (v > 0 && solv->recommendations.count && v == solv->recommendations.elements[solv->recommendations.count - 1])
-             *  solv->recommendations.count--;
-             */
-
             $this->decisionMap[$literal->getPackageId()] = 0;
             array_pop($this->decisionQueue);
             array_pop($this->decisionQueueWhy);
@@ -1421,14 +1128,19 @@ class Solver
                     $l1num++;
                     $l1retry = true;
                 }
-
-                $rule = $this->decisionQueueWhy[$decisionId];
             }
+
+            $rule = $this->decisionQueueWhy[$decisionId];
         }
 
         $why = count($this->learnedPool) - 1;
-        assert($learnedLiterals[0] !== null);
-        $newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why);
+
+        if (count($learnedLiterals) === 1 && $learnedLiterals[0] === null) {
+            $newRule = new Rule(array(), Rule::RULE_LEARNED, $why);
+        } else {
+            assert($learnedLiterals[0] !== null);
+            $newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why);
+        }
 
         return array($learnedLiterals[0], $ruleLevel, $newRule, $why);
     }
@@ -1536,20 +1248,10 @@ class Solver
                 $why = $lastWeakWhy;
             }
 
-            if ($lastWeakWhy->getType() == RuleSet::TYPE_CHOICE) {
-                $this->disableChoiceRules($lastWeakWhy);
-            }
-
             $this->disableProblem($why);
-
-            /**
-@TODO what does v < 0 mean here? ($why == v)
-      if (v < 0)
-    solver_reenablepolicyrules(solv, -(v + 1));
-*/
             $this->resetSolver();
 
-            return true;
+            return 1;
         }
 
         if ($disableRules) {
@@ -1562,10 +1264,10 @@ class Solver
             }
 
             $this->resetSolver();
-            return true;
+            return 1;
         }
 
-        return false;
+        return 0;
     }
 
     private function disableProblem($why)
@@ -1637,10 +1339,8 @@ class Solver
         //    * 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
+        //    * 3) fulfill all unresolved rules
+        //    * 4) minimalize solution if we had choices
         //    * if we encounter a problem, we rewind to a safe level and restart
         //    * with step 1
         //    */
@@ -1654,8 +1354,6 @@ class Solver
         $minimizationSteps = 0;
         $installedPos = 0;
 
-        $this->installedPackages = $this->installed->getPackages();
-
         while (true) {
 
             if (1 === $level) {
@@ -1727,125 +1425,6 @@ class Solver
                 }
             }
 
-            // 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
-                        /** TODO: && or || ? **/
-                        if (0 === $pass && !isset($this->updateMap[$literal->getPackageId()])) {
-                            continue;
-                        }
-
-                        $rule = null;
-
-                        if (isset($this->packageToFeatureRule[$literal->getPackageId()])) {
-                            $rule = $this->packageToFeatureRule[$literal->getPackageId()];
-                        }
-
-                        if (!$rule || $rule->isDisabled()) {
-                            continue;
-                        }
-
-                        $updateRuleLiterals = $rule->getLiterals();
-
-                        $decisionQueue = array();
-                        if (!isset($this->noUpdate[$literal->getPackageId()]) && (
-                            $this->decidedRemove($literal->getPackage()) ||
-                            isset($this->updateMap[$literal->getPackageId()]) ||
-                            !$literal->equals($updateRuleLiterals[0])
-                        )) {
-                            foreach ($updateRuleLiterals as $ruleLiteral) {
-                                if ($this->decidedInstall($ruleLiteral->getPackage())) {
-                                    // already fulfilled
-                                    $decisionQueue = array();
-                                    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;
-                            }
-                        } else if (!$repeat && $this->undecided($literal->getPackage())) {
-                            // still undecided? keep package.
-                            $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;
             }
@@ -1897,18 +1476,14 @@ class Solver
                     return;
                 }
 
-                // open suse sat-solver uses this, but why is $level == 1 trouble?
-                // SYSTEMSOLVABLE related? we don't have that, so should work
-                //if ($level < $systemLevel || $level == 1) {
-
-                if ($level < $systemLevel) {
-                    break; // trouble
-                }
-
                 // something changed, so look at all rules again
                 $n = -1;
             }
 
+            if ($level < $systemLevel) {
+                continue;
+            }
+
             // minimization step
             if (count($this->branches)) {
 

+ 2 - 1
src/Composer/Downloader/FileDownloader.php

@@ -64,7 +64,7 @@ class FileDownloader implements DownloaderInterface
 
         $fileName = $this->getFileName($package, $path);
 
-        $this->io->write("  - Package <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
+        $this->io->write("  - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
 
         $processUrl = $this->processUrl($url);
 
@@ -101,6 +101,7 @@ class FileDownloader implements DownloaderInterface
      */
     public function remove(PackageInterface $package, $path)
     {
+        $this->io->write("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
         if (!$this->filesystem->removeDirectory($path)) {
             throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
         }

+ 3 - 2
src/Composer/Downloader/VcsDownloader.php

@@ -50,7 +50,7 @@ abstract class VcsDownloader implements DownloaderInterface
             throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
         }
 
-        $this->io->write("  - Package <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
+        $this->io->write("  - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
         $this->filesystem->removeDirectory($path);
         $this->doDownload($package, $path);
         $this->io->write('');
@@ -65,7 +65,7 @@ abstract class VcsDownloader implements DownloaderInterface
             throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information');
         }
 
-        $this->io->write("  - Package <info>" . $target->getName() . "</info> (<comment>" . $target->getPrettyVersion() . "</comment>)");
+        $this->io->write("  - Installing <info>" . $target->getName() . "</info> (<comment>" . $target->getPrettyVersion() . "</comment>)");
         $this->enforceCleanDirectory($path);
         $this->doUpdate($initial, $target, $path);
         $this->io->write('');
@@ -77,6 +77,7 @@ abstract class VcsDownloader implements DownloaderInterface
     public function remove(PackageInterface $package, $path)
     {
         $this->enforceCleanDirectory($path);
+        $this->io->write("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
         if (!$this->filesystem->removeDirectory($path)) {
             throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
         }

+ 13 - 19
src/Composer/Installer.php

@@ -242,7 +242,7 @@ class Installer
                 $version = $package->getVersion();
                 foreach ($aliases as $alias) {
                     if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) {
-                        $version = $alias['alias'];
+                        $version = $alias['alias_normalized'];
                         break;
                     }
                 }
@@ -259,9 +259,11 @@ class Installer
             }
         }
 
-        // fix the version all installed packages that are not in the current local repo to prevent rogue updates
+        // fix the version of all installed packages (+ platform) that are not
+        // in the current local repo to prevent rogue updates (e.g. non-dev
+        // updating when in dev)
         foreach ($installedRepo->getPackages() as $package) {
-            if ($package->getRepository() === $localRepo || $package->getRepository() instanceof PlatformRepository) {
+            if ($package->getRepository() === $localRepo) {
                 continue;
             }
 
@@ -324,14 +326,6 @@ class Installer
             }
         }
 
-        // anti-alias local repository to allow updates to work fine
-        foreach ($localRepo->getPackages() as $package) {
-            if ($package instanceof AliasPackage) {
-                $package->getRepository()->addPackage(clone $package->getAliasOf());
-                $package->getRepository()->removePackage($package);
-            }
-        }
-
         // execute operations
         if (!$operations) {
             $this->io->write('Nothing to install or update');
@@ -354,7 +348,10 @@ class Installer
             }
 
             if (!$this->dryRun) {
-                $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
+                $event = 'Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType());
+                if (defined($event)) {
+                    $this->eventDispatcher->dispatchPackageEvent(constant($event), $operation);
+                }
 
                 // if installing from lock, restore dev packages' references to their locked state
                 if ($installFromLock) {
@@ -376,17 +373,15 @@ class Installer
                 }
                 $this->installationManager->execute($localRepo, $operation);
 
-                $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
+                $event = 'Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType());
+                if (defined($event)) {
+                    $this->eventDispatcher->dispatchPackageEvent(constant($event), $operation);
+                }
 
                 $localRepo->write();
             }
         }
 
-        // reload local repository for the dev pass to work ok with aliases since it was anti-aliased above
-        if (!$devMode) {
-            $localRepo->reload();
-        }
-
         return true;
     }
 
@@ -410,7 +405,6 @@ class Installer
                     $package->setAlias($alias['alias_normalized']);
                     $package->setPrettyAlias($alias['alias']);
                     $package->getRepository()->addPackage($aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
-                    $package->getRepository()->removePackage($package);
                     $aliasPackage->setRootPackageAlias(true);
                 }
             }

+ 40 - 17
src/Composer/Installer/InstallationManager.php

@@ -21,6 +21,8 @@ use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\DependencyResolver\Operation\InstallOperation;
 use Composer\DependencyResolver\Operation\UpdateOperation;
 use Composer\DependencyResolver\Operation\UninstallOperation;
+use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
+use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
 use Composer\Util\Filesystem;
 
 /**
@@ -104,6 +106,10 @@ class InstallationManager
      */
     public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
+        if ($package instanceof AliasPackage) {
+            return $repo->hasPackage($package);
+        }
+
         return $this->getInstaller($package->getType())->isInstalled($repo, $package);
     }
 
@@ -127,7 +133,7 @@ class InstallationManager
      */
     public function install(RepositoryInterface $repo, InstallOperation $operation)
     {
-        $package = $this->antiAlias($operation->getPackage());
+        $package = $operation->getPackage();
         $installer = $this->getInstaller($package->getType());
         $installer->install($repo, $package);
         $this->notifyInstall($package);
@@ -141,8 +147,8 @@ class InstallationManager
      */
     public function update(RepositoryInterface $repo, UpdateOperation $operation)
     {
-        $initial = $this->antiAlias($operation->getInitialPackage());
-        $target = $this->antiAlias($operation->getTargetPackage());
+        $initial = $operation->getInitialPackage();
+        $target = $operation->getTargetPackage();
 
         $initialType = $initial->getType();
         $targetType  = $target->getType();
@@ -165,11 +171,41 @@ class InstallationManager
      */
     public function uninstall(RepositoryInterface $repo, UninstallOperation $operation)
     {
-        $package = $this->antiAlias($operation->getPackage());
+        $package = $operation->getPackage();
         $installer = $this->getInstaller($package->getType());
         $installer->uninstall($repo, $package);
     }
 
+    /**
+     * Executes markAliasInstalled operation.
+     *
+     * @param   RepositoryInterface $repo       repository in which to check
+     * @param   MarkAliasInstalledOperation    $operation  operation instance
+     */
+    public function markAliasInstalled(RepositoryInterface $repo, MarkAliasInstalledOperation $operation)
+    {
+        $package = $operation->getPackage();
+
+        if (!$repo->hasPackage($package)) {
+            $repo->addPackage(clone $package);
+        }
+
+        $this->notifyInstall($package);
+    }
+
+    /**
+     * Executes markAlias operation.
+     *
+     * @param   RepositoryInterface $repo       repository in which to check
+     * @param   MarkAliasUninstalledOperation    $operation  operation instance
+     */
+    public function markAliasUninstalled(RepositoryInterface $repo, MarkAliasUninstalledOperation $operation)
+    {
+        $package = $operation->getPackage();
+
+        $repo->removePackage($package);
+    }
+
     /**
      * Returns the installation path of a package
      *
@@ -203,17 +239,4 @@ class InstallationManager
             $package->getRepository()->notifyInstall($package);
         }
     }
-
-    private function antiAlias(PackageInterface $package)
-    {
-        if ($package instanceof AliasPackage) {
-            $alias = $package;
-            $package = $package->getAliasOf();
-            $package->setInstalledAsAlias(true);
-            $package->setAlias($alias->getVersion());
-            $package->setPrettyAlias($alias->getPrettyVersion());
-        }
-
-        return $package;
-    }
 }

+ 7 - 0
src/Composer/Installer/LibraryInstaller.php

@@ -127,6 +127,13 @@ class LibraryInstaller implements InstallerInterface
         $this->downloadManager->remove($package, $downloadPath);
         $this->removeBinaries($package);
         $repo->removePackage($package);
+
+        if (strpos($package->getName(), '/')) {
+            $packageVendorDir = dirname($downloadPath);
+            if (is_dir($packageVendorDir) && !glob($packageVendorDir.'/*')) {
+                @rmdir($packageVendorDir);
+            }
+        }
     }
 
     /**

+ 15 - 5
src/Composer/Package/Locker.php

@@ -89,7 +89,13 @@ class Locker
         $repo = $dev ? $this->repositoryManager->getLocalDevRepository() : $this->repositoryManager->getLocalRepository();
 
         foreach ($lockedPackages as $info) {
-            $resolvedVersion = !empty($info['alias']) ? $info['alias'] : $info['version'];
+            // TODO BC remove this after June 10th
+            if (isset($info['alias']) && empty($warned)) {
+                $warned = true;
+                echo 'BC warning: your lock file appears to be of an older format than this composer version, it is recommended to run composer update'.PHP_EOL;
+            }
+
+            $resolvedVersion = !empty($info['alias-version']) ? $info['alias-version'] : $info['version'];
 
             // try to find the package in the local repo (best match)
             $package = $repo->findPackage($info['package'], $resolvedVersion);
@@ -100,10 +106,10 @@ class Locker
             }
 
             // try to find the package in any repo (second pass without alias + rebuild alias since it disappeared)
-            if (!$package && !empty($info['alias'])) {
+            if (!$package && !empty($info['alias-version'])) {
                 $package = $this->repositoryManager->findPackage($info['package'], $info['version']);
                 if ($package) {
-                    $alias = new AliasPackage($package, $info['alias'], $info['alias']);
+                    $alias = new AliasPackage($package, $info['alias-version'], $info['alias-pretty-version']);
                     $package->getRepository()->addPackage($alias);
                     $package = $alias;
                 }
@@ -179,7 +185,10 @@ class Locker
         $locked = array();
 
         foreach ($packages as $package) {
+            $alias = null;
+
             if ($package instanceof AliasPackage) {
+                $alias = $package;
                 $package = $package->getAliasOf();
             }
 
@@ -198,8 +207,9 @@ class Locker
                 $spec['source-reference'] = $package->getSourceReference();
             }
 
-            if ($package->getAlias() && $package->isInstalledAsAlias()) {
-                $spec['alias'] = $package->getAlias();
+            if ($alias) {
+                $spec['alias-pretty-version'] = $alias->getPrettyVersion();
+                $spec['alias-version'] = $alias->getVersion();
             }
 
             $locked[] = $spec;

+ 0 - 19
src/Composer/Package/MemoryPackage.php

@@ -46,7 +46,6 @@ class MemoryPackage extends BasePackage
     protected $aliases = array();
     protected $alias;
     protected $prettyAlias;
-    protected $installedAsAlias;
     protected $dev;
 
     protected $requires = array();
@@ -211,24 +210,6 @@ class MemoryPackage extends BasePackage
         return $this->prettyAlias;
     }
 
-    /**
-     * Enabled if the package is installed from its alias package
-     *
-     * @param string $installedAsAlias
-     */
-    public function setInstalledAsAlias($installedAsAlias)
-    {
-        $this->installedAsAlias = $installedAsAlias;
-    }
-
-    /**
-     * @return string
-     */
-    public function isInstalledAsAlias()
-    {
-        return $this->installedAsAlias;
-    }
-
     /**
      * {@inheritDoc}
      */

+ 2 - 2
src/Composer/Repository/ArrayRepository.php

@@ -103,8 +103,8 @@ class ArrayRepository implements RepositoryInterface
         $package->setRepository($this);
         $this->packages[] = $package;
 
-        // create alias package on the fly if needed (installed repos manage aliases themselves)
-        if ($package->getAlias() && !$this instanceof InstalledRepositoryInterface) {
+        // create alias package on the fly if needed
+        if ($package->getAlias()) {
             $this->addPackage($this->createAliasPackage($package));
         }
     }

+ 25 - 14
src/Composer/Repository/FilesystemRepository.php

@@ -14,6 +14,7 @@ namespace Composer\Repository;
 
 use Composer\Json\JsonFile;
 use Composer\Package\PackageInterface;
+use Composer\Package\AliasPackage;
 use Composer\Package\Loader\ArrayLoader;
 use Composer\Package\Dumper\ArrayDumper;
 
@@ -54,21 +55,32 @@ class FilesystemRepository extends ArrayRepository implements WritableRepository
             throw new \UnexpectedValueException('Could not parse package list from the '.$this->file->getPath().' repository');
         }
 
+        $aliases = array();
+
         $loader = new ArrayLoader();
         foreach ($packages as $packageData) {
             $package = $loader->load($packageData);
 
-            // package was installed as alias, so we only add the alias
-            if ($this instanceof InstalledRepositoryInterface && !empty($packageData['installed-as-alias'])) {
-                $alias = $packageData['installed-as-alias'];
-                $package->setAlias($alias);
-                $package->setPrettyAlias($alias);
-                $package->setInstalledAsAlias(true);
-                $this->addPackage($this->createAliasPackage($package, $alias, $alias));
-            } else {
-                // only add regular package - if it's not an installed repo the alias will be created on the fly
-                $this->addPackage($package);
+            // aliases need to be looked up in the end to set up references correctly
+            if ($this instanceof InstalledRepositoryInterface && !empty($packageData['alias'])) {
+                $aliases[] = array(
+                    'package' => $package,
+                    'alias' => $packageData['alias'],
+                    'alias_pretty' => $packageData['alias_pretty']
+                );
             }
+
+            $this->addPackage($package);
+        }
+
+        foreach ($aliases as $aliasData) {
+            $temporaryPackage = $aliasData['package'];
+
+            $package = $this->findPackage($package->getName(), $package->getVersion());
+
+            $package->setAlias($aliasData['alias']);
+            $package->setPrettyAlias($aliasData['alias_pretty']);
+            $this->addPackage($this->createAliasPackage($package, $aliasData['alias'], $aliasData['alias_pretty']));
         }
     }
 
@@ -86,11 +98,10 @@ class FilesystemRepository extends ArrayRepository implements WritableRepository
         $packages = array();
         $dumper   = new ArrayDumper();
         foreach ($this->getPackages() as $package) {
-            $data = $dumper->dump($package);
-            if ($this instanceof InstalledRepositoryInterface && $package->isInstalledAsAlias()) {
-                $data['installed-as-alias'] = $package->getAlias();
+            if (!$package instanceof AliasPackage) {
+                $data = $dumper->dump($package);
+                $packages[] = $data;
             }
-            $packages[] = $data;
         }
 
         $this->file->write($packages);

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

@@ -27,7 +27,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
                 new Rule(array(), 'job1', null),
                 new Rule(array(), 'job2', null),
             ),
-            RuleSet::TYPE_FEATURE => array(
+            RuleSet::TYPE_LEARNED => array(
                 new Rule(array(), 'update1', null),
             ),
             RuleSet::TYPE_PACKAGE => array(),
@@ -46,7 +46,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
         $expected = array(
             $this->rules[RuleSet::TYPE_JOB][0],
             $this->rules[RuleSet::TYPE_JOB][1],
-            $this->rules[RuleSet::TYPE_FEATURE][0],
+            $this->rules[RuleSet::TYPE_LEARNED][0],
         );
 
         $this->assertEquals($expected, $result);
@@ -64,7 +64,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
         $expected = array(
             RuleSet::TYPE_JOB,
             RuleSet::TYPE_JOB,
-            RuleSet::TYPE_FEATURE,
+            RuleSet::TYPE_LEARNED,
         );
 
         $this->assertEquals($expected, $result);

+ 9 - 11
tests/Composer/Test/DependencyResolver/RuleSetTest.php

@@ -27,17 +27,15 @@ class RuleSetTest extends TestCase
                 new Rule(array(), 'job1', null),
                 new Rule(array(), 'job2', null),
             ),
-            RuleSet::TYPE_FEATURE => array(
+            RuleSet::TYPE_LEARNED => array(
                 new Rule(array(), 'update1', null),
             ),
-            RuleSet::TYPE_LEARNED => array(),
-            RuleSet::TYPE_CHOICE => array(),
         );
 
         $ruleSet = new RuleSet;
 
         $ruleSet->add($rules[RuleSet::TYPE_JOB][0], RuleSet::TYPE_JOB);
-        $ruleSet->add($rules[RuleSet::TYPE_FEATURE][0], RuleSet::TYPE_FEATURE);
+        $ruleSet->add($rules[RuleSet::TYPE_LEARNED][0], RuleSet::TYPE_LEARNED);
         $ruleSet->add($rules[RuleSet::TYPE_JOB][1], RuleSet::TYPE_JOB);
 
         $this->assertEquals($rules, $ruleSet->getRules());
@@ -80,7 +78,7 @@ class RuleSetTest extends TestCase
         $rule1 = new Rule(array(), 'job1', null);
         $rule2 = new Rule(array(), 'job1', null);
         $ruleSet->add($rule1, RuleSet::TYPE_JOB);
-        $ruleSet->add($rule2, RuleSet::TYPE_FEATURE);
+        $ruleSet->add($rule2, RuleSet::TYPE_LEARNED);
 
         $iterator = $ruleSet->getIterator();
 
@@ -96,9 +94,9 @@ class RuleSetTest extends TestCase
         $rule2 = new Rule(array(), 'job1', null);
 
         $ruleSet->add($rule1, RuleSet::TYPE_JOB);
-        $ruleSet->add($rule2, RuleSet::TYPE_FEATURE);
+        $ruleSet->add($rule2, RuleSet::TYPE_LEARNED);
 
-        $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_FEATURE);
+        $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_LEARNED);
 
         $this->assertSame($rule2, $iterator->current());
     }
@@ -110,7 +108,7 @@ class RuleSetTest extends TestCase
         $rule2 = new Rule(array(), 'job1', null);
 
         $ruleSet->add($rule1, RuleSet::TYPE_JOB);
-        $ruleSet->add($rule2, RuleSet::TYPE_FEATURE);
+        $ruleSet->add($rule2, RuleSet::TYPE_LEARNED);
 
         $iterator = $ruleSet->getIteratorWithout(RuleSet::TYPE_JOB);
 
@@ -142,7 +140,7 @@ class RuleSetTest extends TestCase
             ->method('equal')
             ->will($this->returnValue(false));
 
-        $ruleSet->add($rule, RuleSet::TYPE_FEATURE);
+        $ruleSet->add($rule, RuleSet::TYPE_LEARNED);
 
         $this->assertTrue($ruleSet->containsEqual($rule));
         $this->assertFalse($ruleSet->containsEqual($rule2));
@@ -155,9 +153,9 @@ class RuleSetTest extends TestCase
         $literal = new Literal($this->getPackage('foo', '2.1'), true);
         $rule = new Rule(array($literal), 'job1', null);
 
-        $ruleSet->add($rule, RuleSet::TYPE_FEATURE);
+        $ruleSet->add($rule, RuleSet::TYPE_JOB);
 
-        $this->assertContains('FEATURE : (+foo-2.1.0.0)', $ruleSet->__toString());
+        $this->assertContains('JOB     : (+foo-2.1.0.0)', $ruleSet->__toString());
     }
 
     private function getRuleMock()

+ 75 - 5
tests/Composer/Test/DependencyResolver/SolverTest.php

@@ -54,6 +54,16 @@ class SolverTest extends TestCase
         ));
     }
 
+    public function testSolverRemoveIfNotInstalled()
+    {
+        $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0'));
+        $this->reposComplete();
+
+        $this->checkSolverResult(array(
+            array('job' => 'remove', 'package' => $packageA),
+        ));
+    }
+
     public function testInstallNonExistingPackageFails()
     {
         $this->repo->addPackage($this->getPackage('A', '1.0'));
@@ -176,6 +186,7 @@ class SolverTest extends TestCase
         $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.1'));
         $this->reposComplete();
 
+        $this->request->install('A');
         $this->request->update('A');
 
         $this->checkSolverResult(array(
@@ -191,6 +202,7 @@ class SolverTest extends TestCase
         $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
 
         $packageA->setRequires(array(new Link('A', 'B', null, 'requires')));
+        $newPackageA->setRequires(array(new Link('A', 'B', null, 'requires')));
 
         $this->reposComplete();
 
@@ -209,6 +221,7 @@ class SolverTest extends TestCase
         $this->repo->addPackage($this->getPackage('A', '1.0'));
         $this->reposComplete();
 
+        $this->request->install('A');
         $this->request->update('A');
 
         $this->checkSolverResult(array());
@@ -223,6 +236,8 @@ class SolverTest extends TestCase
 
         $this->reposComplete();
 
+        $this->request->install('A');
+        $this->request->install('B');
         $this->request->update('A');
 
         $this->checkSolverResult(array(
@@ -267,7 +282,7 @@ class SolverTest extends TestCase
     public function testSolverUpdateFullyConstrainedPrunesInstalledPackages()
     {
         $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0'));
-        $this->repoInstalled->addPackage($this->getPackage('B', '1.0'));
+        $this->repoInstalled->addPackage($packageB = $this->getPackage('B', '1.0'));
         $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.2'));
         $this->repo->addPackage($this->getPackage('A', '2.0'));
         $this->reposComplete();
@@ -275,10 +290,15 @@ class SolverTest extends TestCase
         $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0'));
         $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0'));
 
-        $this->checkSolverResult(array(array(
-            'job' => 'update',
-            'from' => $packageA,
-            'to' => $newPackageA,
+        $this->checkSolverResult(array(
+            array(
+                'job' => 'remove',
+                'package' => $packageB,
+            ),
+            array(
+                'job' => 'update',
+                'from' => $packageA,
+                'to' => $newPackageA,
         )));
     }
 
@@ -297,6 +317,7 @@ class SolverTest extends TestCase
         $this->reposComplete();
 
         $this->request->install('A');
+        $this->request->install('C');
         $this->request->update('C');
         $this->request->remove('D');
 
@@ -649,6 +670,55 @@ class SolverTest extends TestCase
         ));
     }
 
+    public function testInstallRecursiveAliasDependencies()
+    {
+        $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
+        $this->repo->addPackage($packageB = $this->getPackage('B', '2.0'));
+        $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0'));
+
+        $packageA2->setRequires(array(
+            new Link('A', 'B', $this->getVersionConstraint('==', '2.0'), 'requires', '== 2.0'),
+        ));
+        $packageB->setRequires(array(
+            new Link('B', 'A', $this->getVersionConstraint('>=', '2.0'), 'requires'),
+        ));
+
+        $this->repo->addPackage($packageA2Alias = $this->getAliasPackage($packageA2, '1.1'));
+
+        $this->reposComplete();
+
+        $this->request->install('A', $this->getVersionConstraint('==', '1.1.0.0'));
+
+        $this->checkSolverResult(array(
+            array('job' => 'install', 'package' => $packageB),
+            array('job' => 'install', 'package' => $packageA2),
+            array('job' => 'install', 'package' => $packageA2Alias),
+        ));
+    }
+
+    public function testInstallDevAlias()
+    {
+        $this->repo->addPackage($packageA = $this->getPackage('A', '2.0'));
+        $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
+
+        $packageB->setRequires(array(
+            new Link('B', 'A', $this->getVersionConstraint('<', '2.0'), 'requires'),
+        ));
+
+        $this->repo->addPackage($packageAAlias = $this->getAliasPackage($packageA, '1.1'));
+
+        $this->reposComplete();
+
+        $this->request->install('A', $this->getVersionConstraint('==', '2.0'));
+        $this->request->install('B');
+
+        $this->checkSolverResult(array(
+            array('job' => 'install', 'package' => $packageAAlias),
+            array('job' => 'install', 'package' => $packageB),
+            array('job' => 'install', 'package' => $packageA),
+        ));
+    }
+
     protected function reposComplete()
     {
         $this->pool->addRepository($this->repoInstalled);

+ 7 - 0
tests/Composer/Test/TestCase.php

@@ -14,6 +14,7 @@ namespace Composer\Test;
 
 use Composer\Package\Version\VersionParser;
 use Composer\Package\MemoryPackage;
+use Composer\Package\AliasPackage;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Util\Filesystem;
 
@@ -45,6 +46,12 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
         return new MemoryPackage($name, $normVersion, $version);
     }
 
+    protected function getAliasPackage($package, $version)
+    {
+        $normVersion = self::getVersionParser()->normalize($version);
+        return new AliasPackage($package, $normVersion, $version);
+    }
+
     protected function ensureDirectoryExistsAndClear($directory)
     {
         $fs = new Filesystem();