Browse Source

Let the solver handle aliases instead of the installer

Nils Adermann 13 years ago
parent
commit
0c1944a9d0

+ 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 Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+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().')';
+    }
+}

+ 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;
         }
     }
 

+ 105 - 36
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;
 
 /**
@@ -255,8 +256,10 @@ class Solver
                         continue;
                     }
 
-                    $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
-                    $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link));
+                    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));
+                    }
                 }
             }
 
@@ -271,17 +274,31 @@ class Solver
                         continue;
                     }
 
-                    if ($isInstalled && !isset($this->installedMap[$provider->getId()])) {
-                        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));
                     }
-
-                    $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
-                    $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package));
                 }
             }
         }
     }
 
+    protected function obsoleteImpossibleForAlias($package, $provider)
+    {
+        $packageIsAlias = $package instanceof AliasPackage;
+        $providerIsAlias = $provider instanceof AliasPackage;
+
+        $impossible = (
+            ($packageIsAlias && $package->getAliasOf() === $provider) ||
+            ($providerIsAlias && $provider->getAliasOf() === $package) ||
+            ($packageIsAlias && $providerIsAlias && $provider->getAliasOf() === $package->getAliasOf())
+        );
+
+        return $impossible;
+    }
+
     /**
      * Adds all rules for all update packages of a given package
      *
@@ -646,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(
@@ -662,21 +687,69 @@ 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])) {
-                $transaction[] = new Operation\UninstallOperation(
-                    $this->pool->packageById($packageId), null
-                );
+                $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;
             }
         }
 
@@ -733,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)
@@ -876,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);
@@ -1060,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);
     }
@@ -1178,7 +1251,7 @@ class Solver
             $this->disableProblem($why);
             $this->resetSolver();
 
-            return true;
+            return 1;
         }
 
         if ($disableRules) {
@@ -1191,10 +1264,10 @@ class Solver
             }
 
             $this->resetSolver();
-            return true;
+            return 1;
         }
 
-        return false;
+        return 0;
     }
 
     private function disableProblem($why)
@@ -1266,8 +1339,8 @@ class Solver
         //    * here's the main loop:
         //    * 1) propagate new decisions (only needed once)
         //    * 2) fulfill jobs
-        //    * 4) fulfill all unresolved rules
-        //    * 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
         //    */
@@ -1403,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)) {
 

+ 8 - 15
src/Composer/Installer.php

@@ -326,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');
@@ -356,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) {
@@ -378,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;
     }
 

+ 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;
-    }
 }

+ 2 - 1
src/Composer/Package/Dumper/ArrayDumper.php

@@ -41,8 +41,9 @@ class ArrayDumper
 
         $data = array();
         $data['name'] = $package->getPrettyName();
+
         $data['version'] = $package->getPrettyVersion();
-        $data['version_normalized'] = $package->getVersion();
+        $data['version_normalized'] = $package->getVersion();;
 
         if ($package->getTargetDir()) {
             $data['target-dir'] = $package->getTargetDir();

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

@@ -89,7 +89,7 @@ class Locker
         $repo = $dev ? $this->repositoryManager->getLocalDevRepository() : $this->repositoryManager->getLocalRepository();
 
         foreach ($lockedPackages as $info) {
-            $resolvedVersion = !empty($info['alias']) ? $info['alias'] : $info['version'];
+            $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 +100,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 +179,10 @@ class Locker
         $locked = array();
 
         foreach ($packages as $package) {
+            $alias = null;
+
             if ($package instanceof AliasPackage) {
+                $alias = $package;
                 $package = $package->getAliasOf();
             }
 
@@ -198,8 +201,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);

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

@@ -670,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();